diff options
53 files changed, 2272 insertions, 784 deletions
diff --git a/cmds/hid/jni/com_android_commands_hid_Device.cpp b/cmds/hid/jni/com_android_commands_hid_Device.cpp index 437a87e8df54..422c2be44251 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.cpp +++ b/cmds/hid/jni/com_android_commands_hid_Device.cpp @@ -113,9 +113,10 @@ void DeviceCallback::onDeviceGetReport(uint32_t requestId, uint8_t reportId) { checkAndClearException(env, "onDeviceGetReport"); } -void DeviceCallback::onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data) { +void DeviceCallback::onDeviceOutput(uint8_t eventId, uint8_t rType, + const std::vector<uint8_t>& data) { JNIEnv* env = getJNIEnv(); - env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, rType, + env->CallVoidMethod(mCallbackObject, gDeviceCallbackClassInfo.onDeviceOutput, eventId, rType, toJbyteArray(env, data).get()); checkAndClearException(env, "onDeviceOutput"); } @@ -261,6 +262,7 @@ int Device::handleEvents(int events) { ALOGD("Received SET_REPORT: id=%" PRIu32 " rnum=%" PRIu8 " data=%s", set_report.id, set_report.rnum, toString(data).c_str()); } + mDeviceCallback->onDeviceOutput(UHID_SET_REPORT, set_report.rtype, data); break; } case UHID_OUTPUT: { @@ -269,7 +271,7 @@ int Device::handleEvents(int events) { if (DEBUG_OUTPUT) { ALOGD("UHID_OUTPUT rtype=%" PRIu8 " data=%s", output.rtype, toString(data).c_str()); } - mDeviceCallback->onDeviceOutput(output.rtype, data); + mDeviceCallback->onDeviceOutput(UHID_OUTPUT, output.rtype, data); break; } default: { @@ -365,7 +367,7 @@ int register_com_android_commands_hid_Device(JNIEnv* env) { uhid::gDeviceCallbackClassInfo.onDeviceGetReport = env->GetMethodID(clazz, "onDeviceGetReport", "(II)V"); uhid::gDeviceCallbackClassInfo.onDeviceOutput = - env->GetMethodID(clazz, "onDeviceOutput", "(B[B)V"); + env->GetMethodID(clazz, "onDeviceOutput", "(BB[B)V"); uhid::gDeviceCallbackClassInfo.onDeviceError = env->GetMethodID(clazz, "onDeviceError", "()V"); if (uhid::gDeviceCallbackClassInfo.onDeviceOpen == NULL || diff --git a/cmds/hid/jni/com_android_commands_hid_Device.h b/cmds/hid/jni/com_android_commands_hid_Device.h index 5483b40831a0..bb73132cc20d 100644 --- a/cmds/hid/jni/com_android_commands_hid_Device.h +++ b/cmds/hid/jni/com_android_commands_hid_Device.h @@ -31,7 +31,7 @@ public: void onDeviceOpen(); void onDeviceGetReport(uint32_t requestId, uint8_t reportId); - void onDeviceOutput(uint8_t rType, const std::vector<uint8_t>& data); + void onDeviceOutput(uint8_t eventId, uint8_t rType, const std::vector<uint8_t>& data); void onDeviceError(); private: diff --git a/cmds/hid/src/com/android/commands/hid/Device.java b/cmds/hid/src/com/android/commands/hid/Device.java index 20b4bd86baec..37d0b1c6bfa1 100644 --- a/cmds/hid/src/com/android/commands/hid/Device.java +++ b/cmds/hid/src/com/android/commands/hid/Device.java @@ -46,7 +46,8 @@ public class Device { // Sync with linux uhid_event_type::UHID_OUTPUT private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6; - + // Sync with linux uhid_event_type::UHID_SET_REPORT + private static final byte UHID_EVENT_TYPE_SET_REPORT = 13; private final int mId; private final HandlerThread mThread; private final DeviceHandler mHandler; @@ -199,10 +200,10 @@ public class Device { } // native callback - public void onDeviceOutput(byte rtype, byte[] data) { + public void onDeviceOutput(byte eventId, byte rtype, byte[] data) { JSONObject json = new JSONObject(); try { - json.put("eventId", UHID_EVENT_TYPE_UHID_OUTPUT); + json.put("eventId", eventId); json.put("deviceId", mId); json.put("reportType", rtype); JSONArray dataArray = new JSONArray(); diff --git a/core/api/current.txt b/core/api/current.txt index b0375b69553d..db5d143662e4 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -18659,6 +18659,58 @@ package android.hardware.input { } +package android.hardware.lights { + + public final class Light implements android.os.Parcelable { + method public int describeContents(); + method public int getId(); + method @NonNull public String getName(); + method public int getOrdinal(); + method public int getType(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR; + field public static final int LIGHT_TYPE_INPUT_PLAYER_ID = 10; // 0xa + field public static final int LIGHT_TYPE_INPUT_RGB = 11; // 0xb + field public static final int LIGHT_TYPE_INPUT_SINGLE = 9; // 0x9 + field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8 + } + + public final class LightState implements android.os.Parcelable { + method public int describeContents(); + method @NonNull public static android.hardware.lights.LightState forColor(@ColorInt int); + method @NonNull public static android.hardware.lights.LightState forPlayerId(int); + method @ColorInt public int getColor(); + method public int getPlayerId(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR; + } + + public abstract class LightsManager { + method @NonNull public abstract android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light); + method @NonNull public abstract java.util.List<android.hardware.lights.Light> getLights(); + method @NonNull public abstract android.hardware.lights.LightsManager.LightsSession openSession(); + } + + public abstract static class LightsManager.LightsSession implements java.lang.AutoCloseable { + ctor public LightsManager.LightsSession(); + method public abstract void close(); + method public abstract void requestLights(@NonNull android.hardware.lights.LightsRequest); + } + + public final class LightsRequest { + method @NonNull public java.util.List<android.hardware.lights.LightState> getLightStates(); + method @NonNull public java.util.List<java.lang.Integer> getLights(); + } + + public static final class LightsRequest.Builder { + ctor public LightsRequest.Builder(); + method @NonNull public android.hardware.lights.LightsRequest.Builder addLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState); + method @NonNull public android.hardware.lights.LightsRequest build(); + method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light); + } + +} + package android.hardware.usb { public class UsbAccessory implements android.os.Parcelable { @@ -46898,6 +46950,7 @@ package android.view { method public int getId(); method public android.view.KeyCharacterMap getKeyCharacterMap(); method public int getKeyboardType(); + method @NonNull public android.hardware.lights.LightsManager getLightsManager(); method public android.view.InputDevice.MotionRange getMotionRange(int); method public android.view.InputDevice.MotionRange getMotionRange(int, int); method public java.util.List<android.view.InputDevice.MotionRange> getMotionRanges(); @@ -55071,6 +55124,7 @@ package android.widget { method public void setChronometerCountDown(@IdRes int, boolean); method public void setColor(@IdRes int, @NonNull String, @ColorRes int); method public void setColorInt(@IdRes int, @NonNull String, @ColorInt int, @ColorInt int); + method public void setColorStateList(@IdRes int, @NonNull String, @Nullable android.content.res.ColorStateList); method public void setColorStateList(@IdRes int, @NonNull String, @Nullable android.content.res.ColorStateList, @Nullable android.content.res.ColorStateList); method public void setColorStateList(@IdRes int, @NonNull String, @ColorRes int); method public void setCompoundButtonChecked(@IdRes int, boolean); @@ -55113,6 +55167,12 @@ package android.widget { method public void setTextViewText(@IdRes int, CharSequence); method public void setTextViewTextSize(@IdRes int, int, float); method public void setUri(@IdRes int, String, android.net.Uri); + method public void setViewLayoutHeight(@IdRes int, float, int); + method public void setViewLayoutHeightDimen(@IdRes int, @DimenRes int); + method public void setViewLayoutMargin(@IdRes int, int, float, int); + method public void setViewLayoutMarginDimen(@IdRes int, int, @DimenRes int); + method public void setViewLayoutWidth(@IdRes int, float, int); + method public void setViewLayoutWidthDimen(@IdRes int, @DimenRes int); method public void setViewOutlinePreferredRadius(@IdRes int, float, int); method public void setViewOutlinePreferredRadiusDimen(@IdRes int, @DimenRes int); method public void setViewPadding(@IdRes int, @Px int, @Px int, @Px int, @Px int); @@ -55123,6 +55183,12 @@ package android.widget { field @NonNull public static final android.os.Parcelable.Creator<android.widget.RemoteViews> CREATOR; field public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED"; field public static final String EXTRA_SHARED_ELEMENT_BOUNDS = "android.widget.extra.SHARED_ELEMENT_BOUNDS"; + field public static final int MARGIN_BOTTOM = 3; // 0x3 + field public static final int MARGIN_END = 5; // 0x5 + field public static final int MARGIN_LEFT = 0; // 0x0 + field public static final int MARGIN_RIGHT = 2; // 0x2 + field public static final int MARGIN_START = 4; // 0x4 + field public static final int MARGIN_TOP = 1; // 0x1 } public static class RemoteViews.ActionException extends java.lang.RuntimeException { diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt index de02d0bb7e98..5dc1cd7a3173 100644 --- a/core/api/module-lib-current.txt +++ b/core/api/module-lib-current.txt @@ -135,7 +135,7 @@ package android.media.session { } public final class MediaSessionManager { - method public void addOnActiveSessionsChangedListener(@NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener, @Nullable android.content.ComponentName, @NonNull android.os.UserHandle, @Nullable android.os.Handler); + method public void addOnActiveSessionsChangedListener(@Nullable android.content.ComponentName, @NonNull android.os.UserHandle, @NonNull java.util.concurrent.Executor, @NonNull android.media.session.MediaSessionManager.OnActiveSessionsChangedListener); method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent); method public void dispatchMediaKeyEvent(@NonNull android.view.KeyEvent, boolean); method public void dispatchMediaKeyEventAsSystemService(@NonNull android.view.KeyEvent); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index d79c11db9d09..a5d40a63a795 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3398,42 +3398,12 @@ package android.hardware.hdmi { package android.hardware.lights { - public final class Light implements android.os.Parcelable { - method public int describeContents(); - method public int getId(); - method public int getOrdinal(); - method public int getType(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.Light> CREATOR; - } - public final class LightState implements android.os.Parcelable { - ctor public LightState(@ColorInt int); - method public int describeContents(); - method @ColorInt public int getColor(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.hardware.lights.LightState> CREATOR; - } - - public final class LightsManager { - method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public java.util.List<android.hardware.lights.Light> getLights(); - method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightsManager.LightsSession openSession(); - field public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8 - } - - public final class LightsManager.LightsSession implements java.lang.AutoCloseable { - method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void close(); - method @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public void requestLights(@NonNull android.hardware.lights.LightsRequest); - } - - public final class LightsRequest { + ctor @Deprecated public LightState(@ColorInt int); } - public static final class LightsRequest.Builder { - ctor public LightsRequest.Builder(); - method @NonNull public android.hardware.lights.LightsRequest build(); - method @NonNull public android.hardware.lights.LightsRequest.Builder clearLight(@NonNull android.hardware.lights.Light); - method @NonNull public android.hardware.lights.LightsRequest.Builder setLight(@NonNull android.hardware.lights.Light, @NonNull android.hardware.lights.LightState); + public abstract class LightsManager { + field @Deprecated public static final int LIGHT_TYPE_MICROPHONE = 8; // 0x8 } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d7b43c0bc48e..fc1edf115625 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -100,7 +100,7 @@ package android.app { method @RequiresPermission(android.Manifest.permission.CHANGE_CONFIGURATION) public boolean updateMccMncConfiguration(@NonNull String, @NonNull String); field public static final long DROP_CLOSE_SYSTEM_DIALOGS = 174664120L; // 0xa6929b8L field public static final long LOCK_DOWN_CLOSE_SYSTEM_DIALOGS = 174664365L; // 0xa692aadL - field public static final int PROCESS_CAPABILITY_ALL = 7; // 0x7 + field public static final int PROCESS_CAPABILITY_ALL = 15; // 0xf field public static final int PROCESS_CAPABILITY_ALL_EXPLICIT = 1; // 0x1 field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6 field public static final int PROCESS_CAPABILITY_FOREGROUND_CAMERA = 2; // 0x2 @@ -1001,14 +1001,6 @@ package android.hardware.input { } -package android.hardware.lights { - - public final class LightsManager { - method @NonNull @RequiresPermission(android.Manifest.permission.CONTROL_DEVICE_LIGHTS) public android.hardware.lights.LightState getLightState(@NonNull android.hardware.lights.Light); - } - -} - package android.hardware.soundtrigger { public class KeyphraseEnrollmentInfo { diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index e2426d116319..220c332647a9 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -616,11 +616,15 @@ public class ActivityManager { @TestApi public static final int PROCESS_CAPABILITY_FOREGROUND_MICROPHONE = 1 << 2; + /** @hide Process can access network despite any power saving resrictions */ + public static final int PROCESS_CAPABILITY_NETWORK = 1 << 3; + /** @hide all capabilities, the ORing of all flags in {@link ProcessCapability}*/ @TestApi public static final int PROCESS_CAPABILITY_ALL = PROCESS_CAPABILITY_FOREGROUND_LOCATION | PROCESS_CAPABILITY_FOREGROUND_CAMERA - | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE; + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE + | PROCESS_CAPABILITY_NETWORK; /** * All explicit capabilities. These are capabilities that need to be specified from manifest * file. @@ -646,6 +650,15 @@ public class ActivityManager { pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-'); pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-'); pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-'); + pw.print((caps & PROCESS_CAPABILITY_NETWORK) != 0 ? 'N' : '-'); + } + + /** @hide */ + public static void printCapabilitiesSummary(StringBuilder sb, @ProcessCapability int caps) { + sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_LOCATION) != 0 ? 'L' : '-'); + sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_CAMERA) != 0 ? 'C' : '-'); + sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-'); + sb.append((caps & PROCESS_CAPABILITY_NETWORK) != 0 ? 'N' : '-'); } /** @@ -656,13 +669,21 @@ public class ActivityManager { printCapabilitiesSummary(pw, caps); final int remain = caps & ~(PROCESS_CAPABILITY_FOREGROUND_LOCATION | PROCESS_CAPABILITY_FOREGROUND_CAMERA - | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE); + | PROCESS_CAPABILITY_FOREGROUND_MICROPHONE + | PROCESS_CAPABILITY_NETWORK); if (remain != 0) { pw.print('+'); pw.print(remain); } } + /** @hide */ + public static String getCapabilitiesSummary(@ProcessCapability int caps) { + final StringBuilder sb = new StringBuilder(); + printCapabilitiesSummary(sb, caps); + return sb.toString(); + } + // NOTE: If PROCESS_STATEs are added, then new fields must be added // to frameworks/base/core/proto/android/app/enums.proto and the following method must // be updated to correctly map between them. @@ -4485,6 +4506,80 @@ public class ActivityManager { } } + /** @hide */ + public static String procStateToString(int procState) { + final String procStateStr; + switch (procState) { + case ActivityManager.PROCESS_STATE_PERSISTENT: + procStateStr = "PER "; + break; + case ActivityManager.PROCESS_STATE_PERSISTENT_UI: + procStateStr = "PERU"; + break; + case ActivityManager.PROCESS_STATE_TOP: + procStateStr = "TOP "; + break; + case ActivityManager.PROCESS_STATE_BOUND_TOP: + procStateStr = "BTOP"; + break; + case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE: + procStateStr = "FGS "; + break; + case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE: + procStateStr = "BFGS"; + break; + case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: + procStateStr = "IMPF"; + break; + case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: + procStateStr = "IMPB"; + break; + case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND: + procStateStr = "TRNB"; + break; + case ActivityManager.PROCESS_STATE_BACKUP: + procStateStr = "BKUP"; + break; + case ActivityManager.PROCESS_STATE_SERVICE: + procStateStr = "SVC "; + break; + case ActivityManager.PROCESS_STATE_RECEIVER: + procStateStr = "RCVR"; + break; + case ActivityManager.PROCESS_STATE_TOP_SLEEPING: + procStateStr = "TPSL"; + break; + case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: + procStateStr = "HVY "; + break; + case ActivityManager.PROCESS_STATE_HOME: + procStateStr = "HOME"; + break; + case ActivityManager.PROCESS_STATE_LAST_ACTIVITY: + procStateStr = "LAST"; + break; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + procStateStr = "CAC "; + break; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + procStateStr = "CACC"; + break; + case ActivityManager.PROCESS_STATE_CACHED_RECENT: + procStateStr = "CRE "; + break; + case ActivityManager.PROCESS_STATE_CACHED_EMPTY: + procStateStr = "CEM "; + break; + case ActivityManager.PROCESS_STATE_NONEXISTENT: + procStateStr = "NONE"; + break; + default: + procStateStr = "??"; + break; + } + return procStateStr; + } + /** * The AppTask allows you to manage your own application's tasks. * See {@link android.app.ActivityManager#getAppTasks()} diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java index ffaaa578cdd2..dd5a9a8a28af 100644 --- a/core/java/android/app/SystemServiceRegistry.java +++ b/core/java/android/app/SystemServiceRegistry.java @@ -99,6 +99,7 @@ import android.hardware.input.InputManager; import android.hardware.iris.IIrisService; import android.hardware.iris.IrisManager; import android.hardware.lights.LightsManager; +import android.hardware.lights.SystemLightsManager; import android.hardware.location.ContextHubManager; import android.hardware.radio.RadioManager; import android.hardware.usb.IUsbManager; @@ -1344,7 +1345,7 @@ public final class SystemServiceRegistry { @Override public LightsManager createService(ContextImpl ctx) throws ServiceNotFoundException { - return new LightsManager(ctx); + return new SystemLightsManager(ctx); }}); registerService(Context.INCREMENTAL_SERVICE, IncrementalManager.class, new CachedServiceFetcher<IncrementalManager>() { diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl index eaa38f3e862c..4743fee3257b 100644 --- a/core/java/android/hardware/input/IInputManager.aidl +++ b/core/java/android/hardware/input/IInputManager.aidl @@ -25,6 +25,8 @@ import android.hardware.input.TouchCalibration; import android.os.CombinedVibrationEffect; import android.hardware.input.IInputSensorEventListener; import android.hardware.input.InputSensorInfo; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; import android.os.IBinder; import android.os.IVibratorStateListener; import android.os.VibrationEffect; @@ -127,4 +129,14 @@ interface IInputManager { void disableSensor(int deviceId, int sensorType); boolean flushSensor(int deviceId, int sensorType); + + List<Light> getLights(int deviceId); + + LightState getLightState(int deviceId, int lightId); + + void setLightStates(int deviceId, in int[] lightIds, in LightState[] states, in IBinder token); + + void openLightSession(int deviceId, String opPkg, in IBinder token); + + void closeLightSession(int deviceId, in IBinder token); } diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java new file mode 100644 index 000000000000..a3b91a99fdb7 --- /dev/null +++ b/core/java/android/hardware/input/InputDeviceLightsManager.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import android.annotation.NonNull; +import android.app.ActivityThread; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; +import android.hardware.lights.LightsManager; +import android.hardware.lights.LightsRequest; +import android.util.CloseGuard; + +import com.android.internal.util.Preconditions; + +import java.lang.ref.Reference; +import java.util.List; + +/** + * LightsManager manages an input device's lights {@link android.hardware.input.Light}. + */ +class InputDeviceLightsManager extends LightsManager { + private static final String TAG = "InputDeviceLightsManager"; + private static final boolean DEBUG = false; + + private final InputManager mInputManager; + + // The input device ID. + private final int mDeviceId; + // Package name + private final String mPackageName; + + InputDeviceLightsManager(InputManager inputManager, int deviceId) { + super(ActivityThread.currentActivityThread().getSystemContext()); + mInputManager = inputManager; + mDeviceId = deviceId; + mPackageName = ActivityThread.currentPackageName(); + } + + /** + * Returns the lights available on the device. + * + * @return A list of available lights + */ + @Override + public @NonNull List<Light> getLights() { + return mInputManager.getLights(mDeviceId); + } + + /** + * Returns the state of a specified light. + * + * @hide + */ + @Override + public @NonNull LightState getLightState(@NonNull Light light) { + Preconditions.checkNotNull(light); + return mInputManager.getLightState(mDeviceId, light); + } + + /** + * Creates a new LightsSession that can be used to control the device lights. + */ + @Override + public @NonNull LightsSession openSession() { + final LightsSession session = new InputDeviceLightsSession(); + mInputManager.openLightSession(mDeviceId, mPackageName, session.getToken()); + return session; + } + + /** + * Encapsulates a session that can be used to control device lights and represents the lifetime + * of the requests. + */ + public final class InputDeviceLightsSession extends LightsManager.LightsSession + implements AutoCloseable { + + private final CloseGuard mCloseGuard = new CloseGuard(); + private boolean mClosed = false; + + /** + * Instantiated by {@link LightsManager#openSession()}. + */ + private InputDeviceLightsSession() { + mCloseGuard.open("close"); + } + + /** + * Sends a request to modify the states of multiple lights. + * + * @param request the settings for lights that should change + */ + @Override + public void requestLights(@NonNull LightsRequest request) { + Preconditions.checkNotNull(request); + Preconditions.checkArgument(!mClosed); + + mInputManager.requestLights(mDeviceId, request, getToken()); + } + + /** + * Closes the session, reverting all changes made through it. + */ + @Override + public void close() { + if (!mClosed) { + mInputManager.closeLightSession(mDeviceId, getToken()); + mClosed = true; + mCloseGuard.close(); + } + Reference.reachabilityFence(this); + } + + /** @hide */ + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + close(); + } finally { + super.finalize(); + } + } + } + +} diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 8a01c660ebd0..e15d6298d63d 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -30,6 +30,10 @@ import android.compat.annotation.ChangeId; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.hardware.SensorManager; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; +import android.hardware.lights.LightsManager; +import android.hardware.lights.LightsRequest; import android.os.BlockUntrustedTouchesMode; import android.os.Build; import android.os.CombinedVibrationEffect; @@ -1409,7 +1413,7 @@ public final class InputManager { } /** - * Gets a vibrator service associated with an input device, always create a new instance. + * Gets a vibrator service associated with an input device, always creates a new instance. * @return The vibrator, never null. * @hide */ @@ -1418,7 +1422,7 @@ public final class InputManager { } /** - * Gets a vibrator manager service associated with an input device, always create a new + * Gets a vibrator manager service associated with an input device, always creates a new * instance. * @return The vibrator manager, never null. * @hide @@ -1486,10 +1490,8 @@ public final class InputManager { /** * Register input device vibrator state listener - * - * @hide */ - public boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) { + boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) { try { return mIm.registerVibratorStateListener(deviceId, listener); } catch (RemoteException ex) { @@ -1499,10 +1501,8 @@ public final class InputManager { /** * Unregister input device vibrator state listener - * - * @hide */ - public boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) { + boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) { try { return mIm.unregisterVibratorStateListener(deviceId, listener); } catch (RemoteException ex) { @@ -1511,7 +1511,7 @@ public final class InputManager { } /** - * Gets a sensor manager service associated with an input device, always create a new instance. + * Gets a sensor manager service associated with an input device, always creates a new instance. * @return The sensor manager, never null. * @hide */ @@ -1533,6 +1533,86 @@ public final class InputManager { } /** + * Gets a lights manager associated with an input device, always creates a new instance. + * @return The lights manager, never null. + * @hide + */ + @NonNull + public LightsManager getInputDeviceLightsManager(int deviceId) { + return new InputDeviceLightsManager(InputManager.this, deviceId); + } + + /** + * Gets a list of light objects associated with an input device. + * @return The list of lights, never null. + */ + @NonNull List<Light> getLights(int deviceId) { + try { + return mIm.getLights(deviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the state of an input device light. + * @return the light state + */ + @NonNull LightState getLightState(int deviceId, @NonNull Light light) { + try { + return mIm.getLightState(deviceId, light.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to modify the states of multiple lights. + * + * @param request the settings for lights that should change + */ + void requestLights(int deviceId, @NonNull LightsRequest request, IBinder token) { + try { + List<Integer> lightIdList = request.getLights(); + int[] lightIds = new int[lightIdList.size()]; + for (int i = 0; i < lightIds.length; i++) { + lightIds[i] = lightIdList.get(i); + } + List<LightState> lightStateList = request.getLightStates(); + mIm.setLightStates(deviceId, lightIds, + lightStateList.toArray(new LightState[lightStateList.size()]), + token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Open light session for input device manager + * + * @param token The token for the light session + */ + void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) { + try { + mIm.openLightSession(deviceId, opPkg, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Close light session + * + */ + void closeLightSession(int deviceId, @NonNull IBinder token) { + try { + mIm.closeLightSession(deviceId, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Listens for changes in input devices. */ public interface InputDeviceListener { diff --git a/core/java/android/hardware/lights/Light.java b/core/java/android/hardware/lights/Light.java index da270182052d..7bfff5d3af97 100644 --- a/core/java/android/hardware/lights/Light.java +++ b/core/java/android/hardware/lights/Light.java @@ -16,22 +16,56 @@ package android.hardware.lights; +import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.annotation.SystemApi; import android.os.Parcel; import android.os.Parcelable; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Represents a logical light on the device. * - * @hide */ -@SystemApi public final class Light implements Parcelable { + // These enum values copy the values from {@link com.android.server.lights.LightsManager} + // and the light HAL. Since 0-7 are lights reserved for system use, 8 for microphone light is + // defined in {@link android.hardware.lights.LightsManager}, following types are available + // through this API. + /** Type for lights that indicate microphone usage */ + public static final int LIGHT_TYPE_MICROPHONE = 8; + + /** + * Type for lights that indicate a monochrome color LED light. + */ + public static final int LIGHT_TYPE_INPUT_SINGLE = 9; + + /** + * Type for lights that indicate a group of LED lights representing player ID. + */ + public static final int LIGHT_TYPE_INPUT_PLAYER_ID = 10; + + /** + * Type for lights that indicate a color LED light. + */ + public static final int LIGHT_TYPE_INPUT_RGB = 11; + + /** @hide */ + @Retention(RetentionPolicy.SOURCE) + @IntDef(prefix = {"LIGHT_TYPE_"}, + value = { + LIGHT_TYPE_INPUT_PLAYER_ID, + LIGHT_TYPE_INPUT_SINGLE, + LIGHT_TYPE_INPUT_RGB, + }) + public @interface LightType {} + private final int mId; private final int mOrdinal; private final int mType; + private final String mName; /** * Creates a new light with the given data. @@ -39,15 +73,26 @@ public final class Light implements Parcelable { * @hide */ public Light(int id, int ordinal, int type) { + this(id, ordinal, type, "Light"); + } + + /** + * Creates a new light with the given data. + * + * @hide + */ + public Light(int id, int ordinal, int type, String name) { mId = id; mOrdinal = ordinal; mType = type; + mName = name; } private Light(@NonNull Parcel in) { mId = in.readInt(); mOrdinal = in.readInt(); mType = in.readInt(); + mName = in.readString(); } /** Implement the Parcelable interface */ @@ -56,6 +101,7 @@ public final class Light implements Parcelable { dest.writeInt(mId); dest.writeInt(mOrdinal); dest.writeInt(mType); + dest.writeString(mName); } /** Implement the Parcelable interface */ @@ -100,6 +146,14 @@ public final class Light implements Parcelable { } /** + * Returns the name of the light. + */ + @NonNull + public String getName() { + return mName; + } + + /** * Returns the ordinal of the light. * * <p>This is a sort key that represents the physical order of lights on the device with the diff --git a/core/java/android/hardware/lights/LightState.java b/core/java/android/hardware/lights/LightState.java index cd39e6df91a9..650b383eeb0f 100644 --- a/core/java/android/hardware/lights/LightState.java +++ b/core/java/android/hardware/lights/LightState.java @@ -32,36 +32,93 @@ import android.os.Parcelable; * will be converted to only a brightness value and that will be used for the light's single * channel. * - * @hide */ -@SystemApi public final class LightState implements Parcelable { private final int mColor; + private final int mPlayerId; /** - * Creates a new LightState with the desired color and intensity. + * Creates a new LightState with the desired color and intensity, for a light type + * of RBG color or monochrome color. * * @param color the desired color and intensity in ARGB format. + * @deprecated this has been replaced with {@link android.hardware.lights.LightState#forColor } + * @hide */ + @Deprecated + @SystemApi public LightState(@ColorInt int color) { + this(color, 0); + } + + /** + * Creates a new LightState with the desired color and intensity, and the player Id. + * Player Id will only be applied on Light type + * {@link android.hardware.lights.Light#LIGHT_TYPE_INPUT_PLAYER_ID} + * + * @param color the desired color and intensity in ARGB format. + * @hide + */ + public LightState(@ColorInt int color, int playerId) { mColor = color; + mPlayerId = playerId; + } + + /** + * Creates a new LightState with the desired color and intensity, for a light type + * of RBG color or single monochrome color. + * + * @param color the desired color and intensity in ARGB format. + * @return The LightState object contains the color. + */ + @NonNull + public static LightState forColor(@ColorInt int color) { + return new LightState(color, 0); + } + + /** + * Creates a new LightState with the desired player id, for a light of type + * {@link android.hardware.lights.Light#LIGHT_TYPE_INPUT_PLAYER_ID}. + * + * @param playerId the desired player id. + * @return The LightState object contains the player id. + */ + @NonNull + public static LightState forPlayerId(int playerId) { + return new LightState(0, playerId); } + /** + * Creates a new LightState from a parcel object. + */ private LightState(@NonNull Parcel in) { mColor = in.readInt(); + mPlayerId = in.readInt(); } /** - * Return the color and intensity associated with this LightState. - * @return the color and intensity in ARGB format. The A channel is ignored. + * Returns the color and intensity associated with this LightState. + * @return the color and intensity in ARGB format. The A channel is ignored. return 0 when + * calling LightsManager.getLightState with LIGHT_TYPE_INPUT_PLAYER_ID. */ public @ColorInt int getColor() { return mColor; } + /** + * Returns the player ID associated with this LightState for Light type + * {@link android.hardware.lights.Light#LIGHT_TYPE_INPUT_PLAYER_ID}, + * or 0 for other types. + * @return the player ID. + */ + public int getPlayerId() { + return mPlayerId; + } + @Override public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mColor); + dest.writeInt(mPlayerId); } @Override @@ -69,6 +126,12 @@ public final class LightState implements Parcelable { return 0; } + @Override + public String toString() { + return "LightState{Color=0x" + Integer.toHexString(mColor) + ", PlayerId=" + + mPlayerId + "}"; + } + public static final @NonNull Parcelable.Creator<LightState> CREATOR = new Parcelable.Creator<LightState>() { public LightState createFromParcel(Parcel in) { diff --git a/core/java/android/hardware/lights/LightsManager.java b/core/java/android/hardware/lights/LightsManager.java index 33e5fcaf2abb..8fd56db33c4b 100644 --- a/core/java/android/hardware/lights/LightsManager.java +++ b/core/java/android/hardware/lights/LightsManager.java @@ -16,43 +16,38 @@ package android.hardware.lights; -import android.Manifest; import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.annotation.SystemService; -import android.annotation.TestApi; import android.content.Context; import android.os.Binder; import android.os.IBinder; -import android.os.RemoteException; -import android.os.ServiceManager; -import android.os.ServiceManager.ServiceNotFoundException; -import android.util.CloseGuard; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; -import java.lang.ref.Reference; import java.util.List; /** * The LightsManager class allows control over device lights. * - * @hide */ -@SystemApi @SystemService(Context.LIGHTS_SERVICE) -public final class LightsManager { +public abstract class LightsManager { private static final String TAG = "LightsManager"; + @NonNull private final Context mContext; // These enum values copy the values from {@link com.android.server.lights.LightsManager} // and the light HAL. Since 0-7 are lights reserved for system use, only the microphone light - // is available through this API. - /** Type for lights that indicate microphone usage */ + // and following types are available through this API. + /** Type for lights that indicate microphone usage + * @deprecated this has been moved to {@link android.hardware.lights.Light } + * @hide + */ + @Deprecated + @SystemApi public static final int LIGHT_TYPE_MICROPHONE = 8; /** @hide */ @@ -63,28 +58,11 @@ public final class LightsManager { }) public @interface LightType {} - @NonNull private final Context mContext; - @NonNull private final ILightsManager mService; - /** - * Creates a LightsManager. - * - * @hide + * @hide to prevent subclassing from outside of the framework */ - public LightsManager(@NonNull Context context) throws ServiceNotFoundException { - this(context, ILightsManager.Stub.asInterface( - ServiceManager.getServiceOrThrow(Context.LIGHTS_SERVICE))); - } - - /** - * Creates a LightsManager with a provided service implementation. - * - * @hide - */ - @VisibleForTesting - public LightsManager(@NonNull Context context, @NonNull ILightsManager service) { + public LightsManager(Context context) { mContext = Preconditions.checkNotNull(context); - mService = Preconditions.checkNotNull(service); } /** @@ -92,112 +70,44 @@ public final class LightsManager { * * @return A list of available lights */ - @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) - public @NonNull List<Light> getLights() { - try { - return mService.getLights(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + public @NonNull abstract List<Light> getLights(); /** * Returns the state of a specified light. * - * @hide */ - @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) - @TestApi - public @NonNull LightState getLightState(@NonNull Light light) { - Preconditions.checkNotNull(light); - try { - return mService.getLightState(light.getId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + public abstract @NonNull LightState getLightState(@NonNull Light light); /** * Creates a new LightsSession that can be used to control the device lights. */ - @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) - public @NonNull LightsSession openSession() { - try { - final LightsSession session = new LightsSession(); - mService.openSession(session.mToken); - return session; - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } + public abstract @NonNull LightsSession openSession(); /** * Encapsulates a session that can be used to control device lights and represents the lifetime * of the requests. */ - public final class LightsSession implements AutoCloseable { - + public abstract static class LightsSession implements AutoCloseable { private final IBinder mToken = new Binder(); - - private final CloseGuard mCloseGuard = new CloseGuard(); - private boolean mClosed = false; - - /** - * Instantiated by {@link LightsManager#openSession()}. - */ - @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) - private LightsSession() { - mCloseGuard.open("close"); - } - /** * Sends a request to modify the states of multiple lights. * - * <p>This method only controls lights that aren't overridden by higher-priority sessions. - * Additionally, lights not controlled by this session can be controlled by lower-priority - * sessions. - * * @param request the settings for lights that should change */ - @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) - public void requestLights(@NonNull LightsRequest request) { - Preconditions.checkNotNull(request); - if (!mClosed) { - try { - mService.setLightStates(mToken, request.mLightIds, request.mLightStates); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - } + public abstract void requestLights(@NonNull LightsRequest request); - /** - * Closes the session, reverting all changes made through it. - */ - @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) @Override - public void close() { - if (!mClosed) { - try { - mService.closeSession(mToken); - mClosed = true; - mCloseGuard.close(); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - Reference.reachabilityFence(this); - } + public abstract void close(); - /** @hide */ - @Override - protected void finalize() throws Throwable { - try { - mCloseGuard.warnIfOpen(); - close(); - } finally { - super.finalize(); - } + /** + * Get the token of a light session. + * + * @return Binder token of the light session. + * @hide + */ + public @NonNull IBinder getToken() { + return mToken; } } + } diff --git a/core/java/android/hardware/lights/LightsRequest.java b/core/java/android/hardware/lights/LightsRequest.java index a318992c35ee..2626a461aaf5 100644 --- a/core/java/android/hardware/lights/LightsRequest.java +++ b/core/java/android/hardware/lights/LightsRequest.java @@ -17,17 +17,17 @@ package android.hardware.lights; import android.annotation.NonNull; -import android.annotation.SystemApi; import android.util.SparseArray; import com.android.internal.util.Preconditions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; /** * Encapsulates a request to modify the state of multiple lights. * - * @hide */ -@SystemApi public final class LightsRequest { /** Visible to {@link LightsManager.Session}. */ @@ -50,6 +50,30 @@ public final class LightsRequest { } /** + * Get a list of Light as ids. The ids will returned in same order as the lights passed + * in Builder. + * + * @return List of light ids + */ + public @NonNull List<Integer> getLights() { + List<Integer> lightList = new ArrayList<Integer>(mLightIds.length); + for (int i = 0; i < mLightIds.length; i++) { + lightList.add(mLightIds[i]); + } + return lightList; + } + + /** + * Get a list of LightState. The states will be returned in same order as the light states + * passed in Builder. + * + * @return List of light states + */ + public @NonNull List<LightState> getLightStates() { + return Arrays.asList(mLightStates); + } + + /** * Builder for creating device light change requests. */ public static final class Builder { @@ -62,7 +86,7 @@ public final class LightsRequest { * @param light the light to modify * @param state the desired color and intensity of the light */ - public @NonNull Builder setLight(@NonNull Light light, @NonNull LightState state) { + public @NonNull Builder addLight(@NonNull Light light, @NonNull LightState state) { Preconditions.checkNotNull(light); Preconditions.checkNotNull(state); mChanges.put(light.getId(), state); diff --git a/core/java/android/hardware/lights/SystemLightsManager.java b/core/java/android/hardware/lights/SystemLightsManager.java new file mode 100644 index 000000000000..726a61359c01 --- /dev/null +++ b/core/java/android/hardware/lights/SystemLightsManager.java @@ -0,0 +1,181 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.lights; + +import android.Manifest; +import android.annotation.NonNull; +import android.annotation.RequiresPermission; +import android.content.Context; +import android.hardware.lights.LightsManager.LightsSession; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.util.CloseGuard; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.Preconditions; + +import java.lang.ref.Reference; +import java.util.List; + +/** + * The LightsManager class allows control over device lights. + * + * @hide + */ +public final class SystemLightsManager extends LightsManager { + private static final String TAG = "LightsManager"; + + @NonNull private final ILightsManager mService; + + /** + * Creates a SystemLightsManager. + * + * @hide + */ + public SystemLightsManager(@NonNull Context context) throws ServiceNotFoundException { + this(context, ILightsManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.LIGHTS_SERVICE))); + } + + /** + * Creates a SystemLightsManager with a provided service implementation. + * + * @hide + */ + @VisibleForTesting + public SystemLightsManager(@NonNull Context context, @NonNull ILightsManager service) { + super(context); + mService = Preconditions.checkNotNull(service); + } + + /** + * Returns the lights available on the device. + * + * @return A list of available lights + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @Override + public @NonNull List<Light> getLights() { + try { + return mService.getLights(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the state of a specified light. + * + * @hide + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @Override + public @NonNull LightState getLightState(@NonNull Light light) { + Preconditions.checkNotNull(light); + try { + return mService.getLightState(light.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Creates a new LightsSession that can be used to control the device lights. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @Override + public @NonNull LightsSession openSession() { + try { + final LightsSession session = new SystemLightsSession(); + mService.openSession(session.getToken()); + return session; + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Encapsulates a session that can be used to control device lights and represents the lifetime + * of the requests. + */ + public final class SystemLightsSession extends LightsManager.LightsSession + implements AutoCloseable { + + private final CloseGuard mCloseGuard = new CloseGuard(); + private boolean mClosed = false; + + /** + * Instantiated by {@link LightsManager#openSession()}. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + private SystemLightsSession() { + mCloseGuard.open("close"); + } + + /** + * Sends a request to modify the states of multiple lights. + * + * <p>This method only controls lights that aren't overridden by higher-priority sessions. + * Additionally, lights not controlled by this session can be controlled by lower-priority + * sessions. + * + * @param request the settings for lights that should change + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @Override + public void requestLights(@NonNull LightsRequest request) { + Preconditions.checkNotNull(request); + if (!mClosed) { + try { + mService.setLightStates(getToken(), request.mLightIds, request.mLightStates); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + } + + /** + * Closes the session, reverting all changes made through it. + */ + @RequiresPermission(Manifest.permission.CONTROL_DEVICE_LIGHTS) + @Override + public void close() { + if (!mClosed) { + try { + mService.closeSession(getToken()); + mClosed = true; + mCloseGuard.close(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + Reference.reachabilityFence(this); + } + + /** @hide */ + @Override + protected void finalize() throws Throwable { + try { + mCloseGuard.warnIfOpen(); + close(); + } finally { + super.finalize(); + } + } + } +} diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java index 6353a25e745f..664120698971 100644 --- a/core/java/android/net/NetworkPolicyManager.java +++ b/core/java/android/net/NetworkPolicyManager.java @@ -16,14 +16,17 @@ package android.net; +import static android.app.ActivityManager.procStateToString; import static android.content.pm.PackageManager.GET_SIGNATURES; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.TestApi; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessCapability; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.Intent; @@ -617,8 +620,18 @@ public class NetworkPolicyManager { * to access network when the device is idle or in battery saver mode. Otherwise, false. * @hide */ - public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(int procState) { - return procState <= FOREGROUND_THRESHOLD_STATE; + public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode(@Nullable UidState uidState) { + if (uidState == null) { + return false; + } + return isProcStateAllowedWhileIdleOrPowerSaveMode(uidState.procState, uidState.capability); + } + + /** @hide */ + public static boolean isProcStateAllowedWhileIdleOrPowerSaveMode( + int procState, @ProcessCapability int capability) { + return procState <= FOREGROUND_THRESHOLD_STATE + || (capability & ActivityManager.PROCESS_CAPABILITY_NETWORK) != 0; } /** @@ -626,11 +639,44 @@ public class NetworkPolicyManager { * to access network when the device is in data saver mode. Otherwise, false. * @hide */ + public static boolean isProcStateAllowedWhileOnRestrictBackground(@Nullable UidState uidState) { + if (uidState == null) { + return false; + } + return isProcStateAllowedWhileOnRestrictBackground(uidState.procState); + } + + /** @hide */ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) { + // Data saver and bg policy restrictions will only take procstate into account. return procState <= FOREGROUND_THRESHOLD_STATE; } /** @hide */ + public static final class UidState { + public int uid; + public int procState; + public int capability; + + public UidState(int uid, int procState, int capability) { + this.uid = uid; + this.procState = procState; + this.capability = capability; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder(); + sb.append("{procState="); + sb.append(procStateToString(procState)); + sb.append(",cap="); + ActivityManager.printCapabilitiesSummary(sb, capability); + sb.append("}"); + return sb.toString(); + } + } + + /** @hide */ @TestApi @NonNull public static String resolveNetworkId(@NonNull WifiConfiguration config) { diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java index 59e493191711..fafb885c58e0 100644 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -26,6 +26,7 @@ import android.hardware.Battery; import android.hardware.SensorManager; import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; +import android.hardware.lights.LightsManager; import android.os.Build; import android.os.NullVibrator; import android.os.Parcel; @@ -89,6 +90,9 @@ public final class InputDevice implements Parcelable { @GuardedBy("mMotionRanges") private Battery mBattery; + @GuardedBy("mMotionRanges") + private LightsManager mLightsManager; + /** * A mask for input source classes. * @@ -859,6 +863,21 @@ public final class InputDevice implements Parcelable { } /** + * Gets the lights manager associated with the device, if there is one. + * Even if the device does not have lights, the result is never null. + * Use {@link LightsManager#getLights} to determine whether any lights is + * present. + * + * @return The lights manager associated with the device, never null. + */ + public @NonNull LightsManager getLightsManager() { + if (mLightsManager == null) { + mLightsManager = InputManager.getInstance().getInputDeviceLightsManager(mId); + } + return mLightsManager; + } + + /** * Gets the sensor manager service associated with the input device. * Even if the device does not have a sensor, the result is never null. * Use {@link SensorManager#getSensorList} to get a full list of all supported sensors. diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index c46fbc7fb4c4..3789324a038c 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -6996,6 +6996,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #getScrollIndicators() * @attr ref android.R.styleable#View_scrollIndicators */ + @RemotableViewMethod public void setScrollIndicators(@ScrollIndicators int indicators) { setScrollIndicators(indicators, SCROLL_INDICATORS_PFLAG3_MASK >>> SCROLL_INDICATORS_TO_PFLAGS3_LSHIFT); @@ -11881,6 +11882,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setFocusable(int) * @attr ref android.R.styleable#View_focusable */ + @RemotableViewMethod public void setFocusable(boolean focusable) { setFocusable(focusable ? FOCUSABLE : NOT_FOCUSABLE); } @@ -11899,6 +11901,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setFocusableInTouchMode(boolean) * @attr ref android.R.styleable#View_focusable */ + @RemotableViewMethod public void setFocusable(@Focusable int focusable) { if ((focusable & (FOCUSABLE_AUTO | FOCUSABLE)) == 0) { setFlags(0, FOCUSABLE_IN_TOUCH_MODE); @@ -11917,6 +11920,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #setFocusable(boolean) * @attr ref android.R.styleable#View_focusableInTouchMode */ + @RemotableViewMethod public void setFocusableInTouchMode(boolean focusableInTouchMode) { // Focusable in touch mode should always be set before the focusable flag // otherwise, setting the focusable flag will trigger a focusableViewAvailable() @@ -12871,6 +12875,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_focusedByDefault */ + @RemotableViewMethod public void setFocusedByDefault(boolean isFocusedByDefault) { if (isFocusedByDefault == ((mPrivateFlags3 & PFLAG3_FOCUSED_BY_DEFAULT) != 0)) { return; @@ -16850,6 +16855,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_rotation */ + @RemotableViewMethod public void setRotation(float rotation) { if (rotation != getRotation()) { // Double-invalidation is necessary to capture view's old and new areas @@ -16896,6 +16902,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_rotationY */ + @RemotableViewMethod public void setRotationY(float rotationY) { if (rotationY != getRotationY()) { invalidateViewProperty(true, false); @@ -16941,6 +16948,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_rotationX */ + @RemotableViewMethod public void setRotationX(float rotationX) { if (rotationX != getRotationX()) { invalidateViewProperty(true, false); @@ -16978,6 +16986,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_scaleX */ + @RemotableViewMethod public void setScaleX(float scaleX) { if (scaleX != getScaleX()) { scaleX = sanitizeFloatPropertyValue(scaleX, "scaleX"); @@ -17016,6 +17025,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_scaleY */ + @RemotableViewMethod public void setScaleY(float scaleY) { if (scaleY != getScaleY()) { scaleY = sanitizeFloatPropertyValue(scaleY, "scaleY"); @@ -17061,6 +17071,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_transformPivotX */ + @RemotableViewMethod public void setPivotX(float pivotX) { if (!mRenderNode.isPivotExplicitlySet() || pivotX != getPivotX()) { invalidateViewProperty(true, false); @@ -17103,6 +17114,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_transformPivotY */ + @RemotableViewMethod public void setPivotY(float pivotY) { if (!mRenderNode.isPivotExplicitlySet() || pivotY != getPivotY()) { invalidateViewProperty(true, false); @@ -17243,6 +17255,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_alpha */ + @RemotableViewMethod public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) { ensureTransformationInfo(); if (mTransformationInfo.mAlpha != alpha) { @@ -17732,6 +17745,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_elevation */ + @RemotableViewMethod public void setElevation(float elevation) { if (elevation != getElevation()) { elevation = sanitizeFloatPropertyValue(elevation, "elevation"); @@ -17766,6 +17780,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_translationX */ + @RemotableViewMethod public void setTranslationX(float translationX) { if (translationX != getTranslationX()) { invalidateViewProperty(true, false); @@ -17801,6 +17816,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_translationY */ + @RemotableViewMethod public void setTranslationY(float translationY) { if (translationY != getTranslationY()) { invalidateViewProperty(true, false); @@ -17828,6 +17844,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * * @attr ref android.R.styleable#View_translationZ */ + @RemotableViewMethod public void setTranslationZ(float translationZ) { if (translationZ != getTranslationZ()) { translationZ = sanitizeFloatPropertyValue(translationZ, "translationZ"); @@ -23989,6 +24006,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #getBackgroundTintList() * @see Drawable#setTintList(ColorStateList) */ + @RemotableViewMethod public void setBackgroundTintList(@Nullable ColorStateList tint) { if (mBackgroundTint == null) { mBackgroundTint = new TintInfo(); @@ -24248,6 +24266,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * @see #getForegroundTintList() * @see Drawable#setTintList(ColorStateList) */ + @RemotableViewMethod public void setForegroundTintList(@Nullable ColorStateList tint) { if (mForegroundInfo == null) { mForegroundInfo = new ForegroundInfo(); diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index f1f6786aa43e..144691d3eaa0 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -1677,7 +1677,8 @@ public final class ViewRootImpl implements ViewParent, // See comment for View.sForceLayoutWhenInsetsChanged if (View.sForceLayoutWhenInsetsChanged && mView != null - && mWindowAttributes.softInputMode == SOFT_INPUT_ADJUST_RESIZE) { + && (mWindowAttributes.softInputMode & SOFT_INPUT_MASK_ADJUST) + == SOFT_INPUT_ADJUST_RESIZE) { forceLayout(mView); } diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 0a08ccd34f02..8aa557bab4e3 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -648,6 +648,7 @@ public class ImageView extends View { * @see #getImageTintList() * @see Drawable#setTintList(ColorStateList) */ + @android.view.RemotableViewMethod public void setImageTintList(@Nullable ColorStateList tint) { mDrawableTintList = tint; mHasDrawableTint = true; diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index a44808eb3120..1b76ebf7c8c6 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -1308,6 +1308,7 @@ public class ProgressBar extends View { * @see #getSecondaryProgressTintList() * @see Drawable#setTintList(ColorStateList) */ + @RemotableViewMethod public void setSecondaryProgressTintList(@Nullable ColorStateList tint) { if (mProgressTintInfo == null) { mProgressTintInfo = new ProgressTintInfo(); @@ -1619,6 +1620,7 @@ public class ProgressBar extends View { * @param stateDescription The state description. */ @Override + @RemotableViewMethod public void setStateDescription(@Nullable CharSequence stateDescription) { mCustomStateDescription = stateDescription; if (stateDescription == null) { diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 30bf546eb83e..514471783c6a 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -224,35 +224,17 @@ public class RemoteViews implements Parcelable, Filter { }) @Retention(RetentionPolicy.SOURCE) public @interface MarginType {} - /** - * The value will apply to the marginLeft. - * @hide - */ + /** The value will apply to the marginLeft. */ public static final int MARGIN_LEFT = 0; - /** - * The value will apply to the marginTop. - * @hide - */ + /** The value will apply to the marginTop. */ public static final int MARGIN_TOP = 1; - /** - * The value will apply to the marginRight. - * @hide - */ + /** The value will apply to the marginRight. */ public static final int MARGIN_RIGHT = 2; - /** - * The value will apply to the marginBottom. - * @hide - */ + /** The value will apply to the marginBottom. */ public static final int MARGIN_BOTTOM = 3; - /** - * The value will apply to the marginStart. - * @hide - */ + /** The value will apply to the marginStart. */ public static final int MARGIN_START = 4; - /** - * The value will apply to the marginEnd. - * @hide - */ + /** The value will apply to the marginEnd. */ public static final int MARGIN_END = 5; /** @hide **/ @@ -4002,7 +3984,6 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view to change * @param type The margin being set e.g. {@link #MARGIN_END} * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin. - * @hide */ public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type, @DimenRes int dimen) { @@ -4021,7 +4002,6 @@ public class RemoteViews implements Parcelable, Filter { * @param type The margin being set e.g. {@link #MARGIN_END} * @param value a value for the margin the given units. * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} - * @hide */ public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value, @ComplexDimensionUnit int units) { @@ -4039,7 +4019,6 @@ public class RemoteViews implements Parcelable, Filter { * * @param width Width of the view in the given units * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} - * @hide */ public void setViewLayoutWidth(@IdRes int viewId, float width, @ComplexDimensionUnit int units) { @@ -4051,7 +4030,6 @@ public class RemoteViews implements Parcelable, Filter { * the result of {@link Resources#getDimensionPixelSize(int)}. * * @param widthDimen the dimension resource for the view's width - * @hide */ public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) { addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen)); @@ -4068,7 +4046,6 @@ public class RemoteViews implements Parcelable, Filter { * * @param height height of the view in the given units * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} - * @hide */ public void setViewLayoutHeight(@IdRes int viewId, float height, @ComplexDimensionUnit int units) { @@ -4080,7 +4057,6 @@ public class RemoteViews implements Parcelable, Filter { * the result of {@link Resources#getDimensionPixelSize(int)}. * * @param heightDimen a dimen resource to read the height from. - * @hide */ public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) { addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen)); @@ -4231,10 +4207,9 @@ public class RemoteViews implements Parcelable, Filter { * @param viewId The id of the view on which to call the method. * @param methodName The name of the method to call. * @param value The value to pass to the method. - * - * @hide */ - public void setColorStateList(@IdRes int viewId, String methodName, ColorStateList value) { + public void setColorStateList(@IdRes int viewId, @NonNull String methodName, + @Nullable ColorStateList value) { addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.COLOR_STATE_LIST, value)); } diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0f2089a5463f..ca0747fadf14 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -4756,6 +4756,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see #getJustificationMode() */ @Layout.JustificationMode + @android.view.RemotableViewMethod public void setJustificationMode(@Layout.JustificationMode int justificationMode) { mJustificationMode = justificationMode; if (mLayout != null) { @@ -5232,6 +5233,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see android.view.Gravity * @attr ref android.R.styleable#TextView_gravity */ + @android.view.RemotableViewMethod public void setGravity(int gravity) { if ((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { gravity |= Gravity.START; @@ -5826,6 +5828,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * * @attr ref android.R.styleable#TextView_lineHeight */ + @android.view.RemotableViewMethod public void setLineHeight(@Px @IntRange(from = 0) int lineHeight) { Preconditions.checkArgumentNonnegative(lineHeight); @@ -10277,6 +10280,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * @see #setTransformationMethod(TransformationMethod) * @attr ref android.R.styleable#TextView_textAllCaps */ + @android.view.RemotableViewMethod public void setAllCaps(boolean allCaps) { if (allCaps) { setTransformationMethod(new AllCapsTransformationMethod(getContext())); diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 56b25b2060ea..93ba0372df08 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -180,6 +180,7 @@ public class TransitionAnimation { "android", com.android.internal.R.anim.cross_profile_apps_thumbnail_enter); } + /** Load animation by resource Id from specific LayoutParams. */ @Nullable private Animation loadAnimationRes(LayoutParams lp, int resId) { Context context = mContext; @@ -193,6 +194,7 @@ public class TransitionAnimation { return null; } + /** Load animation by resource Id from specific package. */ @Nullable private Animation loadAnimationRes(String packageName, int resId) { if (ResourceId.isValid(resId)) { @@ -204,6 +206,13 @@ public class TransitionAnimation { return null; } + /** Load animation by resource Id from android package. */ + @Nullable + public Animation loadDefaultAnimationRes(int resId) { + return loadAnimationRes("android", resId); + } + + /** Load animation by attribute Id from specific LayoutParams */ @Nullable public Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) { int resId = Resources.ID_NULL; @@ -222,6 +231,25 @@ public class TransitionAnimation { return null; } + /** Load animation by attribute Id from android package. */ + @Nullable + public Animation loadDefaultAnimationAttr(int animAttr) { + int resId = Resources.ID_NULL; + Context context = mContext; + if (animAttr >= 0) { + AttributeCache.Entry ent = getCachedAnimations("android", + mDefaultWindowAnimationStyleResId); + if (ent != null) { + context = ent.context; + resId = ent.array.getResourceId(animAttr, 0); + } + } + if (ResourceId.isValid(resId)) { + return loadAnimationSafely(context, resId, mTag); + } + return null; + } + @Nullable private AttributeCache.Entry getCachedAnimations(LayoutParams lp) { if (mDebug) { diff --git a/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java new file mode 100644 index 000000000000..412b36713fa2 --- /dev/null +++ b/core/tests/coretests/src/android/hardware/input/InputDeviceLightsManagerTest.java @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.input; + +import static android.hardware.lights.LightsRequest.Builder; + +import static com.google.common.truth.Truth.assertThat; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNotNull; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.hardware.lights.Light; +import android.hardware.lights.LightState; +import android.hardware.lights.LightsManager; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; +import android.util.ArrayMap; +import android.view.InputDevice; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoJUnitRunner; +import org.mockito.junit.MockitoRule; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Tests for {@link InputDeviceLightsManager}. + * + * Build/Install/Run: + * atest FrameworksCoreTests:InputDeviceLightsManagerTest + */ +@Presubmit +@RunWith(MockitoJUnitRunner.class) +public class InputDeviceLightsManagerTest { + private static final String TAG = "InputDeviceLightsManagerTest"; + + private static final int DEVICE_ID = 1000; + private static final int PLAYER_ID = 3; + + @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + + private InputManager mInputManager; + + @Mock private IInputManager mIInputManagerMock; + + @Before + public void setUp() throws Exception { + when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); + + when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn( + createInputDevice(DEVICE_ID)); + + mInputManager = InputManager.resetInstance(mIInputManagerMock); + + ArrayMap<Integer, LightState> lightStatesById = new ArrayMap<>(); + doAnswer(invocation -> { + final int[] lightIds = (int[]) invocation.getArguments()[1]; + final LightState[] lightStates = + (LightState[]) invocation.getArguments()[2]; + for (int i = 0; i < lightIds.length; i++) { + lightStatesById.put(lightIds[i], lightStates[i]); + } + return null; + }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID), + any(int[].class), any(LightState[].class), any(IBinder.class)); + + doAnswer(invocation -> { + int lightId = (int) invocation.getArguments()[1]; + if (lightStatesById.containsKey(lightId)) { + return lightStatesById.get(lightId); + } + return new LightState(0); + }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt()); + } + + @After + public void tearDown() { + InputManager.clearInstance(); + } + + private InputDevice createInputDevice(int id) { + return new InputDevice(id, 0 /* generation */, 0 /* controllerNumber */, "name", + 0 /* vendorId */, 0 /* productId */, "descriptor", true /* isExternal */, + 0 /* sources */, 0 /* keyboardType */, null /* keyCharacterMap */, + false /* hasVibrator */, false /* hasMicrophone */, false /* hasButtonUnderpad */, + false /* hasSensor */, false /* hasBattery */); + } + + private void mockLights(Light[] lights) throws Exception { + // Mock the Lights returned form InputManagerService + when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn( + new ArrayList(Arrays.asList(lights))); + } + + @Test + public void testGetInputDeviceLights() throws Exception { + InputDevice device = mInputManager.getInputDevice(DEVICE_ID); + assertNotNull(device); + + Light[] mockedLights = { + new Light(1 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_SINGLE), + new Light(2 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB), + new Light(3 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_PLAYER_ID) + }; + mockLights(mockedLights); + + LightsManager lightsManager = device.getLightsManager(); + List<Light> lights = lightsManager.getLights(); + verify(mIInputManagerMock).getLights(eq(DEVICE_ID)); + assertEquals(lights, Arrays.asList(mockedLights)); + } + + @Test + public void testControlMultipleLights() throws Exception { + InputDevice device = mInputManager.getInputDevice(DEVICE_ID); + assertNotNull(device); + + Light[] mockedLights = { + new Light(1 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB), + new Light(2 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB), + new Light(3 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB), + new Light(4 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB) + }; + mockLights(mockedLights); + + LightsManager lightsManager = device.getLightsManager(); + List<Light> lightList = lightsManager.getLights(); + LightState[] states = new LightState[]{new LightState(0xf1), new LightState(0xf2), + new LightState(0xf3)}; + // Open a session to request turn 3/4 lights on: + LightsManager.LightsSession session = lightsManager.openSession(); + session.requestLights(new Builder() + .addLight(lightsManager.getLights().get(0), states[0]) + .addLight(lightsManager.getLights().get(1), states[1]) + .addLight(lightsManager.getLights().get(2), states[2]) + .build()); + IBinder token = session.getToken(); + + verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID), + any(String.class), eq(token)); + verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}), + eq(states), eq(token)); + + // Then all 3 should turn on. + assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor()) + .isEqualTo(0xf1); + assertThat(lightsManager.getLightState(lightsManager.getLights().get(1)).getColor()) + .isEqualTo(0xf2); + assertThat(lightsManager.getLightState(lightsManager.getLights().get(2)).getColor()) + .isEqualTo(0xf3); + + // And the 4th should remain off. + assertThat(lightsManager.getLightState(lightsManager.getLights().get(3)).getColor()) + .isEqualTo(0x00); + + // close session + session.close(); + verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token)); + } + + @Test + public void testControlPlayerIdLight() throws Exception { + InputDevice device = mInputManager.getInputDevice(DEVICE_ID); + assertNotNull(device); + + Light[] mockedLights = { + new Light(1 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_PLAYER_ID), + new Light(2 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_SINGLE), + new Light(3 /* id */, 0 /* ordinal */, Light.LIGHT_TYPE_INPUT_RGB), + }; + mockLights(mockedLights); + + LightsManager lightsManager = device.getLightsManager(); + List<Light> lightList = lightsManager.getLights(); + LightState[] states = new LightState[]{new LightState(0xf1, PLAYER_ID)}; + // Open a session to request set Player ID light: + LightsManager.LightsSession session = lightsManager.openSession(); + session.requestLights(new Builder() + .addLight(lightsManager.getLights().get(0), states[0]) + .build()); + IBinder token = session.getToken(); + + verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID), + any(String.class), eq(token)); + verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}), + eq(states), eq(token)); + + // Verify the light state + assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getColor()) + .isEqualTo(0xf1); + assertThat(lightsManager.getLightState(lightsManager.getLights().get(0)).getPlayerId()) + .isEqualTo(PLAYER_ID); + + // close session + session.close(); + verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token)); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 0ee1f0642352..8697be9db3fd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -124,6 +124,7 @@ public class Bubble implements BubbleViewProvider { private int mDesiredHeight; @DimenRes private int mDesiredHeightResId; + private int mTaskId; /** for logging **/ @Nullable @@ -162,7 +163,7 @@ public class Bubble implements BubbleViewProvider { */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, final int desiredHeight, final int desiredHeightResId, @Nullable final String title, - Executor mainExecutor) { + int taskId, Executor mainExecutor) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mMetadataShortcutId = shortcutInfo.getId(); @@ -178,6 +179,7 @@ public class Bubble implements BubbleViewProvider { mTitle = title; mShowBubbleUpdateDot = false; mMainExecutor = mainExecutor; + mTaskId = taskId; } @VisibleForTesting(visibility = PRIVATE) @@ -197,6 +199,7 @@ public class Bubble implements BubbleViewProvider { }); }; mMainExecutor = mainExecutor; + mTaskId = INVALID_TASK_ID; setEntry(entry); } @@ -520,7 +523,7 @@ public class Bubble implements BubbleViewProvider { */ @Override public int getTaskId() { - return mExpandedView != null ? mExpandedView.getTaskId() : INVALID_TASK_ID; + return mExpandedView != null ? mExpandedView.getTaskId() : mTaskId; } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt index 3108b02cc010..241755227af0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt @@ -28,7 +28,6 @@ import com.android.wm.shell.bubbles.storage.BubbleEntity import com.android.wm.shell.bubbles.storage.BubblePersistentRepository import com.android.wm.shell.bubbles.storage.BubbleVolatileRepository import com.android.wm.shell.common.ShellExecutor -import com.android.wm.shell.common.annotations.ExternalThread import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -36,8 +35,11 @@ import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.launch import kotlinx.coroutines.yield -internal class BubbleDataRepository(context: Context, private val launcherApps: LauncherApps, - private val mainExecutor : ShellExecutor) { +internal class BubbleDataRepository( + context: Context, + private val launcherApps: LauncherApps, + private val mainExecutor: ShellExecutor +) { private val volatileRepository = BubbleVolatileRepository(launcherApps) private val persistentRepository = BubblePersistentRepository(context) @@ -78,7 +80,8 @@ internal class BubbleDataRepository(context: Context, private val launcherApps: b.key, b.rawDesiredHeight, b.rawDesiredHeightResId, - b.title + b.title, + b.taskId ) } } @@ -168,6 +171,7 @@ internal class BubbleDataRepository(context: Context, private val launcherApps: entity.desiredHeight, entity.desiredHeightResId, entity.title, + entity.taskId, mainExecutor ) } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt index aeba302bf487..d5cab5af42e4 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleEntity.kt @@ -25,5 +25,6 @@ data class BubbleEntity( val key: String, val desiredHeight: Int, @DimenRes val desiredHeightResId: Int, - val title: String? = null + val title: String? = null, + val taskId: Int ) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt index fe72bd301e04..470011b136fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelper.kt @@ -15,6 +15,7 @@ */ package com.android.wm.shell.bubbles.storage +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.util.Xml import com.android.internal.util.FastXmlSerializer import com.android.internal.util.XmlUtils @@ -38,6 +39,7 @@ private const val ATTR_KEY = "key" private const val ATTR_DESIRED_HEIGHT = "h" private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" private const val ATTR_TITLE = "t" +private const val ATTR_TASK_ID = "tid" /** * Writes the bubbles in xml format into given output stream. @@ -70,6 +72,7 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) bubble.title?.let { serializer.attribute(null, ATTR_TITLE, it) } + serializer.attribute(null, ATTR_TASK_ID, bubble.taskId.toString()) serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -103,7 +106,8 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_KEY) ?: return null, parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null, - parser.getAttributeWithName(ATTR_TITLE) + parser.getAttributeWithName(ATTR_TITLE), + parser.getAttributeWithName(ATTR_TASK_ID)?.toInt() ?: INVALID_TASK_ID ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index 1a4616c5f591..6afcc06aa1ae 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -32,6 +32,7 @@ import android.media.session.MediaController; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.UserHandle; import androidx.annotation.Nullable; @@ -76,6 +77,7 @@ public class PipMediaController { private final Context mContext; private final Handler mMainHandler; + private final HandlerExecutor mHandlerExecutor; private final MediaSessionManager mMediaSessionManager; private MediaController mMediaController; @@ -123,6 +125,7 @@ public class PipMediaController { public PipMediaController(Context context, Handler mainHandler) { mContext = context; mMainHandler = mainHandler; + mHandlerExecutor = new HandlerExecutor(mMainHandler); IntentFilter mediaControlFilter = new IntentFilter(); mediaControlFilter.addAction(ACTION_PLAY); mediaControlFilter.addAction(ACTION_PAUSE); @@ -247,8 +250,8 @@ public class PipMediaController { */ public void registerSessionListenerForCurrentUser() { mMediaSessionManager.removeOnActiveSessionsChangedListener(mSessionsChangedListener); - mMediaSessionManager.addOnActiveSessionsChangedListener(mSessionsChangedListener, null, - UserHandle.CURRENT, mMainHandler); + mMediaSessionManager.addOnActiveSessionsChangedListener(null, UserHandle.CURRENT, + mHandlerExecutor, mSessionsChangedListener); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java index 59f8c1df1213..2182ee5590e1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java @@ -16,55 +16,78 @@ package com.android.wm.shell.transition; +import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT; +import static android.window.TransitionInfo.FLAG_TRANSLUCENT; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; +import android.graphics.Rect; import android.os.IBinder; import android.util.ArrayMap; +import android.view.Choreographer; import android.view.SurfaceControl; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.Transformation; import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import com.android.internal.R; +import com.android.internal.policy.AttributeCache; +import com.android.internal.policy.TransitionAnimation; +import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.protolog.ShellProtoLogGroup; import java.util.ArrayList; /** The default handler that handles anything not already handled. */ public class DefaultTransitionHandler implements Transitions.TransitionHandler { + private static final int MAX_ANIMATION_DURATION = 3000; + private final TransactionPool mTransactionPool; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; + private final TransitionAnimation mTransitionAnimation; /** Keeps track of the currently-running animations associated with each transition. */ private final ArrayMap<IBinder, ArrayList<Animator>> mAnimations = new ArrayMap<>(); - DefaultTransitionHandler(@NonNull TransactionPool transactionPool, + private float mTransitionAnimationScaleSetting = 1.0f; + + DefaultTransitionHandler(@NonNull TransactionPool transactionPool, Context context, @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { mTransactionPool = transactionPool; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; + mTransitionAnimation = new TransitionAnimation(context, false /* debug */, Transitions.TAG); + + AttributeCache.init(context); } @Override public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t, @NonNull Transitions.TransitionFinishCallback finishCallback) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "start default transition animation, info = %s", info); if (mAnimations.containsKey(transition)) { throw new IllegalStateException("Got a duplicate startAnimation call for " + transition); } final ArrayList<Animator> animations = new ArrayList<>(); mAnimations.put(transition, animations); - final boolean isOpening = Transitions.isOpeningType(info.getType()); final Runnable onAnimFinish = () -> { if (!animations.isEmpty()) return; @@ -77,19 +100,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { // Don't animate anything with an animating parent if (change.getParent() != null) continue; - final int mode = change.getMode(); - if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) { - if ((change.getFlags() & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { - // This received a transferred starting window, so don't animate - continue; - } - // fade in - startExampleAnimation( - animations, change.getLeash(), true /* show */, onAnimFinish); - } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) { - // fade out - startExampleAnimation( - animations, change.getLeash(), false /* show */, onAnimFinish); + Animation a = loadAnimation(info.getType(), change); + if (a != null) { + startAnimInternal(animations, a, change.getLeash(), onAnimFinish); } } t.apply(); @@ -105,32 +118,93 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { return null; } - // TODO(shell-transitions): real animations - private void startExampleAnimation(@NonNull ArrayList<Animator> animations, - @NonNull SurfaceControl leash, boolean show, @NonNull Runnable finishCallback) { - final float end = show ? 1.f : 0.f; - final float start = 1.f - end; + @Override + public void setAnimScaleSetting(float scale) { + mTransitionAnimationScaleSetting = scale; + } + + @Nullable + private Animation loadAnimation(int type, TransitionInfo.Change change) { + // TODO(b/178678389): It should handle more type animation here + Animation a = null; + + final boolean isOpening = Transitions.isOpeningType(type); + final int mode = change.getMode(); + final int flags = change.getFlags(); + + if (mode == TRANSIT_OPEN && isOpening) { + if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { + // This received a transferred starting window, so don't animate + return null; + } + + if (change.getTaskInfo() != null) { + a = mTransitionAnimation.loadDefaultAnimationAttr( + R.styleable.WindowAnimation_taskOpenEnterAnimation); + } else { + a = mTransitionAnimation.loadDefaultAnimationRes((flags & FLAG_TRANSLUCENT) == 0 + ? R.anim.activity_open_enter : R.anim.activity_translucent_open_enter); + } + } else if (mode == TRANSIT_TO_FRONT && isOpening) { + if ((flags & FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT) != 0) { + // This received a transferred starting window, so don't animate + return null; + } + + a = mTransitionAnimation.loadDefaultAnimationAttr( + R.styleable.WindowAnimation_taskToFrontEnterAnimation); + } else if (mode == TRANSIT_CLOSE && !isOpening) { + if (change.getTaskInfo() != null) { + a = mTransitionAnimation.loadDefaultAnimationAttr( + R.styleable.WindowAnimation_taskCloseExitAnimation); + } else { + a = mTransitionAnimation.loadDefaultAnimationRes((flags & FLAG_TRANSLUCENT) == 0 + ? R.anim.activity_close_exit : R.anim.activity_translucent_close_exit); + } + } else if (mode == TRANSIT_TO_BACK && !isOpening) { + a = mTransitionAnimation.loadDefaultAnimationAttr( + R.styleable.WindowAnimation_taskToBackExitAnimation); + } else if (mode == TRANSIT_CHANGE) { + // In the absence of a specific adapter, we just want to keep everything stationary. + a = new AlphaAnimation(1.f, 1.f); + a.setDuration(TransitionAnimation.DEFAULT_APP_TRANSITION_DURATION); + } + + if (a != null) { + Rect start = change.getStartAbsBounds(); + Rect end = change.getEndAbsBounds(); + a.restrictDuration(MAX_ANIMATION_DURATION); + a.initialize(end.width(), end.height(), start.width(), start.height()); + a.scaleCurrentDuration(mTransitionAnimationScaleSetting); + } + return a; + } + + private void startAnimInternal(@NonNull ArrayList<Animator> animations, @NonNull Animation anim, + @NonNull SurfaceControl leash, @NonNull Runnable finishCallback) { final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); - final ValueAnimator va = ValueAnimator.ofFloat(start, end); - va.setDuration(500); + final ValueAnimator va = ValueAnimator.ofFloat(0f, 1f); + final Transformation transformation = new Transformation(); + final float[] matrix = new float[9]; + // Animation length is already expected to be scaled. + va.overrideDurationScale(1.0f); + va.setDuration(anim.computeDurationHint()); va.addUpdateListener(animation -> { - float fraction = animation.getAnimatedFraction(); - transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction); - transaction.apply(); + final long currentPlayTime = Math.min(va.getDuration(), va.getCurrentPlayTime()); + + applyTransformation(currentPlayTime, transaction, leash, anim, transformation, matrix); }); + final Runnable finisher = () -> { - transaction.setAlpha(leash, end); - transaction.apply(); + applyTransformation(va.getDuration(), transaction, leash, anim, transformation, matrix); + mTransactionPool.release(transaction); mMainExecutor.execute(() -> { animations.remove(va); finishCallback.run(); }); }; - va.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { } - + va.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { finisher.run(); @@ -140,11 +214,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler { public void onAnimationCancel(Animator animation) { finisher.run(); } - - @Override - public void onAnimationRepeat(Animator animation) { } }); animations.add(va); mAnimExecutor.execute(va::start); } + + private static void applyTransformation(long time, SurfaceControl.Transaction t, + SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix) { + anim.getTransformation(time, transformation); + t.setMatrix(leash, transformation.getMatrix(), matrix); + t.setAlpha(leash, transformation.getAlpha()); + t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId()); + t.apply(); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java index 10344892e766..89eee67bf5af 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java @@ -25,9 +25,13 @@ import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPI import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.ContentResolver; +import android.content.Context; +import android.database.ContentObserver; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemProperties; +import android.provider.Settings; import android.util.ArrayMap; import android.util.Log; import android.view.SurfaceControl; @@ -43,6 +47,7 @@ import android.window.WindowOrganizer; import androidx.annotation.BinderThread; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.ShellTaskOrganizer; @@ -63,6 +68,7 @@ public class Transitions { SystemProperties.getBoolean("persist.debug.shell_transit", false); private final WindowOrganizer mOrganizer; + private final Context mContext; private final ShellExecutor mMainExecutor; private final ShellExecutor mAnimExecutor; private final TransitionPlayerImpl mPlayerImpl; @@ -72,6 +78,8 @@ public class Transitions { /** List of possible handlers. Ordered by specificity (eg. tapped back to front). */ private final ArrayList<TransitionHandler> mHandlers = new ArrayList<>(); + private float mTransitionAnimationScaleSetting = 1.0f; + private static final class ActiveTransition { TransitionHandler mFirstHandler = null; } @@ -84,26 +92,46 @@ public class Transitions { } public Transitions(@NonNull WindowOrganizer organizer, @NonNull TransactionPool pool, - @NonNull ShellExecutor mainExecutor, @NonNull ShellExecutor animExecutor) { + @NonNull Context context, @NonNull ShellExecutor mainExecutor, + @NonNull ShellExecutor animExecutor) { mOrganizer = organizer; + mContext = context; mMainExecutor = mainExecutor; mAnimExecutor = animExecutor; mPlayerImpl = new TransitionPlayerImpl(); // The very last handler (0 in the list) should be the default one. - mHandlers.add(new DefaultTransitionHandler(pool, mainExecutor, animExecutor)); + mHandlers.add(new DefaultTransitionHandler(pool, context, mainExecutor, animExecutor)); // Next lowest priority is remote transitions. mRemoteTransitionHandler = new RemoteTransitionHandler(mainExecutor); mHandlers.add(mRemoteTransitionHandler); + + ContentResolver resolver = context.getContentResolver(); + mTransitionAnimationScaleSetting = Settings.Global.getFloat(resolver, + Settings.Global.TRANSITION_ANIMATION_SCALE, + context.getResources().getFloat( + R.dimen.config_appTransitionAnimationDurationScaleDefault)); + dispatchAnimScaleSetting(mTransitionAnimationScaleSetting); + + resolver.registerContentObserver( + Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE), false, + new SettingsObserver()); } private Transitions() { mOrganizer = null; + mContext = null; mMainExecutor = null; mAnimExecutor = null; mPlayerImpl = null; mRemoteTransitionHandler = null; } + private void dispatchAnimScaleSetting(float scale) { + for (int i = mHandlers.size() - 1; i >= 0; --i) { + mHandlers.get(i).setAnimScaleSetting(scale); + } + } + /** Create an empty/non-registering transitions object for system-ui tests. */ @VisibleForTesting public static RemoteTransitions createEmptyForTesting() { @@ -368,6 +396,13 @@ public class Transitions { @Nullable WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request); + + /** + * Sets transition animation scale settings value to handler. + * + * @param scale The setting value of transition animation scale. + */ + default void setAnimScaleSetting(float scale) {} } @BinderThread @@ -404,4 +439,21 @@ public class Transitions { }); } } + + private class SettingsObserver extends ContentObserver { + + SettingsObserver() { + super(null); + } + + @Override + public void onChange(boolean selfChange) { + super.onChange(selfChange); + mTransitionAnimationScaleSetting = Settings.Global.getFloat( + mContext.getContentResolver(), Settings.Global.TRANSITION_ANIMATION_SCALE, + mTransitionAnimationScaleSetting); + + mMainExecutor.execute(() -> dispatchAnimScaleSetting(mTransitionAnimationScaleSetting)); + } + } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt index 416028088294..bdf75fcd8816 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles.storage +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase @@ -31,9 +32,10 @@ import org.junit.runner.RunWith class BubblePersistentRepositoryTest : ShellTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title"), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) + BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0, null, 1), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428, "title", 2), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0, null, + INVALID_TASK_ID) ) private lateinit var repository: BubblePersistentRepository diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt index dd1a6a5a281e..05795fde7d6c 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles.storage +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.content.pm.LauncherApps import android.os.UserHandle import android.testing.AndroidTestingRunner @@ -37,10 +38,12 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0) + private val bubble1 = BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0, + null, 1) private val bubble2 = BubbleEntity(10, "com.example.chat", "alice and bob", - "key-2", 0, 16537428, "title") - private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) + "key-2", 0, 16537428, "title", 2) + private val bubble3 = BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0, + null, INVALID_TASK_ID) private val bubbles = listOf(bubble1, bubble2, bubble3) @@ -105,13 +108,13 @@ class BubbleVolatileRepositoryTest : ShellTestCase() { @Test fun testAddBubbleMatchesByKey() { - val bubble = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, "title") + val bubble = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, "title", 1) repository.addBubbles(listOf(bubble)) assertEquals(bubble, repository.bubbles.get(0)) // Same key as first bubble but different entry val bubbleModified = BubbleEntity(0, "com.example.pkg", "shortcut-id", "key", 120, 0, - "different title") + "different title", 2) repository.addBubbles(listOf(bubbleModified)) assertEquals(bubbleModified, repository.bubbles.get(0)) } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt index e0891a95c6a6..839b873d0c23 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubbleXmlHelperTest.kt @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles.storage +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.wm.shell.ShellTestCase @@ -31,17 +32,18 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : ShellTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title"), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, 1), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", 2), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, + INVALID_TASK_ID) ) @Test fun testWriteXml() { val expectedEntries = """ -<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" tid="1" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" tid="2" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" tid="-1" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -56,9 +58,9 @@ class BubbleXmlHelperTest : ShellTestCase() { val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <bs v="1"> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> -<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> -<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" tid="1" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" tid="2" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" tid="-1" /> </bs> """.trimIndent() val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) @@ -79,4 +81,32 @@ class BubbleXmlHelperTest : ShellTestCase() { val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) assertEquals("failed parsing bubbles from xml\n$src", emptyList<BubbleEntity>(), actual) } + + /** + * In S we changed the XML to include a taskId, version didn't increase because we can set a + * reasonable default for taskId (INVALID_TASK_ID) if it wasn't in the XML previously, this + * tests that that works. + */ + @Test + fun testReadXMLWithoutTaskId() { + val expectedBubbles = listOf( + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0, null, + INVALID_TASK_ID), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428, "title", + INVALID_TASK_ID), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0, null, + INVALID_TASK_ID) + ) + + val src = """ +<?xml version='1.0' encoding='utf-8' standalone='yes' ?> +<bs v="1"> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" h="120" hid="0" /> +<bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" h="0" hid="16537428" t="title" /> +<bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> +</bs> + """.trimIndent() + val actual = readXml(ByteArrayInputStream(src.toByteArray(Charsets.UTF_8))) + assertEquals("failed parsing bubbles from xml\n$src", expectedBubbles, actual) + } }
\ No newline at end of file diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 5eca3e75a7b8..926108c41e5e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -41,6 +41,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.app.ActivityManager.RunningTaskInfo; +import android.content.Context; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; @@ -58,6 +59,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; import com.android.wm.shell.TestShellExecutor; import com.android.wm.shell.common.ShellExecutor; @@ -78,6 +80,8 @@ public class ShellTransitionTests { private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class); private final TransactionPool mTransactionPool = mock(TransactionPool.class); + private final Context mContext = + InstrumentationRegistry.getInstrumentation().getTargetContext(); private final TestShellExecutor mMainExecutor = new TestShellExecutor(); private final ShellExecutor mAnimExecutor = new TestShellExecutor(); private final TestTransitionHandler mDefaultHandler = new TestTransitionHandler(); @@ -90,8 +94,8 @@ public class ShellTransitionTests { @Test public void testBasicTransitionFlow() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor, - mAnimExecutor); + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); transitions.replaceDefaultHandlerForTest(mDefaultHandler); IBinder transitToken = new Binder(); @@ -109,8 +113,8 @@ public class ShellTransitionTests { @Test public void testNonDefaultHandler() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor, - mAnimExecutor); + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final WindowContainerTransaction handlerWCT = new WindowContainerTransaction(); @@ -188,8 +192,8 @@ public class ShellTransitionTests { @Test public void testRequestRemoteTransition() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor, - mAnimExecutor); + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; @@ -255,8 +259,8 @@ public class ShellTransitionTests { @Test public void testRegisteredRemoteTransition() { - Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mMainExecutor, - mAnimExecutor); + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); transitions.replaceDefaultHandlerForTest(mDefaultHandler); final boolean[] remoteCalled = new boolean[]{false}; diff --git a/media/java/android/media/session/MediaSessionManager.java b/media/java/android/media/session/MediaSessionManager.java index 8de5e42e93b2..aa0f7fdd70d5 100644 --- a/media/java/android/media/session/MediaSessionManager.java +++ b/media/java/android/media/session/MediaSessionManager.java @@ -34,6 +34,7 @@ import android.media.Session2Token; import android.media.VolumeProvider; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerExecutor; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; @@ -321,7 +322,7 @@ public final class MediaSessionManager { @NonNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, @Nullable Handler handler) { addOnActiveSessionsChangedListener(sessionListener, notificationListener, - UserHandle.myUserId(), handler); + UserHandle.myUserId(), handler == null ? null : new HandlerExecutor(handler)); } /** @@ -337,38 +338,40 @@ public final class MediaSessionManager { * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} permission in order to * add listeners for user ids that do not belong to current process. * - * @param sessionListener The listener to add. * @param notificationListener The enabled notification listener component. May be null. * @param userHandle The user handle to listen for changes on. - * @param handler The handler to post updates on. + * @param executor The executor on which the listener should be invoked + * @param sessionListener The listener to add. * @hide */ - @SuppressLint({"ExecutorRegistration", "SamShouldBeLast", "UserHandle"}) + @SuppressLint("UserHandle") @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) public void addOnActiveSessionsChangedListener( - @NonNull OnActiveSessionsChangedListener sessionListener, - @Nullable ComponentName notificationListener, @NonNull UserHandle userHandle, - @Nullable Handler handler) { + @Nullable ComponentName notificationListener, + @NonNull UserHandle userHandle, @NonNull Executor executor, + @NonNull OnActiveSessionsChangedListener sessionListener) { Objects.requireNonNull(userHandle, "userHandle shouldn't be null"); + Objects.requireNonNull(executor, "executor shouldn't be null"); addOnActiveSessionsChangedListener(sessionListener, notificationListener, - userHandle.getIdentifier(), handler); + userHandle.getIdentifier(), executor); } private void addOnActiveSessionsChangedListener( @NonNull OnActiveSessionsChangedListener sessionListener, @Nullable ComponentName notificationListener, int userId, - @Nullable Handler handler) { + @Nullable Executor executor) { Objects.requireNonNull(sessionListener, "sessionListener shouldn't be null"); - if (handler == null) { - handler = new Handler(); + if (executor == null) { + executor = new HandlerExecutor(new Handler()); } + synchronized (mLock) { if (mListeners.get(sessionListener) != null) { Log.w(TAG, "Attempted to add session listener twice, ignoring."); return; } SessionsChangedWrapper wrapper = new SessionsChangedWrapper(mContext, sessionListener, - handler); + executor); try { mService.addSessionsListener(wrapper.mStub, notificationListener, userId); mListeners.put(sessionListener, wrapper); @@ -412,7 +415,8 @@ public final class MediaSessionManager { */ public void addOnSession2TokensChangedListener( @NonNull OnSession2TokensChangedListener listener) { - addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, new Handler()); + addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, + new HandlerExecutor(new Handler())); } /** @@ -428,7 +432,9 @@ public final class MediaSessionManager { */ public void addOnSession2TokensChangedListener( @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) { - addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, handler); + Objects.requireNonNull(handler, "handler shouldn't be null"); + addOnSession2TokensChangedListener(UserHandle.myUserId(), listener, + new HandlerExecutor(handler)); } /** @@ -445,20 +451,19 @@ public final class MediaSessionManager { * * @param userHandle The userHandle to listen for changes on * @param listener The listener to add - * @param handler The handler to call listener on. If {@code null}, calling thread's looper will - * be used. + * @param executor The executor on which the listener should be invoked * @hide */ @SuppressLint("UserHandle") public void addOnSession2TokensChangedListener(@NonNull UserHandle userHandle, - @NonNull OnSession2TokensChangedListener listener, @NonNull Handler handler) { + @NonNull OnSession2TokensChangedListener listener, @NonNull Executor executor) { Objects.requireNonNull(userHandle, "userHandle shouldn't be null"); - addOnSession2TokensChangedListener(userHandle.getIdentifier(), listener, handler); + Objects.requireNonNull(executor, "executor shouldn't be null"); + addOnSession2TokensChangedListener(userHandle.getIdentifier(), listener, executor); } private void addOnSession2TokensChangedListener(int userId, - OnSession2TokensChangedListener listener, Handler handler) { - Objects.requireNonNull(handler, "handler shouldn't be null"); + OnSession2TokensChangedListener listener, Executor executor) { Objects.requireNonNull(listener, "listener shouldn't be null"); synchronized (mLock) { if (mSession2TokensListeners.get(listener) != null) { @@ -466,7 +471,7 @@ public final class MediaSessionManager { return; } Session2TokensChangedWrapper wrapper = - new Session2TokensChangedWrapper(listener, handler); + new Session2TokensChangedWrapper(listener, executor); try { mService.addSession2TokensListener(wrapper.getStub(), userId); mSession2TokensListeners.put(listener, wrapper); @@ -847,7 +852,7 @@ public final class MediaSessionManager { /** * Add a {@link OnMediaKeyEventDispatchedListener}. * - * @param executor The executor on which the callback should be invoked + * @param executor The executor on which the listener should be invoked * @param listener A {@link OnMediaKeyEventDispatchedListener}. * @hide */ @@ -898,7 +903,7 @@ public final class MediaSessionManager { /** * Add a {@link OnMediaKeyEventDispatchedListener}. * - * @param executor The executor on which the callback should be invoked + * @param executor The executor on which the listener should be invoked * @param listener A {@link OnMediaKeyEventSessionChangedListener}. * @hide */ @@ -1257,62 +1262,61 @@ public final class MediaSessionManager { private static final class SessionsChangedWrapper { private Context mContext; private OnActiveSessionsChangedListener mListener; - private Handler mHandler; + private Executor mExecutor; public SessionsChangedWrapper(Context context, OnActiveSessionsChangedListener listener, - Handler handler) { + Executor executor) { mContext = context; mListener = listener; - mHandler = handler; + mExecutor = executor; } private final IActiveSessionsListener.Stub mStub = new IActiveSessionsListener.Stub() { @Override public void onActiveSessionsChanged(final List<MediaSession.Token> tokens) { - final Handler handler = mHandler; - if (handler != null) { - handler.post(new Runnable() { - @Override - public void run() { - final Context context = mContext; - if (context != null) { - ArrayList<MediaController> controllers = new ArrayList<>(); - int size = tokens.size(); - for (int i = 0; i < size; i++) { - controllers.add(new MediaController(context, tokens.get(i))); - } - final OnActiveSessionsChangedListener listener = mListener; - if (listener != null) { - listener.onActiveSessionsChanged(controllers); - } - } - } - }); + if (mExecutor != null) { + final Executor executor = mExecutor; + executor.execute(() -> callOnActiveSessionsChangedListener(tokens)); } } }; + private void callOnActiveSessionsChangedListener(final List<MediaSession.Token> tokens) { + final Context context = mContext; + if (context != null) { + ArrayList<MediaController> controllers = new ArrayList<>(); + int size = tokens.size(); + for (int i = 0; i < size; i++) { + controllers.add(new MediaController(context, tokens.get(i))); + } + final OnActiveSessionsChangedListener listener = mListener; + if (listener != null) { + listener.onActiveSessionsChanged(controllers); + } + } + } + private void release() { mListener = null; mContext = null; - mHandler = null; + mExecutor = null; } } private static final class Session2TokensChangedWrapper { private final OnSession2TokensChangedListener mListener; - private final Handler mHandler; + private final Executor mExecutor; private final ISession2TokensListener.Stub mStub = new ISession2TokensListener.Stub() { @Override public void onSession2TokensChanged(final List<Session2Token> tokens) { - mHandler.post(() -> mListener.onSession2TokensChanged(tokens)); + mExecutor.execute(() -> mListener.onSession2TokensChanged(tokens)); } }; - Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Handler handler) { + Session2TokensChangedWrapper(OnSession2TokensChangedListener listener, Executor executor) { mListener = listener; - mHandler = (handler == null) ? new Handler() : new Handler(handler.getLooper()); + mExecutor = executor; } public ISession2TokensListener.Stub getStub() { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 4c80b91f300d..5e2d21b2e188 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -851,11 +851,12 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> if (BluetoothUuid.containsAnyUuid(uuids, PbapServerProfile.PBAB_CLIENT_UUIDS)) { // The pairing dialog now warns of phone-book access for paired devices. // No separate prompt is displayed after pairing. + final BluetoothClass bluetoothClass = mDevice.getBluetoothClass(); if (mDevice.getPhonebookAccessPermission() == BluetoothDevice.ACCESS_UNKNOWN) { - if (mDevice.getBluetoothClass().getDeviceClass() - == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE || - mDevice.getBluetoothClass().getDeviceClass() - == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET) { + if (bluetoothClass != null && (bluetoothClass.getDeviceClass() + == BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE + || bluetoothClass.getDeviceClass() + == BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET)) { EventLog.writeEvent(0x534e4554, "138529441", -1, ""); } mDevice.setPhonebookAccessPermission(BluetoothDevice.ACCESS_REJECTED); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java index 53ff1a10b6ff..53a99ab8cbe4 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java @@ -27,12 +27,14 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; import android.media.AudioManager; import com.android.settingslib.R; +import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter; import org.junit.Before; import org.junit.Test; @@ -41,8 +43,11 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadow.api.Shadow; @RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowBluetoothAdapter.class}) public class CachedBluetoothDeviceTest { private static final String DEVICE_NAME = "TestName"; private static final String DEVICE_ALIAS = "TestAlias"; @@ -72,12 +77,14 @@ public class CachedBluetoothDeviceTest { private AudioManager mAudioManager; private Context mContext; private int mBatteryLevel = BluetoothDevice.BATTERY_LEVEL_UNKNOWN; + private ShadowBluetoothAdapter mShadowBluetoothAdapter; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext = RuntimeEnvironment.application; mAudioManager = mContext.getSystemService(AudioManager.class); + mShadowBluetoothAdapter = Shadow.extract(BluetoothAdapter.getDefaultAdapter()); when(mDevice.getAddress()).thenReturn(DEVICE_ADDRESS); when(mHfpProfile.isProfileReady()).thenReturn(true); when(mA2dpProfile.isProfileReady()).thenReturn(true); @@ -937,4 +944,17 @@ public class CachedBluetoothDeviceTest { assertThat(mCachedDevice.getConnectionSummary()).isEqualTo( mContext.getString(R.string.profile_connect_timeout_subtext)); } + + @Test + public void onUuidChanged_bluetoothClassIsNull_shouldNotCrash() { + mShadowBluetoothAdapter.setUuids(PbapServerProfile.PBAB_CLIENT_UUIDS); + when(mDevice.getUuids()).thenReturn(PbapServerProfile.PBAB_CLIENT_UUIDS); + when(mDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED); + when(mDevice.getPhonebookAccessPermission()).thenReturn(BluetoothDevice.ACCESS_UNKNOWN); + when(mDevice.getBluetoothClass()).thenReturn(null); + + mCachedDevice.onUuidChanged(); + + // Should not crash + } } diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java index b265d46058be..3b7fbc73522f 100644 --- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java +++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowBluetoothAdapter.java @@ -24,6 +24,7 @@ import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.content.Context; +import android.os.ParcelUuid; import org.robolectric.annotation.Implementation; import org.robolectric.annotation.Implements; @@ -36,6 +37,7 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto private List<Integer> mSupportedProfiles; private List<BluetoothDevice> mMostRecentlyConnectedDevices; private BluetoothProfile.ServiceListener mServiceListener; + private ParcelUuid[] mParcelUuids; @Implementation protected boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener, @@ -87,4 +89,13 @@ public class ShadowBluetoothAdapter extends org.robolectric.shadows.ShadowBlueto } return true; } + + @Implementation + protected ParcelUuid[] getUuids() { + return mParcelUuids; + } + + public void setUuids(ParcelUuid[] uuids) { + mParcelUuids = uuids; + } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java index fba0b0079012..5d9465904e3b 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShellBaseModule.java @@ -380,9 +380,9 @@ public abstract class WMShellBaseModule { @WMSingleton @Provides static Transitions provideTransitions(ShellTaskOrganizer organizer, TransactionPool pool, - @ShellMainThread ShellExecutor mainExecutor, + Context context, @ShellMainThread ShellExecutor mainExecutor, @ShellAnimationThread ShellExecutor animExecutor) { - return new Transitions(organizer, pool, mainExecutor, animExecutor); + return new Transitions(organizer, pool, context, mainExecutor, animExecutor); } // diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 172f7073852a..194736fbf911 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -1054,76 +1054,7 @@ public final class ProcessList { } public static String makeProcStateString(int curProcState) { - String procState; - switch (curProcState) { - case ActivityManager.PROCESS_STATE_PERSISTENT: - procState = "PER "; - break; - case ActivityManager.PROCESS_STATE_PERSISTENT_UI: - procState = "PERU"; - break; - case ActivityManager.PROCESS_STATE_TOP: - procState = "TOP "; - break; - case ActivityManager.PROCESS_STATE_BOUND_TOP: - procState = "BTOP"; - break; - case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE: - procState = "FGS "; - break; - case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE: - procState = "BFGS"; - break; - case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: - procState = "IMPF"; - break; - case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: - procState = "IMPB"; - break; - case ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND: - procState = "TRNB"; - break; - case ActivityManager.PROCESS_STATE_BACKUP: - procState = "BKUP"; - break; - case ActivityManager.PROCESS_STATE_SERVICE: - procState = "SVC "; - break; - case ActivityManager.PROCESS_STATE_RECEIVER: - procState = "RCVR"; - break; - case ActivityManager.PROCESS_STATE_TOP_SLEEPING: - procState = "TPSL"; - break; - case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: - procState = "HVY "; - break; - case ActivityManager.PROCESS_STATE_HOME: - procState = "HOME"; - break; - case ActivityManager.PROCESS_STATE_LAST_ACTIVITY: - procState = "LAST"; - break; - case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: - procState = "CAC "; - break; - case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: - procState = "CACC"; - break; - case ActivityManager.PROCESS_STATE_CACHED_RECENT: - procState = "CRE "; - break; - case ActivityManager.PROCESS_STATE_CACHED_EMPTY: - procState = "CEM "; - break; - case ActivityManager.PROCESS_STATE_NONEXISTENT: - procState = "NONE"; - break; - default: - procState = "??"; - break; - } - return procState; + return ActivityManager.procStateToString(curProcState); } public static int makeProcStateProtoEnum(int curProcState) { @@ -4768,11 +4699,13 @@ public final class ProcessList { int getBlockStateForUid(UidRecord uidRec) { // Denotes whether uid's process state is currently allowed network access. final boolean isAllowed = - isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState()) + isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState(), + uidRec.getCurCapability()) || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState()); // Denotes whether uid's process state was previously allowed network access. final boolean wasAllowed = - isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState()) + isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState(), + uidRec.getSetCapability()) || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState()); // When the uid is coming to foreground, AMS should inform the app thread that it should diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index c5be20e39864..f6a79ba1abfb 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -17,6 +17,7 @@ package com.android.server.input; import android.annotation.NonNull; +import android.annotation.Nullable; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -51,6 +52,8 @@ import android.hardware.input.InputManagerInternal.LidSwitchCallback; import android.hardware.input.InputSensorInfo; import android.hardware.input.KeyboardLayout; import android.hardware.input.TouchCalibration; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; import android.media.AudioManager; import android.os.Binder; import android.os.Bundle; @@ -237,6 +240,13 @@ public class InputManagerService extends IInputManager.Stub // List of vibrator states by device id. @GuardedBy("mVibratorLock") private final SparseBooleanArray mIsVibrating = new SparseBooleanArray(); + private Object mLightLock = new Object(); + // State for light tokens. A light token marks a lights manager session, it is generated + // by light session open() and deleted in session close(). + // When lights session requests light states, the token will be used to find the light session. + @GuardedBy("mLightLock") + private final ArrayMap<IBinder, LightSession> mLightSessions = + new ArrayMap<IBinder, LightSession>(); // State for lid switch private final Object mLidSwitchLock = new Object(); @@ -303,6 +313,12 @@ public class InputManagerService extends IInputManager.Stub private static native int[] nativeGetVibratorIds(long ptr, int deviceId); private static native int nativeGetBatteryCapacity(long ptr, int deviceId); private static native int nativeGetBatteryStatus(long ptr, int deviceId); + private static native List<Light> nativeGetLights(long ptr, int deviceId); + private static native int nativeGetLightPlayerId(long ptr, int deviceId, int lightId); + private static native int nativeGetLightColor(long ptr, int deviceId, int lightId); + private static native void nativeSetLightPlayerId(long ptr, int deviceId, int lightId, + int playerId); + private static native void nativeSetLightColor(long ptr, int deviceId, int lightId, int color); private static native void nativeReloadKeyboardLayouts(long ptr); private static native void nativeReloadDeviceAliases(long ptr); private static native String nativeDump(long ptr); @@ -2294,6 +2310,151 @@ public class InputManagerService extends IInputManager.Stub } } + /** + * LightSession represents a light session for lights manager. + */ + private final class LightSession implements DeathRecipient { + private final int mDeviceId; + private final IBinder mToken; + private final String mOpPkg; + // The light ids and states that are requested by the light seesion + private int[] mLightIds; + private LightState[] mLightStates; + + LightSession(int deviceId, String opPkg, IBinder token) { + mDeviceId = deviceId; + mOpPkg = opPkg; + mToken = token; + } + + @Override + public void binderDied() { + if (DEBUG) { + Slog.d(TAG, "Light token died."); + } + synchronized (mLightLock) { + closeLightSession(mDeviceId, mToken); + mLightSessions.remove(mToken); + } + } + } + + /** + * Returns the lights available for apps to control on the specified input device. + * Only lights that aren't reserved for system use are available to apps. + */ + @Override // Binder call + public List<Light> getLights(int deviceId) { + return nativeGetLights(mPtr, deviceId); + } + + /** + * Set specified light state with for a specific input device. + */ + private void setLightStateInternal(int deviceId, Light light, LightState lightState) { + Preconditions.checkNotNull(light, "light does not exist"); + if (DEBUG) { + Slog.d(TAG, "setLightStateInternal device " + deviceId + " light " + light + + "lightState " + lightState); + } + if (light.getType() == Light.LIGHT_TYPE_INPUT_PLAYER_ID) { + nativeSetLightPlayerId(mPtr, deviceId, light.getId(), lightState.getPlayerId()); + } else if (light.getType() == Light.LIGHT_TYPE_INPUT_SINGLE + || light.getType() == Light.LIGHT_TYPE_INPUT_RGB) { + // Set ARGB format color to input device light + // Refer to https://developer.android.com/reference/kotlin/android/graphics/Color + nativeSetLightColor(mPtr, deviceId, light.getId(), lightState.getColor()); + } else { + Slog.e(TAG, "setLightStates for unsupported light type " + light.getType()); + } + } + + /** + * Set multiple light states with multiple light ids for a specific input device. + */ + private void setLightStatesInternal(int deviceId, int[] lightIds, LightState[] lightStates) { + final List<Light> lights = nativeGetLights(mPtr, deviceId); + SparseArray<Light> lightArray = new SparseArray<>(); + for (int i = 0; i < lights.size(); i++) { + lightArray.put(lights.get(i).getId(), lights.get(i)); + } + for (int i = 0; i < lightIds.length; i++) { + if (lightArray.contains(lightIds[i])) { + setLightStateInternal(deviceId, lightArray.get(lightIds[i]), lightStates[i]); + } + } + } + + /** + * Set states for multiple lights for an opened light session. + */ + @Override + public void setLightStates(int deviceId, int[] lightIds, LightState[] lightStates, + IBinder token) { + Preconditions.checkArgument(lightIds.length == lightStates.length, + "lights and light states are not same length"); + synchronized (mLightLock) { + LightSession lightSession = mLightSessions.get(token); + Preconditions.checkArgument(lightSession != null, "not registered"); + Preconditions.checkState(lightSession.mDeviceId == deviceId, "Incorrect device ID"); + lightSession.mLightIds = lightIds.clone(); + lightSession.mLightStates = lightStates.clone(); + if (DEBUG) { + Slog.d(TAG, "setLightStates for " + lightSession.mOpPkg + " device " + deviceId); + } + } + setLightStatesInternal(deviceId, lightIds, lightStates); + } + + @Override + public @Nullable LightState getLightState(int deviceId, int lightId) { + synchronized (mLightLock) { + int color = nativeGetLightColor(mPtr, deviceId, lightId); + int playerId = nativeGetLightPlayerId(mPtr, deviceId, lightId); + + return new LightState(color, playerId); + } + } + + @Override + public void openLightSession(int deviceId, String opPkg, IBinder token) { + Preconditions.checkNotNull(token); + synchronized (mLightLock) { + Preconditions.checkState(mLightSessions.get(token) == null, "already registered"); + LightSession lightSession = new LightSession(deviceId, opPkg, token); + try { + token.linkToDeath(lightSession, 0); + } catch (RemoteException ex) { + // give up + ex.rethrowAsRuntimeException(); + } + mLightSessions.put(token, lightSession); + if (DEBUG) { + Slog.d(TAG, "Open light session for " + opPkg + " device " + deviceId); + } + } + } + + @Override + public void closeLightSession(int deviceId, IBinder token) { + Preconditions.checkNotNull(token); + synchronized (mLightLock) { + LightSession lightSession = mLightSessions.get(token); + Preconditions.checkState(lightSession != null, "not registered"); + // Turn off the lights that were previously requested by the session to be closed. + Arrays.fill(lightSession.mLightStates, new LightState(0)); + setLightStatesInternal(deviceId, lightSession.mLightIds, + lightSession.mLightStates); + mLightSessions.remove(token); + // If any other session is still pending with light request, apply the first session's + // request. + if (!mLightSessions.isEmpty()) { + LightSession nextSession = mLightSessions.valueAt(0); + setLightStatesInternal(deviceId, nextSession.mLightIds, nextSession.mLightStates); + } + } + } + @Override public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; diff --git a/services/core/java/com/android/server/net/NetworkPolicyLogger.java b/services/core/java/com/android/server/net/NetworkPolicyLogger.java index 676f4218f745..4b4146683a09 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyLogger.java +++ b/services/core/java/com/android/server/net/NetworkPolicyLogger.java @@ -29,6 +29,7 @@ import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT; import static android.os.Process.INVALID_UID; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessCapability; import android.net.NetworkPolicyManager; import android.os.UserHandle; import android.util.Log; @@ -97,13 +98,15 @@ public class NetworkPolicyLogger { } } - void uidStateChanged(int uid, int procState, long procStateSeq) { + void uidStateChanged(int uid, int procState, long procStateSeq, + @ProcessCapability int capability) { synchronized (mLock) { if (LOGV || uid == mDebugUid) { Slog.v(TAG, uid + " state changed to " - + ProcessList.makeProcStateString(procState) + " with seq=" + procStateSeq); + + ProcessList.makeProcStateString(procState) + ",seq=" + procStateSeq + + ",cap=" + ActivityManager.getCapabilitiesSummary(capability)); } - mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq); + mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq, capability); } } @@ -373,7 +376,8 @@ public class NetworkPolicyLogger { super(Data.class, capacity); } - public void uidStateChanged(int uid, int procState, long procStateSeq) { + public void uidStateChanged(int uid, int procState, long procStateSeq, + @ProcessCapability int capability) { final Data data = getNextSlot(); if (data == null) return; @@ -381,6 +385,7 @@ public class NetworkPolicyLogger { data.type = EVENT_UID_STATE_CHANGED; data.ifield1 = uid; data.ifield2 = procState; + data.ifield3 = capability; data.lfield1 = procStateSeq; data.timeStamp = System.currentTimeMillis(); } @@ -546,8 +551,9 @@ public class NetworkPolicyLogger { case EVENT_NETWORK_BLOCKED: return data.ifield1 + "-" + getBlockedReason(data.ifield2); case EVENT_UID_STATE_CHANGED: - return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2) - + "-" + data.lfield1; + return data.ifield1 + ":" + ProcessList.makeProcStateString(data.ifield2) + + ":" + ActivityManager.getCapabilitiesSummary(data.ifield3) + + ":" + data.lfield1; case EVENT_POLICIES_CHANGED: return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3); case EVENT_METEREDNESS_CHANGED: diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index aa7da54b2e1d..ecf4438d8aca 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -131,6 +131,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; +import android.app.ActivityManager.ProcessCapability; import android.app.ActivityManagerInternal; import android.app.AppGlobals; import android.app.AppOpsManager; @@ -166,6 +167,7 @@ import android.net.NetworkCapabilities; import android.net.NetworkIdentity; import android.net.NetworkPolicy; import android.net.NetworkPolicyManager; +import android.net.NetworkPolicyManager.UidState; import android.net.NetworkQuotaInfo; import android.net.NetworkRequest; import android.net.NetworkSpecifier; @@ -557,7 +559,7 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { /** Foreground at UID granularity. */ @GuardedBy("mUidRulesFirstLock") - final SparseIntArray mUidState = new SparseIntArray(); + final SparseArray<UidState> mUidState = new SparseArray<UidState>(); /** Map from network ID to last observed meteredness state */ @GuardedBy("mNetworkPoliciesSecondLock") @@ -988,9 +990,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { final private IUidObserver mUidObserver = new IUidObserver.Stub() { @Override public void onUidStateChanged(int uid, int procState, long procStateSeq, - int capability) { + @ProcessCapability int capability) { + // TODO: Avoid creating a new UidStateCallbackInfo object every time + // we get a callback for an uid mUidEventHandler.obtainMessage(UID_MSG_STATE_CHANGED, - uid, procState, procStateSeq).sendToTarget(); + new UidStateCallbackInfo(uid, procState, procStateSeq, capability)) + .sendToTarget(); } @Override public void onUidGone(int uid, boolean disabled) { @@ -1007,6 +1012,22 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } }; + private static final class UidStateCallbackInfo { + public final int uid; + public final int procState; + public final long procStateSeq; + @ProcessCapability + public final int capability; + + UidStateCallbackInfo(int uid, int procState, long procStateSeq, + @ProcessCapability int capability) { + this.uid = uid; + this.procState = procState; + this.procStateSeq = procStateSeq; + this.capability = capability; + } + } + final private BroadcastReceiver mPowerSaveWhitelistReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -3718,14 +3739,12 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { fout.print("UID="); fout.print(uid); - final int state = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); - fout.print(" state="); - fout.print(state); - if (state <= ActivityManager.PROCESS_STATE_TOP) { - fout.print(" (fg)"); + final UidState uidState = mUidState.get(uid); + if (uidState == null) { + fout.print(" state={null}"); } else { - fout.print(state <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - ? " (fg svc)" : " (bg)"); + fout.print(" state="); + fout.print(uidState.toString()); } final int uidRules = mUidRules.get(uid, RULE_NONE); @@ -3783,26 +3802,20 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { @VisibleForTesting boolean isUidForeground(int uid) { synchronized (mUidRulesFirstLock) { - return isUidStateForeground( - mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY)); + return isProcStateAllowedWhileIdleOrPowerSaveMode(mUidState.get(uid)); } } @GuardedBy("mUidRulesFirstLock") private boolean isUidForegroundOnRestrictBackgroundUL(int uid) { - final int procState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); - return isProcStateAllowedWhileOnRestrictBackground(procState); + final UidState uidState = mUidState.get(uid); + return isProcStateAllowedWhileOnRestrictBackground(uidState); } @GuardedBy("mUidRulesFirstLock") private boolean isUidForegroundOnRestrictPowerUL(int uid) { - final int procState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); - return isProcStateAllowedWhileIdleOrPowerSaveMode(procState); - } - - private boolean isUidStateForeground(int state) { - // only really in foreground when screen is also on - return state <= NetworkPolicyManager.FOREGROUND_THRESHOLD_STATE; + final UidState uidState = mUidState.get(uid); + return isProcStateAllowedWhileIdleOrPowerSaveMode(uidState); } /** @@ -3811,16 +3824,18 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { * {@link #updateRulesForPowerRestrictionsUL(int)}. Returns true if the state was updated. */ @GuardedBy("mUidRulesFirstLock") - private boolean updateUidStateUL(int uid, int uidState) { + private boolean updateUidStateUL(int uid, int procState, @ProcessCapability int capability) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "updateUidStateUL"); try { - final int oldUidState = mUidState.get(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY); - if (oldUidState != uidState) { + final UidState oldUidState = mUidState.get(uid); + if (oldUidState == null || oldUidState.procState != procState + || oldUidState.capability != capability) { + final UidState newUidState = new UidState(uid, procState, capability); // state changed, push updated rules - mUidState.put(uid, uidState); - updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, uidState); + mUidState.put(uid, newUidState); + updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, newUidState); if (isProcStateAllowedWhileIdleOrPowerSaveMode(oldUidState) - != isProcStateAllowedWhileIdleOrPowerSaveMode(uidState) ) { + != isProcStateAllowedWhileIdleOrPowerSaveMode(newUidState)) { updateRuleForAppIdleUL(uid); if (mDeviceIdleMode) { updateRuleForDeviceIdleUL(uid); @@ -3842,11 +3857,10 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private boolean removeUidStateUL(int uid) { final int index = mUidState.indexOfKey(uid); if (index >= 0) { - final int oldUidState = mUidState.valueAt(index); + final UidState oldUidState = mUidState.valueAt(index); mUidState.removeAt(index); - if (oldUidState != ActivityManager.PROCESS_STATE_CACHED_EMPTY) { - updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, - ActivityManager.PROCESS_STATE_CACHED_EMPTY); + if (oldUidState != null) { + updateRestrictBackgroundRulesOnUidStatusChangedUL(uid, oldUidState, null); if (mDeviceIdleMode) { updateRuleForDeviceIdleUL(uid); } @@ -3873,8 +3887,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } - private void updateRestrictBackgroundRulesOnUidStatusChangedUL(int uid, int oldUidState, - int newUidState) { + private void updateRestrictBackgroundRulesOnUidStatusChangedUL(int uid, + @Nullable UidState oldUidState, @Nullable UidState newUidState) { final boolean oldForeground = isProcStateAllowedWhileOnRestrictBackground(oldUidState); final boolean newForeground = @@ -4979,11 +4993,14 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { public boolean handleMessage(Message msg) { switch (msg.what) { case UID_MSG_STATE_CHANGED: { - final int uid = msg.arg1; - final int procState = msg.arg2; - final long procStateSeq = (Long) msg.obj; - - handleUidChanged(uid, procState, procStateSeq); + final UidStateCallbackInfo uidStateCallbackInfo = + (UidStateCallbackInfo) msg.obj; + final int uid = uidStateCallbackInfo.uid; + final int procState = uidStateCallbackInfo.procState; + final long procStateSeq = uidStateCallbackInfo.procStateSeq; + final int capability = uidStateCallbackInfo.capability; + + handleUidChanged(uid, procState, procStateSeq, capability); return true; } case UID_MSG_GONE: { @@ -4998,23 +5015,24 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } }; - void handleUidChanged(int uid, int procState, long procStateSeq) { + void handleUidChanged(int uid, int procState, long procStateSeq, + @ProcessCapability int capability) { Trace.traceBegin(Trace.TRACE_TAG_NETWORK, "onUidStateChanged"); try { boolean updated; synchronized (mUidRulesFirstLock) { // We received a uid state change callback, add it to the history so that it // will be useful for debugging. - mLogger.uidStateChanged(uid, procState, procStateSeq); + mLogger.uidStateChanged(uid, procState, procStateSeq, capability); // Now update the network policy rules as per the updated uid state. - updated = updateUidStateUL(uid, procState); + updated = updateUidStateUL(uid, procState, capability); // Updating the network rules is done, so notify AMS about this. mActivityManagerInternal.notifyNetworkPolicyRulesUpdated(uid, procStateSeq); } // Do this without the lock held. handleUidChanged() and handleUidGone() are // called from the handler, so there's no multi-threading issue. if (updated) { - updateNetworkStats(uid, isUidStateForeground(procState)); + updateNetworkStats(uid, isProcStateAllowedWhileOnRestrictBackground(procState)); } } finally { Trace.traceEnd(Trace.TRACE_TAG_NETWORK); @@ -5349,6 +5367,13 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } } + private static void collectKeys(SparseArray<UidState> source, SparseBooleanArray target) { + final int size = source.size(); + for (int i = 0; i < size; i++) { + target.put(source.keyAt(i), true); + } + } + @Override public void factoryReset(String subscriber) { mContext.enforceCallingOrSelfPermission(NETWORK_SETTINGS, TAG); diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java index 5d7c41c7b08f..1acbabda9e19 100644 --- a/services/core/java/com/android/server/pm/AppsFilter.java +++ b/services/core/java/com/android/server/pm/AppsFilter.java @@ -69,6 +69,7 @@ import java.util.Objects; import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.Executor; +import java.util.function.Function; /** * The entity responsible for filtering visibility between apps based on declarations in their @@ -1354,14 +1355,13 @@ public class AppsFilter implements Watchable, Snappable { } public void dumpQueries( - PrintWriter pw, PackageManagerService pms, @Nullable Integer filteringAppId, - DumpState dumpState, - int[] users) { + PrintWriter pw, @Nullable Integer filteringAppId, DumpState dumpState, int[] users, + Function<Integer, String[]> getPackagesForUid) { final SparseArray<String> cache = new SparseArray<>(); ToString<Integer> expandPackages = input -> { String cachedValue = cache.get(input); if (cachedValue == null) { - final String[] packagesForUid = pms.getPackagesForUid(input); + final String[] packagesForUid = getPackagesForUid.apply(input); if (packagesForUid == null) { cachedValue = "[unknown app id " + input + "]"; } else { diff --git a/services/core/java/com/android/server/pm/DumpState.java b/services/core/java/com/android/server/pm/DumpState.java index 2a1fc87fc29f..380cdb10569b 100644 --- a/services/core/java/com/android/server/pm/DumpState.java +++ b/services/core/java/com/android/server/pm/DumpState.java @@ -55,6 +55,9 @@ public final class DumpState { private int mOptions; private boolean mTitlePrinted; + private boolean mFullPreferred; + + private String mTargetPackageName; private SharedUserSetting mSharedUser; @@ -99,4 +102,20 @@ public final class DumpState { public void setSharedUser(SharedUserSetting user) { mSharedUser = user; } + + public String getTargetPackageName() { + return mTargetPackageName; + } + + public void setTargetPackageName(String packageName) { + mTargetPackageName = packageName; + } + + public boolean isFullPreferred() { + return mFullPreferred; + } + + public void setFullPreferred(boolean fullPreferred) { + mFullPreferred = fullPreferred; + } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 2a3feb123dd9..b0037f423563 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1994,6 +1994,7 @@ public class PackageManagerService extends IPackageManager.Stub SigningDetails getSigningDetails(int uid); boolean filterAppAccess(AndroidPackage pkg, int callingUid, int userId); boolean filterAppAccess(String packageName, int callingUid, int userId); + void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState); } /** @@ -2037,6 +2038,9 @@ public class PackageManagerService extends IPackageManager.Stub private final InstantAppResolverConnection mInstantAppResolverConnection; private final DefaultAppProvider mDefaultAppProvider; private final DomainVerificationManagerInternal mDomainVerificationManager; + private final PackageDexOptimizer mPackageDexOptimizer; + private final DexManager mDexManager; + private final CompilerStats mCompilerStats; // PackageManagerService attributes that are primitives are referenced through the // pms object directly. Primitives are the only attributes so referenced. @@ -2083,6 +2087,9 @@ public class PackageManagerService extends IPackageManager.Stub mInstantAppResolverConnection = args.service.mInstantAppResolverConnection; mDefaultAppProvider = args.service.mDefaultAppProvider; mDomainVerificationManager = args.service.mDomainVerificationManager; + mPackageDexOptimizer = args.service.mPackageDexOptimizer; + mDexManager = args.service.mDexManager; + mCompilerStats = args.service.mCompilerStats; // Used to reference PMS attributes that are primitives and which are not // updated under control of the PMS lock. @@ -4393,6 +4400,143 @@ public class PackageManagerService extends IPackageManager.Stub userId); } + public void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) { + final String packageName = dumpState.getTargetPackageName(); + + switch (type) { + case DumpState.DUMP_VERSION: + { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + pw.println("Database versions:"); + mSettings.dumpVersionLPr(new IndentingPrintWriter(pw, " ")); + break; + } + + case DumpState.DUMP_PREFERRED_XML: + { + pw.flush(); + FileOutputStream fout = new FileOutputStream(fd); + BufferedOutputStream str = new BufferedOutputStream(fout); + TypedXmlSerializer serializer = Xml.newFastSerializer(); + try { + serializer.setOutput(str, StandardCharsets.UTF_8.name()); + serializer.startDocument(null, true); + serializer.setFeature( + "http://xmlpull.org/v1/doc/features.html#indent-output", true); + mSettings.writePreferredActivitiesLPr(serializer, 0, + dumpState.isFullPreferred()); + serializer.endDocument(); + serializer.flush(); + } catch (IllegalArgumentException e) { + pw.println("Failed writing: " + e); + } catch (IllegalStateException e) { + pw.println("Failed writing: " + e); + } catch (IOException e) { + pw.println("Failed writing: " + e); + } + break; + } + + case DumpState.DUMP_QUERIES: + { + final PackageSetting setting = mSettings.getPackageLPr(packageName); + Integer filteringAppId = setting == null ? null : setting.appId; + mAppsFilter.dumpQueries( + pw, filteringAppId, dumpState, mUserManager.getUserIds(), + this::getPackagesForUid); + break; + } + + case DumpState.DUMP_DOMAIN_PREFERRED: + { + final android.util.IndentingPrintWriter writer = + new android.util.IndentingPrintWriter(pw); + if (dumpState.onTitlePrinted()) pw.println(); + + writer.println("Domain verification status:"); + writer.increaseIndent(); + try { + mDomainVerificationManager.printState(writer, packageName, + UserHandle.USER_ALL, mSettings::getPackageLPr); + } catch (PackageManager.NameNotFoundException e) { + pw.println("Failure printing domain verification information"); + Slog.e(TAG, "Failure printing domain verification information", e); + } + writer.decreaseIndent(); + break; + } + + case DumpState.DUMP_DEXOPT: + { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println(); + ipw.println("Dexopt state:"); + ipw.increaseIndent(); + Collection<PackageSetting> pkgSettings; + if (packageName != null) { + PackageSetting targetPkgSetting = mSettings.getPackageLPr(packageName); + if (targetPkgSetting != null) { + pkgSettings = Collections.singletonList(targetPkgSetting); + } else { + ipw.println("Unable to find package: " + packageName); + return; + } + } else { + pkgSettings = mSettings.getPackagesLocked().values(); + } + + for (PackageSetting pkgSetting : pkgSettings) { + final AndroidPackage pkg = pkgSetting.getPkg(); + if (pkg == null) { + continue; + } + ipw.println("[" + pkgSetting.name + "]"); + ipw.increaseIndent(); + mPackageDexOptimizer.dumpDexoptState(ipw, pkg, pkgSetting, + mDexManager.getPackageUseInfoOrDefault(pkg.getPackageName())); + ipw.decreaseIndent(); + } + break; + } + + case DumpState.DUMP_COMPILER_STATS: + { + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); + ipw.println(); + ipw.println("Compiler stats:"); + ipw.increaseIndent(); + Collection<AndroidPackage> packages; + if (packageName != null) { + AndroidPackage targetPackage = mPackages.get(packageName); + if (targetPackage != null) { + packages = Collections.singletonList(targetPackage); + } else { + ipw.println("Unable to find package: " + packageName); + return; + } + } else { + packages = mPackages.values(); + } + + for (AndroidPackage pkg : packages) { + final String pkgName = pkg.getPackageName(); + ipw.println("[" + pkgName + "]"); + ipw.increaseIndent(); + + CompilerStats.PackageStats stats = mCompilerStats.getPackageStats(pkgName); + if (stats == null) { + ipw.println("(No recorded stats)"); + } else { + stats.dump(ipw); + } + ipw.decreaseIndent(); + } + break; + } + } // switch + } } /** @@ -23554,10 +23698,8 @@ public class PackageManagerService extends IPackageManager.Stub if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return; DumpState dumpState = new DumpState(); - boolean fullPreferred = false; boolean checkin = false; - String packageName = null; ArraySet<String> permissionNames = null; int opti = 0; @@ -23626,7 +23768,7 @@ public class PackageManagerService extends IPackageManager.Stub opti++; // Is this a package name? if ("android".equals(cmd) || cmd.contains(".")) { - packageName = cmd; + dumpState.setTargetPackageName(cmd); // When dumping a single package, we always dump all of its // filter information since the amount of data will be reasonable. dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS); @@ -23707,7 +23849,7 @@ public class PackageManagerService extends IPackageManager.Stub } else if ("preferred-xml".equals(cmd)) { dumpState.setDump(DumpState.DUMP_PREFERRED_XML); if (opti < args.length && "--full".equals(args[opti])) { - fullPreferred = true; + dumpState.setFullPreferred(true); opti++; } } else if ("d".equals(cmd) || "domain-preferred-apps".equals(cmd)) { @@ -23760,257 +23902,208 @@ public class PackageManagerService extends IPackageManager.Stub } } + final String packageName = dumpState.getTargetPackageName(); if (checkin) { pw.println("vers,1"); } // reader - synchronized (mLock) { - if (dumpState.isDumping(DumpState.DUMP_VERSION) && packageName == null) { - if (!checkin) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Database versions:"); - mSettings.dumpVersionLPr(new IndentingPrintWriter(pw, " ")); - } + if (dumpState.isDumping(DumpState.DUMP_VERSION) && packageName == null) { + if (!checkin) { + dump(DumpState.DUMP_VERSION, fd, pw, dumpState); } + } - if (!checkin - && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES) - && packageName == null) { - if (dumpState.onTitlePrinted()) { - pw.println(); - } - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - ipw.println("Known Packages:"); + if (!checkin + && dumpState.isDumping(DumpState.DUMP_KNOWN_PACKAGES) + && packageName == null) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + ipw.println("Known Packages:"); + ipw.increaseIndent(); + for (int i = 0; i <= LAST_KNOWN_PACKAGE; i++) { + final String knownPackage = mPmInternal.knownPackageToString(i); + ipw.print(knownPackage); + ipw.println(":"); + final String[] pkgNames = mPmInternal.getKnownPackageNames(i, + UserHandle.USER_SYSTEM); ipw.increaseIndent(); - for (int i = 0; i <= LAST_KNOWN_PACKAGE; i++) { - final String knownPackage = mPmInternal.knownPackageToString(i); - ipw.print(knownPackage); - ipw.println(":"); - final String[] pkgNames = mPmInternal.getKnownPackageNames(i, - UserHandle.USER_SYSTEM); - ipw.increaseIndent(); - if (ArrayUtils.isEmpty(pkgNames)) { - ipw.println("none"); - } else { - for (String name : pkgNames) { - ipw.println(name); - } + if (ArrayUtils.isEmpty(pkgNames)) { + ipw.println("none"); + } else { + for (String name : pkgNames) { + ipw.println(name); } - ipw.decreaseIndent(); } ipw.decreaseIndent(); } + ipw.decreaseIndent(); + } - if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) { + if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) { + final String requiredVerifierPackage = mRequiredVerifierPackage; + if (!checkin) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + pw.println("Verifiers:"); + pw.print(" Required: "); + pw.print(requiredVerifierPackage); + pw.print(" (uid="); + pw.print(getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, + UserHandle.USER_SYSTEM)); + pw.println(")"); + } else if (requiredVerifierPackage != null) { + pw.print("vrfy,"); pw.print(requiredVerifierPackage); + pw.print(","); + pw.println(getPackageUid(requiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, + UserHandle.USER_SYSTEM)); + } + } + + if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER) && packageName == null) { + final DomainVerificationProxy proxy = mDomainVerificationManager.getProxy(); + final ComponentName verifierComponent = proxy.getComponentName(); + if (verifierComponent != null) { + String verifierPackageName = verifierComponent.getPackageName(); if (!checkin) { if (dumpState.onTitlePrinted()) pw.println(); - pw.println("Verifiers:"); - pw.print(" Required: "); - pw.print(mRequiredVerifierPackage); + pw.println("Domain Verifier:"); + pw.print(" Using: "); + pw.print(verifierPackageName); pw.print(" (uid="); - pw.print(getPackageUid(mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, + pw.print(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); pw.println(")"); - } else if (mRequiredVerifierPackage != null) { - pw.print("vrfy,"); pw.print(mRequiredVerifierPackage); + } else if (verifierPackageName != null) { + pw.print("dv,"); pw.print(verifierPackageName); pw.print(","); - pw.println(getPackageUid(mRequiredVerifierPackage, MATCH_DEBUG_TRIAGED_MISSING, + pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, UserHandle.USER_SYSTEM)); } + } else { + pw.println(); + pw.println("No Domain Verifier available!"); } + } - if (dumpState.isDumping(DumpState.DUMP_DOMAIN_VERIFIER) && - packageName == null) { - DomainVerificationProxy proxy = mDomainVerificationManager.getProxy(); - ComponentName verifierComponent = proxy.getComponentName(); - if (verifierComponent != null) { - String verifierPackageName = verifierComponent.getPackageName(); - if (!checkin) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Domain Verifier:"); - pw.print(" Using: "); - pw.print(verifierPackageName); - pw.print(" (uid="); - pw.print(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.USER_SYSTEM)); - pw.println(")"); - } else if (verifierPackageName != null) { - pw.print("dv,"); pw.print(verifierPackageName); - pw.print(","); - pw.println(getPackageUid(verifierPackageName, MATCH_DEBUG_TRIAGED_MISSING, - UserHandle.USER_SYSTEM)); - } - } else { - pw.println(); - pw.println("No Domain Verifier available!"); - } + if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { + // TODO: Move it to ComputerEngine once LongSparseArray<SharedLibraryInfo> is copied + // in snapshot. + synchronized (mLock) { + dumpSharedLibrariesLPr(pw, dumpState, checkin); } + } - if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { - boolean printedHeader = false; - final int numSharedLibraries = mSharedLibraries.size(); - for (int index = 0; index < numSharedLibraries; index++) { - final String libName = mSharedLibraries.keyAt(index); - WatchedLongSparseArray<SharedLibraryInfo> versionedLib - = mSharedLibraries.get(libName); - if (versionedLib == null) { - continue; - } - final int versionCount = versionedLib.size(); - for (int i = 0; i < versionCount; i++) { - SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); - if (!checkin) { - if (!printedHeader) { - if (dumpState.onTitlePrinted()) - pw.println(); - pw.println("Libraries:"); - printedHeader = true; - } - pw.print(" "); - } else { - pw.print("lib,"); - } - pw.print(libraryInfo.getName()); - if (libraryInfo.isStatic()) { - pw.print(" version=" + libraryInfo.getLongVersion()); - } - if (!checkin) { - pw.print(" -> "); - } - if (libraryInfo.getPath() != null) { - if (libraryInfo.isNative()) { - pw.print(" (so) "); - } else { - pw.print(" (jar) "); - } - pw.print(libraryInfo.getPath()); - } else { - pw.print(" (apk) "); - pw.print(libraryInfo.getPackageName()); - } - pw.println(); - } - } + if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + if (!checkin) { + pw.println("Features:"); } - if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { - if (dumpState.onTitlePrinted()) - pw.println(); - if (!checkin) { - pw.println("Features:"); - } - - synchronized (mAvailableFeatures) { - for (FeatureInfo feat : mAvailableFeatures.values()) { - if (checkin) { - pw.print("feat,"); - pw.print(feat.name); - pw.print(","); - pw.println(feat.version); - } else { - pw.print(" "); - pw.print(feat.name); - if (feat.version > 0) { - pw.print(" version="); - pw.print(feat.version); - } - pw.println(); + synchronized (mAvailableFeatures) { + for (FeatureInfo feat : mAvailableFeatures.values()) { + if (checkin) { + pw.print("feat,"); + pw.print(feat.name); + pw.print(","); + pw.println(feat.version); + } else { + pw.print(" "); + pw.print(feat.name); + if (feat.version > 0) { + pw.print(" version="); + pw.print(feat.version); } + pw.println(); } } } + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_ACTIVITY_RESOLVERS)) { + synchronized (mLock) { mComponentResolver.dumpActivityResolvers(pw, dumpState, packageName); } - if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) { + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_RECEIVER_RESOLVERS)) { + synchronized (mLock) { mComponentResolver.dumpReceiverResolvers(pw, dumpState, packageName); } - if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) { + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_RESOLVERS)) { + synchronized (mLock) { mComponentResolver.dumpServiceResolvers(pw, dumpState, packageName); } - if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) { + } + if (!checkin && dumpState.isDumping(DumpState.DUMP_CONTENT_RESOLVERS)) { + synchronized (mLock) { mComponentResolver.dumpProviderResolvers(pw, dumpState, packageName); } + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED)) { + // TODO: This cannot be moved to ComputerEngine since some variables with collections + // types in IntentResolver such as mTypeToFilter do not have a copy of `F[]`. + synchronized (mLock) { mSettings.dumpPreferred(pw, dumpState, packageName); } + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) { - pw.flush(); - FileOutputStream fout = new FileOutputStream(fd); - BufferedOutputStream str = new BufferedOutputStream(fout); - TypedXmlSerializer serializer = Xml.newFastSerializer(); - try { - serializer.setOutput(str, StandardCharsets.UTF_8.name()); - serializer.startDocument(null, true); - serializer.setFeature( - "http://xmlpull.org/v1/doc/features.html#indent-output", true); - mSettings.writePreferredActivitiesLPr(serializer, 0, fullPreferred); - serializer.endDocument(); - serializer.flush(); - } catch (IllegalArgumentException e) { - pw.println("Failed writing: " + e); - } catch (IllegalStateException e) { - pw.println("Failed writing: " + e); - } catch (IOException e) { - pw.println("Failed writing: " + e); - } - } - - if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) { - android.util.IndentingPrintWriter writer = - new android.util.IndentingPrintWriter(pw); - if (dumpState.onTitlePrinted()) pw.println(); + if (!checkin && dumpState.isDumping(DumpState.DUMP_PREFERRED_XML)) { + dump(DumpState.DUMP_PREFERRED_XML, fd, pw, dumpState); + } - writer.println("Domain verification status:"); - writer.increaseIndent(); - try { - mDomainVerificationManager.printState(writer, packageName, UserHandle.USER_ALL, - mSettings::getPackageLPr); - } catch (PackageManager.NameNotFoundException e) { - pw.println("Failure printing domain verification information"); - Slog.e(TAG, "Failure printing domain verification information", e); - } - writer.decreaseIndent(); - } + if (!checkin && dumpState.isDumping(DumpState.DUMP_DOMAIN_PREFERRED)) { + dump(DumpState.DUMP_DOMAIN_PREFERRED, fd, pw, dumpState); + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { - mSettings.dumpPermissionsLPr(pw, packageName, permissionNames, dumpState); - } + if (!checkin && dumpState.isDumping(DumpState.DUMP_PERMISSIONS)) { + mSettings.dumpPermissions(pw, packageName, permissionNames, dumpState); + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { + synchronized (mLock) { mComponentResolver.dumpContentProviders(pw, dumpState, packageName); } + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_KEYSETS)) { + synchronized (mLock) { mSettings.getKeySetManagerService().dumpLPr(pw, packageName, dumpState); } + } - if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) { + if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) { + // This cannot be moved to ComputerEngine since some variables of the collections + // in PackageUserState such as suspendParams, disabledComponents and enabledComponents + // do not have a copy. + synchronized (mLock) { mSettings.dumpPackagesLPr(pw, packageName, permissionNames, dumpState, checkin); } + } - if (dumpState.isDumping(DumpState.DUMP_QUERIES)) { - final PackageSetting setting = mSettings.getPackageLPr(packageName); - Integer filteringAppId = setting == null ? null : setting.appId; - mAppsFilter.dumpQueries( - pw, this, filteringAppId, dumpState, - mUserManager.getUserIds()); - } + if (dumpState.isDumping(DumpState.DUMP_QUERIES)) { + dump(DumpState.DUMP_QUERIES, fd, pw, dumpState); + } - if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { + if (dumpState.isDumping(DumpState.DUMP_SHARED_USERS)) { + // This cannot be moved to ComputerEngine since the set of packages in the + // SharedUserSetting do not have a copy. + synchronized (mLock) { mSettings.dumpSharedUsersLPr(pw, packageName, permissionNames, dumpState, checkin); } + } - if (dumpState.isDumping(DumpState.DUMP_CHANGES)) { - if (dumpState.onTitlePrinted()) pw.println(); - pw.println("Package Changes:"); + if (dumpState.isDumping(DumpState.DUMP_CHANGES)) { + if (dumpState.onTitlePrinted()) pw.println(); + pw.println("Package Changes:"); + synchronized (mLock) { pw.print(" Sequence number="); pw.println(mChangedPackagesSequenceNumber); final int K = mChangedPackages.size(); for (int i = 0; i < K; i++) { @@ -24032,16 +24125,18 @@ public class PackageManagerService extends IPackageManager.Stub } } } + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_FROZEN) && packageName == null) { - // XXX should handle packageName != null by dumping only install data that - // the given package is involved with. - if (dumpState.onTitlePrinted()) pw.println(); + if (!checkin && dumpState.isDumping(DumpState.DUMP_FROZEN) && packageName == null) { + // XXX should handle packageName != null by dumping only install data that + // the given package is involved with. + if (dumpState.onTitlePrinted()) pw.println(); - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - ipw.println(); - ipw.println("Frozen packages:"); - ipw.increaseIndent(); + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + ipw.println(); + ipw.println("Frozen packages:"); + ipw.increaseIndent(); + synchronized (mLock) { if (mFrozenPackages.size() == 0) { ipw.println("(none)"); } else { @@ -24049,16 +24144,18 @@ public class PackageManagerService extends IPackageManager.Stub ipw.println(mFrozenPackages.valueAt(i)); } } - ipw.decreaseIndent(); } + ipw.decreaseIndent(); + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_VOLUMES) && packageName == null) { - if (dumpState.onTitlePrinted()) pw.println(); + if (!checkin && dumpState.isDumping(DumpState.DUMP_VOLUMES) && packageName == null) { + if (dumpState.onTitlePrinted()) pw.println(); - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - ipw.println(); - ipw.println("Loaded volumes:"); - ipw.increaseIndent(); + final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); + ipw.println(); + ipw.println("Loaded volumes:"); + ipw.increaseIndent(); + synchronized (mLoadedVolumes) { if (mLoadedVolumes.size() == 0) { ipw.println("(none)"); } else { @@ -24066,36 +24163,39 @@ public class PackageManagerService extends IPackageManager.Stub ipw.println(mLoadedVolumes.valueAt(i)); } } - ipw.decreaseIndent(); } + ipw.decreaseIndent(); + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS) - && packageName == null) { + if (!checkin && dumpState.isDumping(DumpState.DUMP_SERVICE_PERMISSIONS) + && packageName == null) { + synchronized (mLock) { mComponentResolver.dumpServicePermissions(pw, dumpState); } + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_DEXOPT)) { - if (dumpState.onTitlePrinted()) pw.println(); - dumpDexoptStateLPr(pw, packageName); - } + if (!checkin && dumpState.isDumping(DumpState.DUMP_DEXOPT)) { + if (dumpState.onTitlePrinted()) pw.println(); + dump(DumpState.DUMP_DEXOPT, fd, pw, dumpState); + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) { - if (dumpState.onTitlePrinted()) pw.println(); - dumpCompilerStatsLPr(pw, packageName); - } + if (!checkin && dumpState.isDumping(DumpState.DUMP_COMPILER_STATS)) { + if (dumpState.onTitlePrinted()) pw.println(); + dump(DumpState.DUMP_COMPILER_STATS, fd, pw, dumpState); + } - if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { - if (dumpState.onTitlePrinted()) pw.println(); + if (!checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { + if (dumpState.onTitlePrinted()) pw.println(); + synchronized (mLock) { mSettings.dumpReadMessagesLPr(pw, dumpState); - - pw.println(); - pw.println("Package warning messages:"); - dumpCriticalInfo(pw, null); } + pw.println(); + pw.println("Package warning messages:"); + dumpCriticalInfo(pw, null); + } - if (checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES)) { - dumpCriticalInfo(pw, "msg,"); - } + if (checkin && dumpState.isDumping(DumpState.DUMP_MESSAGES)) { + dumpCriticalInfo(pw, "msg,"); } // PackageInstaller should be called outside of mPackages lock @@ -24130,6 +24230,14 @@ public class PackageManagerService extends IPackageManager.Stub } } + /** + * Dump package manager states to the file according to a given dumping type of + * {@link DumpState}. + */ + private void dump(int type, FileDescriptor fd, PrintWriter pw, DumpState dumpState) { + snapshotComputer().dump(type, fd, pw, dumpState); + } + //TODO: b/111402650 private void disableSkuSpecificApps() { String apkList[] = mContext.getResources().getStringArray( @@ -24228,69 +24336,50 @@ public class PackageManagerService extends IPackageManager.Stub } } - @GuardedBy("mLock") - @SuppressWarnings("resource") - private void dumpDexoptStateLPr(PrintWriter pw, String packageName) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println(); - ipw.println("Dexopt state:"); - ipw.increaseIndent(); - Collection<PackageSetting> pkgSettings; - if (packageName != null) { - PackageSetting targetPkgSetting = mSettings.getPackageLPr(packageName); - if (targetPkgSetting != null) { - pkgSettings = Collections.singletonList(targetPkgSetting); - } else { - ipw.println("Unable to find package: " + packageName); - return; - } - } else { - pkgSettings = mSettings.getPackagesLocked().values(); - } - - for (PackageSetting pkgSetting : pkgSettings) { - if (pkgSetting.pkg == null) { + private void dumpSharedLibrariesLPr(PrintWriter pw, DumpState dumpState, boolean checkin) { + boolean printedHeader = false; + final int numSharedLibraries = mSharedLibraries.size(); + for (int index = 0; index < numSharedLibraries; index++) { + final String libName = mSharedLibraries.keyAt(index); + WatchedLongSparseArray<SharedLibraryInfo> versionedLib = mSharedLibraries.get(libName); + if (versionedLib == null) { continue; } - ipw.println("[" + pkgSetting.name + "]"); - ipw.increaseIndent(); - mPackageDexOptimizer.dumpDexoptState(ipw, pkgSetting.pkg, pkgSetting, - mDexManager.getPackageUseInfoOrDefault(pkgSetting.pkg.getPackageName())); - ipw.decreaseIndent(); - } - } - - @GuardedBy("mLock") - @SuppressWarnings("resource") - private void dumpCompilerStatsLPr(PrintWriter pw, String packageName) { - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " "); - ipw.println(); - ipw.println("Compiler stats:"); - ipw.increaseIndent(); - Collection<AndroidPackage> packages; - if (packageName != null) { - AndroidPackage targetPackage = mPackages.get(packageName); - if (targetPackage != null) { - packages = Collections.singletonList(targetPackage); - } else { - ipw.println("Unable to find package: " + packageName); - return; - } - } else { - packages = mPackages.values(); - } - - for (AndroidPackage pkg : packages) { - ipw.println("[" + pkg.getPackageName() + "]"); - ipw.increaseIndent(); - - CompilerStats.PackageStats stats = getCompilerPackageStats(pkg.getPackageName()); - if (stats == null) { - ipw.println("(No recorded stats)"); - } else { - stats.dump(ipw); + final int versionCount = versionedLib.size(); + for (int i = 0; i < versionCount; i++) { + SharedLibraryInfo libraryInfo = versionedLib.valueAt(i); + if (!checkin) { + if (!printedHeader) { + if (dumpState.onTitlePrinted()) { + pw.println(); + } + pw.println("Libraries:"); + printedHeader = true; + } + pw.print(" "); + } else { + pw.print("lib,"); + } + pw.print(libraryInfo.getName()); + if (libraryInfo.isStatic()) { + pw.print(" version=" + libraryInfo.getLongVersion()); + } + if (!checkin) { + pw.print(" -> "); + } + if (libraryInfo.getPath() != null) { + if (libraryInfo.isNative()) { + pw.print(" (so) "); + } else { + pw.print(" (jar) "); + } + pw.print(libraryInfo.getPath()); + } else { + pw.print(" (apk) "); + pw.print(libraryInfo.getPackageName()); + } + pw.println(); } - ipw.decreaseIndent(); } } @@ -24452,7 +24541,9 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_INSTALL) Slog.d(TAG, "Loaded packages " + loaded); sendResourcesChangedBroadcast(true, false, loaded, null); - mLoadedVolumes.add(vol.getId()); + synchronized (mLoadedVolumes) { + mLoadedVolumes.add(vol.getId()); + } } private void unloadPrivatePackages(final VolumeInfo vol) { @@ -24500,7 +24591,9 @@ public class PackageManagerService extends IPackageManager.Stub if (DEBUG_INSTALL) Slog.d(TAG, "Unloaded packages " + unloaded); sendResourcesChangedBroadcast(false, false, unloaded, null); - mLoadedVolumes.remove(vol.getId()); + synchronized (mLoadedVolumes) { + mLoadedVolumes.remove(vol.getId()); + } // Try very hard to release any references to this path so we don't risk // the system server being killed due to open FDs diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 2112247650a5..f1ffdaf7f111 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4877,7 +4877,7 @@ public final class Settings implements Watchable, Snappable { } } - void dumpPermissionsLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames, + void dumpPermissions(PrintWriter pw, String packageName, ArraySet<String> permissionNames, DumpState dumpState) { LegacyPermissionSettings.dumpPermissions(pw, packageName, permissionNames, mPermissionDataProvider.getLegacyPermissions(), diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 643503d18bed..21d57d8c189f 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -158,6 +158,20 @@ static struct { static struct { jclass clazz; jmethodID constructor; + jfieldID lightTypeSingle; + jfieldID lightTypePlayerId; + jfieldID lightTypeRgb; +} gLightClassInfo; + +static struct { + jclass clazz; + jmethodID constructor; + jmethodID add; +} gArrayListClassInfo; + +static struct { + jclass clazz; + jmethodID constructor; jmethodID keyAt; jmethodID valueAt; jmethodID size; @@ -1923,6 +1937,79 @@ static jintArray nativeGetVibratorIds(JNIEnv* env, jclass clazz, jlong ptr, jint return vibIdArray; } +static jobject nativeGetLights(JNIEnv* env, jclass clazz, jlong ptr, jint deviceId) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + jobject jLights = env->NewObject(gArrayListClassInfo.clazz, gArrayListClassInfo.constructor); + + std::vector<int> lightIds = im->getInputManager()->getReader()->getLightIds(deviceId); + + for (size_t i = 0; i < lightIds.size(); i++) { + const InputDeviceLightInfo* lightInfo = + im->getInputManager()->getReader()->getLightInfo(deviceId, lightIds[i]); + if (lightInfo == nullptr) { + ALOGW("Failed to get input device %d light info for id %d", deviceId, lightIds[i]); + continue; + } + + jint jTypeId = 0; + if (lightInfo->type == InputDeviceLightType::SINGLE) { + jTypeId = + env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeSingle); + } else if (lightInfo->type == InputDeviceLightType::PLAYER_ID) { + jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, + gLightClassInfo.lightTypePlayerId); + } else if (lightInfo->type == InputDeviceLightType::RGB || + lightInfo->type == InputDeviceLightType::MULTI_COLOR) { + jTypeId = env->GetStaticIntField(gLightClassInfo.clazz, gLightClassInfo.lightTypeRgb); + } else { + ALOGW("Unknown light type %d", lightInfo->type); + continue; + } + ScopedLocalRef<jobject> + lightObj(env, + env->NewObject(gLightClassInfo.clazz, gLightClassInfo.constructor, + (jint)lightInfo->id, (jint)lightInfo->ordinal, jTypeId, + env->NewStringUTF(lightInfo->name.c_str()))); + // Add light object to list + env->CallBooleanMethod(jLights, gArrayListClassInfo.add, lightObj.get()); + } + + return jLights; +} + +static jint nativeGetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint lightId) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + std::optional<int32_t> ret = + im->getInputManager()->getReader()->getLightPlayerId(deviceId, lightId); + + return static_cast<jint>(ret.value_or(0)); +} + +static jint nativeGetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint lightId) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + std::optional<int32_t> ret = + im->getInputManager()->getReader()->getLightColor(deviceId, lightId); + return static_cast<jint>(ret.value_or(0)); +} + +static void nativeSetLightPlayerId(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint lightId, jint playerId) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + im->getInputManager()->getReader()->setLightPlayerId(deviceId, lightId, playerId); +} + +static void nativeSetLightColor(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId, + jint lightId, jint color) { + NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); + + im->getInputManager()->getReader()->setLightColor(deviceId, lightId, color); +} + static jint nativeGetBatteryCapacity(JNIEnv* env, jclass /* clazz */, jlong ptr, jint deviceId) { NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr); @@ -2192,6 +2279,11 @@ static const JNINativeMethod gInputManagerMethods[] = { {"nativeCancelVibrate", "(JII)V", (void*)nativeCancelVibrate}, {"nativeIsVibrating", "(JI)Z", (void*)nativeIsVibrating}, {"nativeGetVibratorIds", "(JI)[I", (void*)nativeGetVibratorIds}, + {"nativeGetLights", "(JI)Ljava/util/List;", (void*)nativeGetLights}, + {"nativeGetLightPlayerId", "(JII)I", (void*)nativeGetLightPlayerId}, + {"nativeGetLightColor", "(JII)I", (void*)nativeGetLightColor}, + {"nativeSetLightPlayerId", "(JIII)V", (void*)nativeSetLightPlayerId}, + {"nativeSetLightColor", "(JIII)V", (void*)nativeSetLightColor}, {"nativeGetBatteryCapacity", "(JI)I", (void*)nativeGetBatteryCapacity}, {"nativeGetBatteryStatus", "(JI)I", (void*)nativeGetBatteryStatus}, {"nativeReloadKeyboardLayouts", "(J)V", (void*)nativeReloadKeyboardLayouts}, @@ -2386,6 +2478,27 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gTouchCalibrationClassInfo.getAffineTransform, gTouchCalibrationClassInfo.clazz, "getAffineTransform", "()[F"); + // Light + FIND_CLASS(gLightClassInfo.clazz, "android/hardware/lights/Light"); + gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz)); + GET_METHOD_ID(gLightClassInfo.constructor, gLightClassInfo.clazz, "<init>", + "(IIILjava/lang/String;)V"); + + gLightClassInfo.clazz = jclass(env->NewGlobalRef(gLightClassInfo.clazz)); + gLightClassInfo.lightTypeSingle = + env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT_SINGLE", "I"); + gLightClassInfo.lightTypePlayerId = + env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT_PLAYER_ID", "I"); + gLightClassInfo.lightTypeRgb = + env->GetStaticFieldID(gLightClassInfo.clazz, "LIGHT_TYPE_INPUT_RGB", "I"); + + // ArrayList + FIND_CLASS(gArrayListClassInfo.clazz, "java/util/ArrayList"); + gArrayListClassInfo.clazz = jclass(env->NewGlobalRef(gArrayListClassInfo.clazz)); + GET_METHOD_ID(gArrayListClassInfo.constructor, gArrayListClassInfo.clazz, "<init>", "()V"); + GET_METHOD_ID(gArrayListClassInfo.add, gArrayListClassInfo.clazz, "add", + "(Ljava/lang/Object;)Z"); + // SparseArray FIND_CLASS(gSparseArrayClassInfo.clazz, "android/util/SparseArray"); gSparseArrayClassInfo.clazz = jclass(env->NewGlobalRef(gSparseArrayClassInfo.clazz)); diff --git a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java index 3e9709d55268..f0a9a0089ec9 100644 --- a/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/lights/LightsServiceTest.java @@ -31,6 +31,7 @@ import android.hardware.light.ILights; import android.hardware.lights.Light; import android.hardware.lights.LightState; import android.hardware.lights.LightsManager; +import android.hardware.lights.SystemLightsManager; import android.os.Looper; import androidx.test.filters.SmallTest; @@ -93,7 +94,7 @@ public class LightsServiceTest { @Test public void testGetLights_filtersSystemLights() { LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper()); - LightsManager manager = new LightsManager(mContext, service.mManagerService); + LightsManager manager = new SystemLightsManager(mContext, service.mManagerService); // When lights are listed, only the 4 MICROPHONE lights should be visible. assertThat(manager.getLights().size()).isEqualTo(4); @@ -102,14 +103,14 @@ public class LightsServiceTest { @Test public void testControlMultipleLights() { LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper()); - LightsManager manager = new LightsManager(mContext, service.mManagerService); + LightsManager manager = new SystemLightsManager(mContext, service.mManagerService); // When the session requests to turn 3/4 lights on: LightsManager.LightsSession session = manager.openSession(); session.requestLights(new Builder() - .setLight(manager.getLights().get(0), new LightState(0xf1)) - .setLight(manager.getLights().get(1), new LightState(0xf2)) - .setLight(manager.getLights().get(2), new LightState(0xf3)) + .addLight(manager.getLights().get(0), new LightState(0xf1)) + .addLight(manager.getLights().get(1), new LightState(0xf2)) + .addLight(manager.getLights().get(2), new LightState(0xf3)) .build()); // Then all 3 should turn on. @@ -122,9 +123,9 @@ public class LightsServiceTest { } @Test - public void testControlLights_onlyEffectiveForLifetimeOfClient() { + public void testControlLights_onlyEffectiveForLifetimeOfClient() throws Exception { LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper()); - LightsManager manager = new LightsManager(mContext, service.mManagerService); + LightsManager manager = new SystemLightsManager(mContext, service.mManagerService); Light micLight = manager.getLights().get(0); // The light should begin by being off. @@ -132,38 +133,41 @@ public class LightsServiceTest { // When a session commits changes: LightsManager.LightsSession session = manager.openSession(); - session.requestLights(new Builder().setLight(micLight, new LightState(GREEN)).build()); + session.requestLights(new Builder().addLight(micLight, new LightState(GREEN)).build()); // Then the light should turn on. assertThat(manager.getLightState(micLight).getColor()).isEqualTo(GREEN); // When the session goes away: session.close(); + // Then the light should turn off. assertThat(manager.getLightState(micLight).getColor()).isEqualTo(TRANSPARENT); } @Test - public void testControlLights_firstCallerWinsContention() { + public void testControlLights_firstCallerWinsContention() throws Exception { LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper()); - LightsManager manager = new LightsManager(mContext, service.mManagerService); + LightsManager manager = new SystemLightsManager(mContext, service.mManagerService); Light micLight = manager.getLights().get(0); LightsManager.LightsSession session1 = manager.openSession(); LightsManager.LightsSession session2 = manager.openSession(); // When session1 and session2 both request the same light: - session1.requestLights(new Builder().setLight(micLight, new LightState(BLUE)).build()); - session2.requestLights(new Builder().setLight(micLight, new LightState(WHITE)).build()); + session1.requestLights(new Builder().addLight(micLight, new LightState(BLUE)).build()); + session2.requestLights(new Builder().addLight(micLight, new LightState(WHITE)).build()); // Then session1 should win because it was created first. assertThat(manager.getLightState(micLight).getColor()).isEqualTo(BLUE); // When session1 goes away: session1.close(); + // Then session2 should have its request go into effect. assertThat(manager.getLightState(micLight).getColor()).isEqualTo(WHITE); // When session2 goes away: session2.close(); + // Then the light should turn off because there are no more sessions. assertThat(manager.getLightState(micLight).getColor()).isEqualTo(0); } @@ -171,12 +175,12 @@ public class LightsServiceTest { @Test public void testClearLight() { LightsService service = new LightsService(mContext, () -> mHal, Looper.getMainLooper()); - LightsManager manager = new LightsManager(mContext, service.mManagerService); + LightsManager manager = new SystemLightsManager(mContext, service.mManagerService); Light micLight = manager.getLights().get(0); // When the session turns a light on: LightsManager.LightsSession session = manager.openSession(); - session.requestLights(new Builder().setLight(micLight, new LightState(WHITE)).build()); + session.requestLights(new Builder().addLight(micLight, new LightState(WHITE)).build()); // And then the session clears it again: session.requestLights(new Builder().clearLight(micLight).build()); |