diff options
37 files changed, 1179 insertions, 113 deletions
diff --git a/api/test-current.txt b/api/test-current.txt index ed4c9b13dacd..66b5015902f0 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -1298,6 +1298,130 @@ package android.hardware.display { } +package android.hardware.hdmi { + + public final class HdmiControlManager { + method @Nullable public android.hardware.hdmi.HdmiSwitchClient getSwitchClient(); + method @RequiresPermission("android.permission.HDMI_CEC") public void setStandbyMode(boolean); + field public static final String ACTION_OSD_MESSAGE = "android.hardware.hdmi.action.OSD_MESSAGE"; + field public static final int AVR_VOLUME_MUTED = 101; // 0x65 + field public static final int CLEAR_TIMER_STATUS_CEC_DISABLE = 162; // 0xa2 + field public static final int CLEAR_TIMER_STATUS_CHECK_RECORDER_CONNECTION = 160; // 0xa0 + field public static final int CLEAR_TIMER_STATUS_FAIL_TO_CLEAR_SELECTED_SOURCE = 161; // 0xa1 + field public static final int CLEAR_TIMER_STATUS_TIMER_CLEARED = 128; // 0x80 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_INFO_AVAILABLE = 2; // 0x2 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_NO_MATCHING = 1; // 0x1 + field public static final int CLEAR_TIMER_STATUS_TIMER_NOT_CLEARED_RECORDING = 0; // 0x0 + field public static final int CONTROL_STATE_CHANGED_REASON_SETTING = 1; // 0x1 + field public static final int CONTROL_STATE_CHANGED_REASON_STANDBY = 3; // 0x3 + field public static final int CONTROL_STATE_CHANGED_REASON_START = 0; // 0x0 + field public static final int CONTROL_STATE_CHANGED_REASON_WAKEUP = 2; // 0x2 + field public static final int DEVICE_EVENT_ADD_DEVICE = 1; // 0x1 + field public static final int DEVICE_EVENT_REMOVE_DEVICE = 2; // 0x2 + field public static final int DEVICE_EVENT_UPDATE_DEVICE = 3; // 0x3 + field public static final String EXTRA_MESSAGE_EXTRA_PARAM1 = "android.hardware.hdmi.extra.MESSAGE_EXTRA_PARAM1"; + field public static final String EXTRA_MESSAGE_ID = "android.hardware.hdmi.extra.MESSAGE_ID"; + field public static final int ONE_TOUCH_RECORD_ALREADY_RECORDING = 18; // 0x12 + field public static final int ONE_TOUCH_RECORD_CEC_DISABLED = 51; // 0x33 + field public static final int ONE_TOUCH_RECORD_CHECK_RECORDER_CONNECTION = 49; // 0x31 + field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_COPY = 13; // 0xd + field public static final int ONE_TOUCH_RECORD_DISALLOW_TO_FUTHER_COPIES = 14; // 0xe + field public static final int ONE_TOUCH_RECORD_FAIL_TO_RECORD_DISPLAYED_SCREEN = 50; // 0x32 + field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PHYSICAL_ADDRESS = 10; // 0xa + field public static final int ONE_TOUCH_RECORD_INVALID_EXTERNAL_PLUG_NUMBER = 9; // 0x9 + field public static final int ONE_TOUCH_RECORD_MEDIA_PROBLEM = 21; // 0x15 + field public static final int ONE_TOUCH_RECORD_MEDIA_PROTECTED = 19; // 0x13 + field public static final int ONE_TOUCH_RECORD_NOT_ENOUGH_SPACE = 22; // 0x16 + field public static final int ONE_TOUCH_RECORD_NO_MEDIA = 16; // 0x10 + field public static final int ONE_TOUCH_RECORD_NO_OR_INSUFFICIENT_CA_ENTITLEMENTS = 12; // 0xc + field public static final int ONE_TOUCH_RECORD_NO_SOURCE_SIGNAL = 20; // 0x14 + field public static final int ONE_TOUCH_RECORD_OTHER_REASON = 31; // 0x1f + field public static final int ONE_TOUCH_RECORD_PARENT_LOCK_ON = 23; // 0x17 + field public static final int ONE_TOUCH_RECORD_PLAYING = 17; // 0x11 + field public static final int ONE_TOUCH_RECORD_PREVIOUS_RECORDING_IN_PROGRESS = 48; // 0x30 + field public static final int ONE_TOUCH_RECORD_RECORDING_ALREADY_TERMINATED = 27; // 0x1b + field public static final int ONE_TOUCH_RECORD_RECORDING_ANALOGUE_SERVICE = 3; // 0x3 + field public static final int ONE_TOUCH_RECORD_RECORDING_CURRENTLY_SELECTED_SOURCE = 1; // 0x1 + field public static final int ONE_TOUCH_RECORD_RECORDING_DIGITAL_SERVICE = 2; // 0x2 + field public static final int ONE_TOUCH_RECORD_RECORDING_EXTERNAL_INPUT = 4; // 0x4 + field public static final int ONE_TOUCH_RECORD_RECORDING_TERMINATED_NORMALLY = 26; // 0x1a + field public static final int ONE_TOUCH_RECORD_UNABLE_ANALOGUE_SERVICE = 6; // 0x6 + field public static final int ONE_TOUCH_RECORD_UNABLE_DIGITAL_SERVICE = 5; // 0x5 + field public static final int ONE_TOUCH_RECORD_UNABLE_SELECTED_SERVICE = 7; // 0x7 + field public static final int ONE_TOUCH_RECORD_UNSUPPORTED_CA = 11; // 0xb + field public static final int OSD_MESSAGE_ARC_CONNECTED_INVALID_PORT = 1; // 0x1 + field public static final int OSD_MESSAGE_AVR_VOLUME_CHANGED = 2; // 0x2 + field public static final int POWER_STATUS_ON = 0; // 0x0 + field public static final int POWER_STATUS_STANDBY = 1; // 0x1 + field public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; // 0x2 + field public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; // 0x3 + field public static final int POWER_STATUS_UNKNOWN = -1; // 0xffffffff + field @Deprecated public static final int RESULT_ALREADY_IN_PROGRESS = 4; // 0x4 + field public static final int RESULT_COMMUNICATION_FAILED = 7; // 0x7 + field public static final int RESULT_EXCEPTION = 5; // 0x5 + field public static final int RESULT_INCORRECT_MODE = 6; // 0x6 + field public static final int RESULT_SOURCE_NOT_AVAILABLE = 2; // 0x2 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public static final int RESULT_TARGET_NOT_AVAILABLE = 3; // 0x3 + field public static final int RESULT_TIMEOUT = 1; // 0x1 + field public static final int TIMER_RECORDING_RESULT_EXTRA_CEC_DISABLED = 3; // 0x3 + field public static final int TIMER_RECORDING_RESULT_EXTRA_CHECK_RECORDER_CONNECTION = 1; // 0x1 + field public static final int TIMER_RECORDING_RESULT_EXTRA_FAIL_TO_RECORD_SELECTED_SOURCE = 2; // 0x2 + field public static final int TIMER_RECORDING_RESULT_EXTRA_NO_ERROR = 0; // 0x0 + field public static final int TIMER_RECORDING_TYPE_ANALOGUE = 2; // 0x2 + field public static final int TIMER_RECORDING_TYPE_DIGITAL = 1; // 0x1 + field public static final int TIMER_RECORDING_TYPE_EXTERNAL = 3; // 0x3 + field public static final int TIMER_STATUS_MEDIA_INFO_NOT_PRESENT = 2; // 0x2 + field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_NOT_PROTECTED = 0; // 0x0 + field public static final int TIMER_STATUS_MEDIA_INFO_PRESENT_PROTECTED = 1; // 0x1 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_CA_NOT_SUPPORTED = 6; // 0x6 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_CLOCK_FAILURE = 10; // 0xa + field public static final int TIMER_STATUS_NOT_PROGRAMMED_DATE_OUT_OF_RANGE = 2; // 0x2 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_DUPLICATED = 14; // 0xe + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PHYSICAL_NUMBER = 5; // 0x5 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_EXTERNAL_PLUG_NUMBER = 4; // 0x4 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_INVALID_SEQUENCE = 3; // 0x3 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_CA_ENTITLEMENTS = 7; // 0x7 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_NO_FREE_TIME = 1; // 0x1 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_PARENTAL_LOCK_ON = 9; // 0x9 + field public static final int TIMER_STATUS_NOT_PROGRAMMED_UNSUPPORTED_RESOLUTION = 8; // 0x8 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_ENOUGH_SPACE = 8; // 0x8 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_MIGHT_NOT_ENOUGH_SPACE = 11; // 0xb + field public static final int TIMER_STATUS_PROGRAMMED_INFO_NOT_ENOUGH_SPACE = 9; // 0x9 + field public static final int TIMER_STATUS_PROGRAMMED_INFO_NO_MEDIA_INFO = 10; // 0xa + } + + public final class HdmiControlServiceWrapper { + ctor public HdmiControlServiceWrapper(); + method @NonNull public android.hardware.hdmi.HdmiControlManager createHdmiControlManager(); + method @BinderThread public void setDeviceTypes(@NonNull int[]); + method @BinderThread public void setPortInfo(@NonNull java.util.List<android.hardware.hdmi.HdmiPortInfo>); + field public static final int DEVICE_PURE_CEC_SWITCH = 6; // 0x6 + } + + public final class HdmiPortInfo implements android.os.Parcelable { + ctor public HdmiPortInfo(int, int, int, boolean, boolean, boolean); + method public int describeContents(); + method public int getAddress(); + method public int getId(); + method public int getType(); + method public boolean isArcSupported(); + method public boolean isCecSupported(); + method public boolean isMhlSupported(); + field @NonNull public static final android.os.Parcelable.Creator<android.hardware.hdmi.HdmiPortInfo> CREATOR; + field public static final int PORT_INPUT = 0; // 0x0 + field public static final int PORT_OUTPUT = 1; // 0x1 + } + + public class HdmiSwitchClient { + method public int getDeviceType(); + method @NonNull public java.util.List<android.hardware.hdmi.HdmiPortInfo> getPortInfo(); + method public void sendKeyEvent(int, boolean); + method public void sendVendorCommand(int, byte[], boolean); + } + +} + package android.hardware.lights { public final class Light implements android.os.Parcelable { diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h index 3de5b99a2b09..505b23921806 100644 --- a/cmds/statsd/src/metrics/ValueMetricProducer.h +++ b/cmds/statsd/src/metrics/ValueMetricProducer.h @@ -75,7 +75,7 @@ public: if (!mSplitBucketForAppUpgrade) { return; } - if (mIsPulled && mCondition) { + if (mIsPulled && mCondition == ConditionState::kTrue) { pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); @@ -84,7 +84,7 @@ public: // ValueMetric needs special logic if it's a pulled atom. void onStatsdInitCompleted(const int64_t& eventTimeNs) override { std::lock_guard<std::mutex> lock(mMutex); - if (mIsPulled && mCondition) { + if (mIsPulled && mCondition == ConditionState::kTrue) { pullAndMatchEventsLocked(eventTimeNs); } flushCurrentBucketLocked(eventTimeNs, eventTimeNs); diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp index 1bcc35d99d18..58a3259bbf2c 100644 --- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp +++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp @@ -4722,6 +4722,46 @@ TEST(ValueMetricProducerTest, TestSlicedStateWithPrimaryField_WithDimensions) { EXPECT_EQ(5, report.value_metrics().data(4).bucket_info(1).values(0).value_long()); } +/* + * Test bucket splits when condition is unknown. + */ +TEST(ValueMetricProducerTest, TestForcedBucketSplitWhenConditionUnknownSkipsBucket) { + ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition(); + + sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>(); + + sp<ValueMetricProducer> valueProducer = + ValueMetricProducerTestHelper::createValueProducerWithCondition( + pullerManager, metric, + ConditionState::kUnknown); + + // App update event. + int64_t appUpdateTimeNs = bucketStartTimeNs + 1000; + valueProducer->notifyAppUpgrade(appUpdateTimeNs); + + // Check dump report. + ProtoOutputStream output; + std::set<string> strSet; + int64_t dumpReportTimeNs = bucketStartTimeNs + 10000000000; // 10 seconds + valueProducer->onDumpReport(dumpReportTimeNs, false /* include current buckets */, true, + NO_TIME_CONSTRAINTS /* dumpLatency */, &strSet, &output); + + StatsLogReport report = outputStreamToProto(&output); + EXPECT_TRUE(report.has_value_metrics()); + ASSERT_EQ(0, report.value_metrics().data_size()); + ASSERT_EQ(1, report.value_metrics().skipped_size()); + + EXPECT_EQ(NanoToMillis(bucketStartTimeNs), + report.value_metrics().skipped(0).start_bucket_elapsed_millis()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), + report.value_metrics().skipped(0).end_bucket_elapsed_millis()); + ASSERT_EQ(1, report.value_metrics().skipped(0).drop_event_size()); + + auto dropEvent = report.value_metrics().skipped(0).drop_event(0); + EXPECT_EQ(BucketDropReason::NO_DATA, dropEvent.drop_reason()); + EXPECT_EQ(NanoToMillis(appUpdateTimeNs), dropEvent.drop_time_millis()); +} + } // namespace statsd } // namespace os } // namespace android diff --git a/core/java/android/hardware/hdmi/HdmiClient.java b/core/java/android/hardware/hdmi/HdmiClient.java index bff8c39e8c56..a921215c6133 100644 --- a/core/java/android/hardware/hdmi/HdmiClient.java +++ b/core/java/android/hardware/hdmi/HdmiClient.java @@ -1,6 +1,7 @@ package android.hardware.hdmi; import android.annotation.NonNull; +import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.hardware.hdmi.HdmiControlManager.VendorCommandListener; import android.os.RemoteException; @@ -84,7 +85,8 @@ public abstract class HdmiClient { * @param hasVendorId {@code true} if the command type will be <Vendor Command With ID>. * {@code false} if the command will be <Vendor Command> */ - public void sendVendorCommand(int targetAddress, byte[] params, boolean hasVendorId) { + public void sendVendorCommand(int targetAddress, + @SuppressLint("MissingNullability") byte[] params, boolean hasVendorId) { try { mService.sendVendorCommand(getDeviceType(), targetAddress, params, hasVendorId); } catch (RemoteException e) { diff --git a/core/java/android/hardware/hdmi/HdmiControlManager.java b/core/java/android/hardware/hdmi/HdmiControlManager.java index 208406566e52..1ce9b9c71c0e 100644 --- a/core/java/android/hardware/hdmi/HdmiControlManager.java +++ b/core/java/android/hardware/hdmi/HdmiControlManager.java @@ -29,6 +29,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; +import android.annotation.TestApi; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; @@ -57,6 +58,7 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi +@TestApi @SystemService(Context.HDMI_CONTROL_SERVICE) @RequiresFeature(PackageManager.FEATURE_HDMI_CEC) public final class HdmiControlManager { @@ -139,6 +141,8 @@ public final class HdmiControlManager { public static final int POWER_STATUS_TRANSIENT_TO_ON = 2; public static final int POWER_STATUS_TRANSIENT_TO_STANDBY = 3; + /** @hide */ + @SystemApi @IntDef ({ RESULT_SUCCESS, RESULT_TIMEOUT, @@ -400,8 +404,11 @@ public final class HdmiControlManager { * See {@link HdmiDeviceInfo#DEVICE_PLAYBACK} * See {@link HdmiDeviceInfo#DEVICE_TV} * See {@link HdmiDeviceInfo#DEVICE_AUDIO_SYSTEM} + * + * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiClient getClient(int type) { if (mService == null) { @@ -430,8 +437,11 @@ public final class HdmiControlManager { * system if the system is configured to host more than one type of HDMI-CEC logical devices. * * @return {@link HdmiPlaybackClient} instance. {@code null} on failure. + * + * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiPlaybackClient getPlaybackClient() { return (HdmiPlaybackClient) getClient(HdmiDeviceInfo.DEVICE_PLAYBACK); @@ -445,8 +455,11 @@ public final class HdmiControlManager { * system if the system is configured to host more than one type of HDMI-CEC logical devices. * * @return {@link HdmiTvClient} instance. {@code null} on failure. + * + * @hide */ @Nullable + @SystemApi @SuppressLint("Doclava125") public HdmiTvClient getTvClient() { return (HdmiTvClient) getClient(HdmiDeviceInfo.DEVICE_TV); @@ -478,10 +491,8 @@ public final class HdmiControlManager { * system if the system is configured to host more than one type of HDMI-CEC logical device. * * @return {@link HdmiSwitchClient} instance. {@code null} on failure. - * @hide */ @Nullable - @SystemApi @SuppressLint("Doclava125") public HdmiSwitchClient getSwitchClient() { return (HdmiSwitchClient) getClient(HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH); @@ -790,7 +801,10 @@ public final class HdmiControlManager { /** * Listener used to get hotplug event from HDMI port. + * + * @hide */ + @SystemApi public interface HotplugEventListener { void onReceived(HdmiHotplugEvent event); } @@ -840,7 +854,10 @@ public final class HdmiControlManager { /** * Listener used to get vendor-specific commands. + * + * @hide */ + @SystemApi public interface VendorCommandListener { /** * Called when a vendor command is received. @@ -879,7 +896,10 @@ public final class HdmiControlManager { * * @param listener {@link HotplugEventListener} instance * @see HdmiControlManager#removeHotplugEventListener(HotplugEventListener) + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void addHotplugEventListener(HotplugEventListener listener) { if (mService == null) { @@ -903,7 +923,10 @@ public final class HdmiControlManager { * Removes a listener to stop getting informed of {@link HdmiHotplugEvent}. * * @param listener {@link HotplugEventListener} instance to be removed + * + * @hide */ + @SystemApi @RequiresPermission(android.Manifest.permission.HDMI_CEC) public void removeHotplugEventListener(HotplugEventListener listener) { if (mService == null) { diff --git a/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java new file mode 100644 index 000000000000..02896351ea3a --- /dev/null +++ b/core/java/android/hardware/hdmi/HdmiControlServiceWrapper.java @@ -0,0 +1,469 @@ +/* + * Copyright 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.hdmi; + +import android.annotation.BinderThread; +import android.annotation.NonNull; +import android.annotation.TestApi; + +import java.util.List; + +/** + * A wrapper of the Binder interface that clients running in the application process + * will use to perform HDMI-CEC features by communicating with other devices + * on the bus. + * + * @hide + */ +@TestApi +public final class HdmiControlServiceWrapper { + + /** Pure CEC switch device type. */ + public static final int DEVICE_PURE_CEC_SWITCH = HdmiDeviceInfo.DEVICE_PURE_CEC_SWITCH; + + private List<HdmiPortInfo> mInfoList = null; + private int[] mTypes = null; + + /** + * Create a new HdmiControlManager with the current HdmiControlService wrapper + * + * @return the created HdmiControlManager + */ + @NonNull + public HdmiControlManager createHdmiControlManager() { + return new HdmiControlManager(mInterface); + } + + private final IHdmiControlService mInterface = new IHdmiControlService.Stub() { + + @Override + public int[] getSupportedTypes() { + return HdmiControlServiceWrapper.this.getSupportedTypes(); + } + + @Override + public HdmiDeviceInfo getActiveSource() { + return HdmiControlServiceWrapper.this.getActiveSource(); + } + + @Override + public void oneTouchPlay(IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.oneTouchPlay(callback); + } + + @Override + public void queryDisplayStatus(IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.queryDisplayStatus(callback); + } + + @Override + public void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) { + HdmiControlServiceWrapper.this.addHdmiControlStatusChangeListener(listener); + } + + @Override + public void removeHdmiControlStatusChangeListener( + IHdmiControlStatusChangeListener listener) { + HdmiControlServiceWrapper.this.removeHdmiControlStatusChangeListener(listener); + } + + @Override + public void addHotplugEventListener(IHdmiHotplugEventListener listener) { + HdmiControlServiceWrapper.this.addHotplugEventListener(listener); + } + + @Override + public void removeHotplugEventListener(IHdmiHotplugEventListener listener) { + HdmiControlServiceWrapper.this.removeHotplugEventListener(listener); + } + + @Override + public void addDeviceEventListener(IHdmiDeviceEventListener listener) { + HdmiControlServiceWrapper.this.addDeviceEventListener(listener); + } + + @Override + public void deviceSelect(int deviceId, IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.deviceSelect(deviceId, callback); + } + + @Override + public void portSelect(int portId, IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.portSelect(portId, callback); + } + + @Override + public void sendKeyEvent(int deviceType, int keyCode, boolean isPressed) { + HdmiControlServiceWrapper.this.sendKeyEvent(deviceType, keyCode, isPressed); + } + + @Override + public void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed) { + HdmiControlServiceWrapper.this.sendVolumeKeyEvent(deviceType, keyCode, isPressed); + } + + @Override + public List<HdmiPortInfo> getPortInfo() { + return HdmiControlServiceWrapper.this.getPortInfo(); + } + + @Override + public boolean canChangeSystemAudioMode() { + return HdmiControlServiceWrapper.this.canChangeSystemAudioMode(); + } + + @Override + public boolean getSystemAudioMode() { + return HdmiControlServiceWrapper.this.getSystemAudioMode(); + } + + @Override + public int getPhysicalAddress() { + return HdmiControlServiceWrapper.this.getPhysicalAddress(); + } + + @Override + public void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback) { + HdmiControlServiceWrapper.this.setSystemAudioMode(enabled, callback); + } + + @Override + public void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) { + HdmiControlServiceWrapper.this.addSystemAudioModeChangeListener(listener); + } + + @Override + public void removeSystemAudioModeChangeListener( + IHdmiSystemAudioModeChangeListener listener) { + HdmiControlServiceWrapper.this.removeSystemAudioModeChangeListener(listener); + } + + @Override + public void setArcMode(boolean enabled) { + HdmiControlServiceWrapper.this.setArcMode(enabled); + } + + @Override + public void setProhibitMode(boolean enabled) { + HdmiControlServiceWrapper.this.setProhibitMode(enabled); + } + + @Override + public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) { + HdmiControlServiceWrapper.this.setSystemAudioVolume(oldIndex, newIndex, maxIndex); + } + + @Override + public void setSystemAudioMute(boolean mute) { + HdmiControlServiceWrapper.this.setSystemAudioMute(mute); + } + + @Override + public void setInputChangeListener(IHdmiInputChangeListener listener) { + HdmiControlServiceWrapper.this.setInputChangeListener(listener); + } + + @Override + public List<HdmiDeviceInfo> getInputDevices() { + return HdmiControlServiceWrapper.this.getInputDevices(); + } + + @Override + public List<HdmiDeviceInfo> getDeviceList() { + return HdmiControlServiceWrapper.this.getDeviceList(); + } + + @Override + public void powerOffRemoteDevice(int logicalAddress, int powerStatus) { + HdmiControlServiceWrapper.this.powerOffRemoteDevice(logicalAddress, powerStatus); + } + + @Override + public void powerOnRemoteDevice(int logicalAddress, int powerStatus) { + HdmiControlServiceWrapper.this.powerOnRemoteDevice(logicalAddress, powerStatus); + } + + @Override + public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) { + HdmiControlServiceWrapper.this.askRemoteDeviceToBecomeActiveSource(physicalAddress); + } + + @Override + public void sendVendorCommand(int deviceType, int targetAddress, byte[] params, + boolean hasVendorId) { + HdmiControlServiceWrapper.this.sendVendorCommand( + deviceType, targetAddress, params, hasVendorId); + } + + @Override + public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) { + HdmiControlServiceWrapper.this.addVendorCommandListener(listener, deviceType); + } + + @Override + public void sendStandby(int deviceType, int deviceId) { + HdmiControlServiceWrapper.this.sendStandby(deviceType, deviceId); + } + + @Override + public void setHdmiRecordListener(IHdmiRecordListener callback) { + HdmiControlServiceWrapper.this.setHdmiRecordListener(callback); + } + + @Override + public void startOneTouchRecord(int recorderAddress, byte[] recordSource) { + HdmiControlServiceWrapper.this.startOneTouchRecord(recorderAddress, recordSource); + } + + @Override + public void stopOneTouchRecord(int recorderAddress) { + HdmiControlServiceWrapper.this.stopOneTouchRecord(recorderAddress); + } + + @Override + public void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { + HdmiControlServiceWrapper.this.startTimerRecording( + recorderAddress, sourceType, recordSource); + } + + @Override + public void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) { + HdmiControlServiceWrapper.this.clearTimerRecording( + recorderAddress, sourceType, recordSource); + } + + @Override + public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) { + HdmiControlServiceWrapper.this.sendMhlVendorCommand(portId, offset, length, data); + } + + @Override + public void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) { + HdmiControlServiceWrapper.this.addHdmiMhlVendorCommandListener(listener); + } + + @Override + public void setStandbyMode(boolean isStandbyModeOn) { + HdmiControlServiceWrapper.this.setStandbyMode(isStandbyModeOn); + } + + @Override + public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) { + HdmiControlServiceWrapper.this.setHdmiCecVolumeControlEnabled( + isHdmiCecVolumeControlEnabled); + } + + @Override + public boolean isHdmiCecVolumeControlEnabled() { + return HdmiControlServiceWrapper.this.isHdmiCecVolumeControlEnabled(); + } + + @Override + public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) { + HdmiControlServiceWrapper.this.reportAudioStatus(deviceType, volume, maxVolume, isMute); + } + + @Override + public void setSystemAudioModeOnForAudioOnlySource() { + HdmiControlServiceWrapper.this.setSystemAudioModeOnForAudioOnlySource(); + } + + @Override + public void addHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + HdmiControlServiceWrapper.this.addHdmiCecVolumeControlFeatureListener(listener); + } + + @Override + public void removeHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) { + HdmiControlServiceWrapper.this.removeHdmiCecVolumeControlFeatureListener(listener); + } + }; + + @BinderThread + public void setPortInfo(@NonNull List<HdmiPortInfo> infoList) { + mInfoList = infoList; + } + + @BinderThread + public void setDeviceTypes(@NonNull int[] types) { + mTypes = types; + } + + /** @hide */ + public List<HdmiPortInfo> getPortInfo() { + return mInfoList; + } + + /** @hide */ + public int[] getSupportedTypes() { + return mTypes; + } + + /** @hide */ + public HdmiDeviceInfo getActiveSource() { + return null; + } + + /** @hide */ + public void oneTouchPlay(IHdmiControlCallback callback) {} + + /** @hide */ + public void queryDisplayStatus(IHdmiControlCallback callback) {} + + /** @hide */ + public void addHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {} + + /** @hide */ + public void removeHdmiControlStatusChangeListener(IHdmiControlStatusChangeListener listener) {} + + /** @hide */ + public void addHotplugEventListener(IHdmiHotplugEventListener listener) {} + + /** @hide */ + public void removeHotplugEventListener(IHdmiHotplugEventListener listener) {} + + /** @hide */ + public void addDeviceEventListener(IHdmiDeviceEventListener listener) {} + + /** @hide */ + public void deviceSelect(int deviceId, IHdmiControlCallback callback) {} + + /** @hide */ + public void portSelect(int portId, IHdmiControlCallback callback) {} + + /** @hide */ + public void sendKeyEvent(int deviceType, int keyCode, boolean isPressed) {} + + /** @hide */ + public void sendVolumeKeyEvent(int deviceType, int keyCode, boolean isPressed) {} + + /** @hide */ + public boolean canChangeSystemAudioMode() { + return true; + } + + /** @hide */ + public boolean getSystemAudioMode() { + return true; + } + + /** @hide */ + public int getPhysicalAddress() { + return 0xffff; + } + + /** @hide */ + public void setSystemAudioMode(boolean enabled, IHdmiControlCallback callback) {} + + /** @hide */ + public void addSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {} + + /** @hide */ + public void removeSystemAudioModeChangeListener(IHdmiSystemAudioModeChangeListener listener) {} + + /** @hide */ + public void setArcMode(boolean enabled) {} + + /** @hide */ + public void setProhibitMode(boolean enabled) {} + + /** @hide */ + public void setSystemAudioVolume(int oldIndex, int newIndex, int maxIndex) {} + + /** @hide */ + public void setSystemAudioMute(boolean mute) {} + + /** @hide */ + public void setInputChangeListener(IHdmiInputChangeListener listener) {} + + /** @hide */ + public List<HdmiDeviceInfo> getInputDevices() { + return null; + } + + /** @hide */ + public List<HdmiDeviceInfo> getDeviceList() { + return null; + } + + /** @hide */ + public void powerOffRemoteDevice(int logicalAddress, int powerStatus) {} + + /** @hide */ + public void powerOnRemoteDevice(int logicalAddress, int powerStatus) {} + + /** @hide */ + public void askRemoteDeviceToBecomeActiveSource(int physicalAddress) {} + + /** @hide */ + public void sendVendorCommand(int deviceType, int targetAddress, byte[] params, + boolean hasVendorId) {} + + /** @hide */ + public void addVendorCommandListener(IHdmiVendorCommandListener listener, int deviceType) {} + + /** @hide */ + public void sendStandby(int deviceType, int deviceId) {} + + /** @hide */ + public void setHdmiRecordListener(IHdmiRecordListener callback) {} + + /** @hide */ + public void startOneTouchRecord(int recorderAddress, byte[] recordSource) {} + + /** @hide */ + public void stopOneTouchRecord(int recorderAddress) {} + + /** @hide */ + public void startTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {} + + /** @hide */ + public void clearTimerRecording(int recorderAddress, int sourceType, byte[] recordSource) {} + + /** @hide */ + public void sendMhlVendorCommand(int portId, int offset, int length, byte[] data) {} + + /** @hide */ + public void addHdmiMhlVendorCommandListener(IHdmiMhlVendorCommandListener listener) {} + + /** @hide */ + public void setStandbyMode(boolean isStandbyModeOn) {} + + /** @hide */ + public void setHdmiCecVolumeControlEnabled(boolean isHdmiCecVolumeControlEnabled) {} + + /** @hide */ + public boolean isHdmiCecVolumeControlEnabled() { + return true; + } + + /** @hide */ + public void reportAudioStatus(int deviceType, int volume, int maxVolume, boolean isMute) {} + + /** @hide */ + public void setSystemAudioModeOnForAudioOnlySource() {} + + /** @hide */ + public void addHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) {} + + /** @hide */ + public void removeHdmiCecVolumeControlFeatureListener( + IHdmiCecVolumeControlFeatureListener listener) {} +} diff --git a/core/java/android/hardware/hdmi/HdmiPortInfo.java b/core/java/android/hardware/hdmi/HdmiPortInfo.java index 2623458aff5b..52c3628f358b 100644 --- a/core/java/android/hardware/hdmi/HdmiPortInfo.java +++ b/core/java/android/hardware/hdmi/HdmiPortInfo.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -28,6 +29,7 @@ import android.os.Parcelable; * @hide */ @SystemApi +@TestApi public final class HdmiPortInfo implements Parcelable { /** HDMI port type: Input */ public static final int PORT_INPUT = 0; @@ -153,7 +155,9 @@ public final class HdmiPortInfo implements Parcelable { * @param dest The Parcel in which the object should be written. * @param flags Additional flags about how the object should be written. * May be 0 or {@link Parcelable#PARCELABLE_WRITE_RETURN_VALUE}. + * @hide */ + @SystemApi @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mId); @@ -187,4 +191,9 @@ public final class HdmiPortInfo implements Parcelable { && mCecSupported == other.mCecSupported && mArcSupported == other.mArcSupported && mMhlSupported == other.mMhlSupported; } + + @Override + public int hashCode() { + return mId; + } } diff --git a/core/java/android/hardware/hdmi/HdmiSwitchClient.java b/core/java/android/hardware/hdmi/HdmiSwitchClient.java index 7833653e41b0..913edfd0ebf4 100644 --- a/core/java/android/hardware/hdmi/HdmiSwitchClient.java +++ b/core/java/android/hardware/hdmi/HdmiSwitchClient.java @@ -18,6 +18,7 @@ package android.hardware.hdmi; import android.annotation.CallbackExecutor; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.hardware.hdmi.HdmiControlManager.ControlCallbackResult; import android.os.Binder; import android.os.RemoteException; @@ -38,6 +39,7 @@ import java.util.concurrent.Executor; * @hide */ @SystemApi +@TestApi public class HdmiSwitchClient extends HdmiClient { private static final String TAG = "HdmiSwitchClient"; @@ -187,11 +189,8 @@ public class HdmiSwitchClient extends HdmiClient { * <p>This returns an empty list when the current device does not have HDMI input. * * @return a list of {@link HdmiPortInfo} - * - * @hide */ @NonNull - @SystemApi public List<HdmiPortInfo> getPortInfo() { try { return mService.getPortInfo(); diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index b0fca006d1e2..d7ca63a54987 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -260,4 +260,10 @@ public abstract class AbstractInputMethodService extends Service */ public void notifyUserActionIfNecessary() { } + + /** @hide */ + @Override + public final boolean isUiContext() { + return true; + } } diff --git a/core/java/android/view/GestureDetector.java b/core/java/android/view/GestureDetector.java index f6c72c4eefbc..55c527ba6fa6 100644 --- a/core/java/android/view/GestureDetector.java +++ b/core/java/android/view/GestureDetector.java @@ -16,6 +16,8 @@ package android.view; +import static android.os.StrictMode.vmIncorrectContextUseEnabled; + import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DOUBLE_TAP; import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS; @@ -26,9 +28,12 @@ import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFI import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.os.Build; +import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.os.StrictMode; import android.os.SystemClock; +import android.util.Log; import com.android.internal.util.FrameworkStatsLog; @@ -228,6 +233,7 @@ public class GestureDetector { } } + private static final String TAG = GestureDetector.class.getSimpleName(); @UnsupportedAppUsage private int mTouchSlopSquare; private int mDoubleTapTouchSlopSquare; @@ -378,7 +384,8 @@ public class GestureDetector { * You may only use this constructor from a {@link android.os.Looper} thread. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or * {@link OnContextClickListener} then it will also be set as the listener for @@ -395,7 +402,8 @@ public class GestureDetector { * thread associated with the supplied {@link android.os.Handler}. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. If the listener implements the {@link OnDoubleTapListener} or * {@link OnContextClickListener} then it will also be set as the listener for @@ -425,7 +433,8 @@ public class GestureDetector { * thread associated with the supplied {@link android.os.Handler}. * @see android.os.Handler#Handler() * - * @param context the application's context + * @param context An {@link android.app.Activity} or a {@link Context} created from + * {@link Context#createWindowContext(int, Bundle)} * @param listener the listener invoked for all the callbacks, this must * not be null. * @param handler the handler to use for running deferred listener events. @@ -456,6 +465,17 @@ public class GestureDetector { mMaximumFlingVelocity = ViewConfiguration.getMaximumFlingVelocity(); mAmbiguousGestureMultiplier = ViewConfiguration.getAmbiguousGestureMultiplier(); } else { + if (!context.isUiContext() && vmIncorrectContextUseEnabled()) { + final String errorMessage = + "Tried to access UI constants from a non-visual Context."; + final String message = "GestureDetector must be accessed from Activity or other " + + "visual Context. Use an Activity or a Context created with " + + "Context#createWindowContext(int, Bundle), which are adjusted to the " + + "configuration and visual bounds of an area on screen."; + final Exception exception = new IllegalArgumentException(errorMessage); + StrictMode.onIncorrectContextUsed(message, exception); + Log.e(TAG, errorMessage + message, exception); + } final ViewConfiguration configuration = ViewConfiguration.get(context); touchSlop = configuration.getScaledTouchSlop(); doubleTapTouchSlop = configuration.getScaledDoubleTapTouchSlop(); diff --git a/core/res/res/layout/notification_material_action_list.xml b/core/res/res/layout/notification_material_action_list.xml index ec54091e5a20..3615b9e2f9cb 100644 --- a/core/res/res/layout/notification_material_action_list.xml +++ b/core/res/res/layout/notification_material_action_list.xml @@ -22,10 +22,11 @@ android:layout_gravity="bottom"> <LinearLayout + android:id="@+id/actions_container_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingEnd="12dp" + android:paddingEnd="@dimen/bubble_gone_padding_end" > <com.android.internal.widget.NotificationActionListLayout diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index ad3d20ee5f24..59bb052cbdf5 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -312,6 +312,12 @@ <!-- The margin of the content to an image--> <dimen name="notification_content_image_margin_end">8dp</dimen> + <!-- The padding at the end of actions when the bubble button is visible--> + <dimen name="bubble_visible_padding_end">3dp</dimen> + + <!-- The padding at the end of actions when the bubble button is gone--> + <dimen name="bubble_gone_padding_end">12dp</dimen> + <!-- The spacing between messages in Notification.MessagingStyle --> <dimen name="notification_messaging_spacing">6dp</dimen> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 53cdeb5e5c1c..f3345b015c87 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2766,6 +2766,7 @@ <java-symbol type="bool" name="config_mainBuiltInDisplayIsRound" /> <java-symbol type="id" name="actions_container" /> + <java-symbol type="id" name="actions_container_layout" /> <java-symbol type="id" name="smart_reply_container" /> <java-symbol type="id" name="remote_input_tag" /> <java-symbol type="id" name="pending_intent_tag" /> @@ -3533,6 +3534,8 @@ <java-symbol type="id" name="clip_to_padding_tag" /> <java-symbol type="id" name="clip_children_tag" /> <java-symbol type="id" name="bubble_button" /> + <java-symbol type="dimen" name="bubble_visible_padding_end" /> + <java-symbol type="dimen" name="bubble_gone_padding_end" /> <java-symbol type="dimen" name="messaging_avatar_size" /> <java-symbol type="dimen" name="messaging_group_sending_progress_size" /> <java-symbol type="dimen" name="messaging_image_rounding" /> diff --git a/core/tests/coretests/src/android/content/ContextTest.java b/core/tests/coretests/src/android/content/ContextTest.java index f13a11e9edfb..17d1389a6602 100644 --- a/core/tests/coretests/src/android/content/ContextTest.java +++ b/core/tests/coretests/src/android/content/ContextTest.java @@ -23,12 +23,14 @@ import static android.view.Display.DEFAULT_DISPLAY; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import android.app.ActivityThread; import android.content.res.Configuration; import android.graphics.PixelFormat; import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; +import android.inputmethodservice.InputMethodService; import android.media.ImageReader; import android.os.UserHandle; import android.view.Display; @@ -136,6 +138,13 @@ public class ContextTest { } @Test + public void testIsUiContext_InputMethodService_returnsTrue() { + final InputMethodService ims = new InputMethodService(); + + assertTrue(ims.isUiContext()); + } + + @Test public void testGetDisplayFromDisplayContextDerivedContextOnPrimaryDisplay() { verifyGetDisplayFromDisplayContextDerivedContext(false /* onSecondaryDisplay */); } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 44349c5f5ab4..4e17062fc52c 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -201,6 +201,9 @@ <uses-permission android:name="android.permission.MANAGE_APPOPS" /> + <!-- Permission required for IncrementalLogCollectionTest --> + <uses-permission android:name="android.permission.LOADER_USAGE_STATS" /> + <!-- Permission required for storage tests - FuseDaemonHostTest --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> diff --git a/packages/SystemUI/res/drawable/ic_create_bubble.xml b/packages/SystemUI/res/drawable/ic_create_bubble.xml index d58e9a347a2f..4abbc8179b4d 100644 --- a/packages/SystemUI/res/drawable/ic_create_bubble.xml +++ b/packages/SystemUI/res/drawable/ic_create_bubble.xml @@ -15,11 +15,11 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="20dp" + android:height="20dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" - android:pathData="M22,12C22,12 22,12 22,12C22,12 22,12 22,12c0,0.56 -0.06,1.1 -0.15,1.64l-1.97,-0.33c0.15,-0.91 0.15,-1.84 -0.02,-2.75c-0.01,-0.03 -0.01,-0.07 -0.02,-0.1c-0.03,-0.18 -0.08,-0.36 -0.13,-0.54c-0.02,-0.08 -0.04,-0.16 -0.06,-0.24c-0.04,-0.14 -0.09,-0.27 -0.14,-0.41c-0.04,-0.12 -0.08,-0.24 -0.13,-0.35c-0.04,-0.09 -0.08,-0.18 -0.13,-0.27c-0.07,-0.15 -0.14,-0.3 -0.22,-0.45c-0.03,-0.05 -0.06,-0.09 -0.08,-0.14c-0.72,-1.26 -1.77,-2.31 -3.03,-3.03c-0.05,-0.03 -0.09,-0.06 -0.14,-0.08c-0.15,-0.08 -0.3,-0.15 -0.45,-0.22c-0.09,-0.04 -0.18,-0.09 -0.27,-0.13c-0.11,-0.05 -0.23,-0.09 -0.35,-0.13c-0.14,-0.05 -0.27,-0.1 -0.41,-0.14c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.18,-0.05 -0.36,-0.1 -0.54,-0.13c-0.03,-0.01 -0.07,-0.01 -0.1,-0.01c-0.95,-0.17 -1.93,-0.17 -2.88,0c-0.03,0.01 -0.07,0.01 -0.1,0.01c-0.18,0.04 -0.36,0.08 -0.54,0.13C9.85,4.3 9.77,4.32 9.69,4.34C9.55,4.38 9.42,4.44 9.28,4.49C9.17,4.53 9.05,4.57 8.93,4.61C8.84,4.65 8.75,4.7 8.66,4.74c-0.15,0.07 -0.3,0.14 -0.45,0.22C8.16,4.98 8.12,5.01 8.07,5.04C5.64,6.42 4,9.02 4,12c0,2.74 1.39,5.16 3.49,6.6c0.01,0.01 0.03,0.02 0.04,0.03c0.16,0.11 0.33,0.2 0.49,0.3c0.06,0.04 0.12,0.08 0.19,0.11c0.13,0.07 0.27,0.13 0.4,0.19c0.11,0.05 0.21,0.1 0.32,0.15c0.1,0.04 0.2,0.07 0.29,0.11c0.15,0.06 0.31,0.11 0.46,0.16c0.05,0.02 0.11,0.03 0.17,0.04c1.11,0.31 2.27,0.35 3.4,0.18l0.35,1.98c-0.54,0.09 -1.08,0.14 -1.62,0.14V22c-0.65,0 -1.28,-0.07 -1.9,-0.19c-0.01,0 -0.01,0 -0.02,0c-0.25,-0.05 -0.49,-0.11 -0.73,-0.18c-0.08,-0.02 -0.16,-0.04 -0.23,-0.06c-0.19,-0.06 -0.37,-0.13 -0.55,-0.19c-0.13,-0.05 -0.26,-0.09 -0.39,-0.14c-0.13,-0.05 -0.25,-0.12 -0.38,-0.18c-0.18,-0.08 -0.35,-0.16 -0.53,-0.25c-0.07,-0.04 -0.14,-0.08 -0.21,-0.13c-0.22,-0.12 -0.43,-0.25 -0.64,-0.39c-0.01,-0.01 -0.02,-0.02 -0.04,-0.03c-0.51,-0.35 -1,-0.74 -1.45,-1.2l0,0C3.12,17.26 2,14.76 2,12c0,-2.76 1.12,-5.26 2.93,-7.07l0,0c0.45,-0.45 0.93,-0.84 1.44,-1.19C6.39,3.73 6.4,3.72 6.42,3.71c0.2,-0.14 0.41,-0.26 0.62,-0.38c0.08,-0.05 0.15,-0.09 0.23,-0.14c0.17,-0.09 0.33,-0.16 0.5,-0.24c0.13,-0.06 0.27,-0.13 0.4,-0.19C8.3,2.71 8.42,2.67 8.55,2.63c0.19,-0.07 0.38,-0.14 0.58,-0.2c0.07,-0.02 0.14,-0.03 0.21,-0.05C10.18,2.14 11.07,2 12,2c0.65,0 1.29,0.07 1.91,0.19c0,0 0,0 0,0c0.25,0.05 0.5,0.11 0.75,0.18c0.07,0.02 0.14,0.03 0.22,0.06c0.19,0.06 0.38,0.13 0.57,0.2c0.12,0.05 0.25,0.09 0.37,0.14c0.14,0.06 0.27,0.12 0.4,0.18c0.17,0.08 0.34,0.16 0.51,0.25c0.08,0.04 0.15,0.09 0.23,0.14c0.21,0.12 0.42,0.24 0.62,0.38c0.01,0.01 0.03,0.02 0.04,0.03c0.51,0.35 0.99,0.74 1.45,1.19c0.24,0.24 0.47,0.49 0.68,0.75c0.04,0.04 0.06,0.09 0.1,0.13c0.17,0.22 0.34,0.45 0.5,0.68c0.01,0.01 0.02,0.03 0.03,0.04c0.69,1.05 1.17,2.21 1.42,3.44c0,0 0,0.01 0,0.01c0.06,0.29 0.1,0.58 0.13,0.87c0.01,0.04 0.01,0.09 0.02,0.13C21.98,11.32 22,11.66 22,12zM18.5,15c-1.93,0 -3.5,1.57 -3.5,3.5s1.57,3.5 3.5,3.5s3.5,-1.57 3.5,-3.5S20.43,15 18.5,15z"/> -</vector>
\ No newline at end of file + android:pathData="M23,5v8h-2V5H3v14h10v2v0H3c-1.1,0 -2,-0.9 -2,-2V5c0,-1.1 0.9,-2 2,-2h18C22.1,3 23,3.9 23,5zM10,8v2.59L5.71,6.29L4.29,7.71L8.59,12H6v2h6V8H10zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/> +</vector> diff --git a/packages/SystemUI/res/drawable/ic_stop_bubble.xml b/packages/SystemUI/res/drawable/ic_stop_bubble.xml index 11bc741a4f17..6cf67a77ff55 100644 --- a/packages/SystemUI/res/drawable/ic_stop_bubble.xml +++ b/packages/SystemUI/res/drawable/ic_stop_bubble.xml @@ -15,11 +15,11 @@ limitations under the License. --> <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="24dp" - android:height="24dp" + android:width="20dp" + android:height="20dp" android:viewportWidth="24" android:viewportHeight="24"> <path android:fillColor="#FF000000" - android:pathData="M21.98,18.32l-3.3,-3.3C20.47,15.11 21.89,16.53 21.98,18.32zM8.66,4.74C8.75,4.7 8.84,4.65 8.93,4.61c0.11,-0.05 0.23,-0.09 0.35,-0.13c0.14,-0.05 0.27,-0.1 0.41,-0.14C9.77,4.32 9.85,4.3 9.92,4.28c0.18,-0.05 0.36,-0.1 0.54,-0.13c0.03,-0.01 0.07,-0.01 0.1,-0.01c0.95,-0.17 1.93,-0.17 2.88,0c0.03,0.01 0.07,0.01 0.1,0.01c0.18,0.04 0.36,0.08 0.54,0.13c0.08,0.02 0.16,0.04 0.23,0.06c0.14,0.04 0.27,0.09 0.41,0.14c0.12,0.04 0.23,0.08 0.35,0.13c0.09,0.04 0.18,0.09 0.27,0.13c0.15,0.07 0.3,0.14 0.45,0.22c0.05,0.03 0.09,0.06 0.14,0.08c1.26,0.72 2.31,1.77 3.03,3.03c0.03,0.05 0.06,0.09 0.08,0.14c0.08,0.15 0.15,0.3 0.22,0.45c0.04,0.09 0.09,0.18 0.13,0.27c0.05,0.11 0.09,0.23 0.13,0.35c0.05,0.13 0.1,0.27 0.14,0.41c0.02,0.08 0.04,0.16 0.06,0.24c0.05,0.18 0.1,0.36 0.13,0.54c0.01,0.03 0.01,0.07 0.02,0.1c0.16,0.91 0.17,1.84 0.02,2.75l1.97,0.33C21.94,13.1 22,12.56 22,12c0,0 0,0 0,0s0,0 0,0c0,-0.34 -0.02,-0.68 -0.05,-1.01c0,-0.04 -0.01,-0.09 -0.02,-0.13c-0.03,-0.29 -0.07,-0.58 -0.13,-0.87c0,0 0,-0.01 0,-0.01c-0.25,-1.23 -0.73,-2.39 -1.42,-3.44c-0.01,-0.01 -0.02,-0.03 -0.03,-0.04c-0.15,-0.23 -0.32,-0.46 -0.5,-0.68c-0.03,-0.04 -0.06,-0.09 -0.1,-0.13c-0.21,-0.26 -0.44,-0.51 -0.68,-0.75c-0.45,-0.45 -0.94,-0.84 -1.45,-1.19c-0.01,-0.01 -0.03,-0.02 -0.04,-0.03c-0.2,-0.14 -0.41,-0.26 -0.62,-0.38c-0.08,-0.04 -0.15,-0.09 -0.23,-0.14c-0.17,-0.09 -0.34,-0.17 -0.51,-0.25c-0.13,-0.06 -0.26,-0.13 -0.4,-0.18c-0.12,-0.05 -0.25,-0.09 -0.37,-0.14c-0.19,-0.07 -0.38,-0.14 -0.57,-0.2c-0.07,-0.02 -0.14,-0.04 -0.22,-0.06c-0.25,-0.07 -0.5,-0.13 -0.75,-0.18c0,0 0,0 0,0C13.29,2.07 12.65,2 12,2c-0.93,0 -1.82,0.14 -2.67,0.37C9.26,2.39 9.19,2.41 9.12,2.43c-0.2,0.06 -0.39,0.13 -0.58,0.2C8.42,2.67 8.3,2.71 8.18,2.76c-0.14,0.06 -0.27,0.12 -0.4,0.19C7.61,3.03 7.44,3.1 7.27,3.19C7.19,3.24 7.12,3.29 7.04,3.33C7.03,3.34 7.02,3.34 7.01,3.35l1.48,1.48C8.55,4.8 8.6,4.77 8.66,4.74zM2.71,1.29L1.29,2.71l2.97,2.97C2.85,7.4 2,9.6 2,12c0,2.76 1.12,5.26 2.93,7.07l0,0c0.45,0.45 0.94,0.85 1.45,1.2c0.01,0.01 0.02,0.02 0.04,0.03c0.21,0.14 0.42,0.27 0.64,0.39c0.07,0.04 0.14,0.09 0.21,0.13c0.17,0.09 0.35,0.17 0.53,0.25c0.13,0.06 0.25,0.12 0.38,0.18c0.13,0.05 0.26,0.1 0.39,0.14c0.18,0.07 0.36,0.14 0.55,0.19c0.08,0.02 0.16,0.04 0.23,0.06c0.24,0.07 0.48,0.13 0.73,0.18c0.01,0 0.01,0 0.02,0C10.72,21.93 11.35,22 12,22v-0.01c0.54,0 1.08,-0.05 1.62,-0.14l-0.35,-1.98c-1.13,0.18 -2.29,0.13 -3.4,-0.18c-0.06,-0.02 -0.11,-0.03 -0.17,-0.04c-0.16,-0.05 -0.31,-0.11 -0.46,-0.16c-0.1,-0.04 -0.2,-0.07 -0.29,-0.11c-0.11,-0.05 -0.22,-0.1 -0.32,-0.15c-0.13,-0.06 -0.27,-0.12 -0.4,-0.19c-0.06,-0.03 -0.13,-0.08 -0.19,-0.11c-0.17,-0.1 -0.33,-0.19 -0.49,-0.3c-0.01,-0.01 -0.03,-0.02 -0.04,-0.03C5.39,17.16 4,14.74 4,12c0,-1.85 0.64,-3.54 1.7,-4.89l9.73,9.73C15.16,17.33 15,17.9 15,18.5c0,1.93 1.57,3.5 3.5,3.5c0.6,0 1.17,-0.16 1.66,-0.43l1.13,1.13l1.41,-1.41L2.71,1.29z"/> + android:pathData="M11.29,14.71L7,10.41V13H5V7h6v2H8.41l4.29,4.29L11.29,14.71zM21,3H3C1.9,3 1,3.9 1,5v14c0,1.1 0.9,2 2,2h10v0v-2H3V5h18v8h2V5C23,3.9 22.1,3 21,3zM19,15c-1.66,0 -3,1.34 -3,3s1.34,3 3,3s3,-1.34 3,-3S20.66,15 19,15z"/> </vector> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 15eda0689101..97a73043aa08 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -21,6 +21,7 @@ import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; +import android.annotation.DimenRes; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification; @@ -104,27 +105,39 @@ class Bubble implements BubbleViewProvider { private Path mDotPath; private int mFlags; + @NonNull + private UserHandle mUser; + @NonNull + private String mPackageName; + private int mDesiredHeight; + @DimenRes + private int mDesiredHeightResId; + /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ - Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) { + Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo, + final int desiredHeight, final int desiredHeightResId) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; mKey = key; mFlags = 0; + mUser = shortcutInfo.getUserHandle(); + mPackageName = shortcutInfo.getPackage(); + mDesiredHeight = desiredHeight; + mDesiredHeightResId = desiredHeightResId; } /** Used in tests when no UI is required. */ @VisibleForTesting(visibility = PRIVATE) - Bubble(NotificationEntry e, - BubbleController.NotificationSuppressionChangedListener listener) { - mEntry = e; + Bubble(@NonNull final NotificationEntry e, + @Nullable final BubbleController.NotificationSuppressionChangedListener listener) { + Objects.requireNonNull(e); mKey = e.getKey(); - mLastUpdated = e.getSbn().getPostTime(); mSuppressionListener = listener; - mFlags = e.getSbn().getNotification().flags; + setEntry(e); } @Override @@ -137,17 +150,14 @@ class Bubble implements BubbleViewProvider { return mEntry; } - @Nullable + @NonNull public UserHandle getUser() { - if (mEntry != null) return mEntry.getSbn().getUser(); - if (mShortcutInfo != null) return mShortcutInfo.getUserHandle(); - return null; + return mUser; } + @NonNull public String getPackageName() { - return mEntry == null - ? mShortcutInfo == null ? null : mShortcutInfo.getPackage() - : mEntry.getSbn().getPackageName(); + return mPackageName; } @Override @@ -318,9 +328,18 @@ class Bubble implements BubbleViewProvider { /** * Sets the entry associated with this bubble. */ - void setEntry(NotificationEntry entry) { + void setEntry(@NonNull final NotificationEntry entry) { + Objects.requireNonNull(entry); + Objects.requireNonNull(entry.getSbn()); mEntry = entry; mLastUpdated = entry.getSbn().getPostTime(); + mFlags = entry.getSbn().getNotification().flags; + mPackageName = entry.getSbn().getPackageName(); + mUser = entry.getSbn().getUser(); + if (entry.getBubbleMetadata() != null) { + mDesiredHeight = entry.getBubbleMetadata().getDesiredHeight(); + mDesiredHeightResId = entry.getBubbleMetadata().getDesiredHeightResId(); + } } /** @@ -434,28 +453,30 @@ class Bubble implements BubbleViewProvider { return mFlyoutMessage; } + int getRawDesiredHeight() { + return mDesiredHeight; + } + + int getRawDesiredHeightResId() { + return mDesiredHeightResId; + } + float getDesiredHeight(Context context) { - if (mEntry == null) return 0; - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - boolean useRes = data.getDesiredHeightResId() != 0; + boolean useRes = mDesiredHeightResId != 0; if (useRes) { - return getDimenForPackageUser(context, data.getDesiredHeightResId(), - mEntry.getSbn().getPackageName(), - mEntry.getSbn().getUser().getIdentifier()); + return getDimenForPackageUser(context, mDesiredHeightResId, mPackageName, + mUser.getIdentifier()); } else { - return data.getDesiredHeight() - * context.getResources().getDisplayMetrics().density; + return mDesiredHeight * context.getResources().getDisplayMetrics().density; } } String getDesiredHeightString() { - if (mEntry == null) return String.valueOf(0); - Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); - boolean useRes = data.getDesiredHeightResId() != 0; + boolean useRes = mDesiredHeightResId != 0; if (useRes) { - return String.valueOf(data.getDesiredHeightResId()); + return String.valueOf(mDesiredHeightResId); } else { - return String.valueOf(data.getDesiredHeight()); + return String.valueOf(mDesiredHeight); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt index c2b9195c8ba3..d20f40559b5d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt @@ -77,7 +77,8 @@ internal class BubbleDataRepository @Inject constructor( var shortcutId = b.shortcutInfo?.id if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId if (shortcutId == null) return@mapNotNull null - BubbleEntity(userId, b.packageName, shortcutId, b.key) + BubbleEntity(userId, b.packageName, shortcutId, b.key, b.rawDesiredHeight, + b.rawDesiredHeightResId) } } @@ -158,7 +159,8 @@ internal class BubbleDataRepository @Inject constructor( val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } - ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) } + ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo, entity.desiredHeight, + entity.desiredHeightResId) } } uiScope.launch { cb(bubbles) } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt index 43482616da2c..355c4b115c8d 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleEntity.kt @@ -15,11 +15,14 @@ */ package com.android.systemui.bubbles.storage +import android.annotation.DimenRes import android.annotation.UserIdInt data class BubbleEntity( @UserIdInt val userId: Int, val packageName: String, val shortcutId: String, - val key: String + val key: String, + val desiredHeight: Int, + @DimenRes val desiredHeightResId: Int ) diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt index 1df9f72022f0..a8faf258da07 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/storage/BubbleXmlHelper.kt @@ -31,6 +31,8 @@ private const val ATTR_USER_ID = "uid" private const val ATTR_PACKAGE = "pkg" private const val ATTR_SHORTCUT_ID = "sid" private const val ATTR_KEY = "key" +private const val ATTR_DESIRED_HEIGHT = "h" +private const val ATTR_DESIRED_HEIGHT_RES_ID = "hid" /** * Writes the bubbles in xml format into given output stream. @@ -59,6 +61,8 @@ private fun writeXmlEntry(serializer: XmlSerializer, bubble: BubbleEntity) { serializer.attribute(null, ATTR_PACKAGE, bubble.packageName) serializer.attribute(null, ATTR_SHORTCUT_ID, bubble.shortcutId) serializer.attribute(null, ATTR_KEY, bubble.key) + serializer.attribute(null, ATTR_DESIRED_HEIGHT, bubble.desiredHeight.toString()) + serializer.attribute(null, ATTR_DESIRED_HEIGHT_RES_ID, bubble.desiredHeightResId.toString()) serializer.endTag(null, TAG_BUBBLE) } catch (e: IOException) { throw RuntimeException(e) @@ -86,7 +90,9 @@ private fun readXmlEntry(parser: XmlPullParser): BubbleEntity? { parser.getAttributeWithName(ATTR_USER_ID)?.toInt() ?: return null, parser.getAttributeWithName(ATTR_PACKAGE) ?: return null, parser.getAttributeWithName(ATTR_SHORTCUT_ID) ?: return null, - parser.getAttributeWithName(ATTR_KEY) ?: return null + parser.getAttributeWithName(ATTR_KEY) ?: return null, + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT)?.toInt() ?: return null, + parser.getAttributeWithName(ATTR_DESIRED_HEIGHT_RES_ID)?.toInt() ?: return null ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index b8c1842a1780..f039fc2d84ae 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -49,15 +49,17 @@ import com.android.settingslib.Utils; import com.android.settingslib.media.MediaOutputSliceConstants; import com.android.settingslib.widget.AdaptiveIcon; import com.android.systemui.R; +import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.util.animation.TransitionLayout; -import com.android.systemui.util.concurrency.DelayableExecutor; import org.jetbrains.annotations.NotNull; import java.util.List; import java.util.concurrent.Executor; +import javax.inject.Inject; + /** * A view controller used for Media Playback. */ @@ -93,12 +95,14 @@ public class MediaControlPanel { * @param backgroundExecutor background executor, used for processing artwork * @param activityStarter activity starter */ - public MediaControlPanel(Context context, DelayableExecutor backgroundExecutor, - ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager) { + @Inject + public MediaControlPanel(Context context, @Background Executor backgroundExecutor, + ActivityStarter activityStarter, MediaHostStatesManager mediaHostStatesManager, + SeekBarViewModel seekBarViewModel) { mContext = context; mBackgroundExecutor = backgroundExecutor; mActivityStarter = activityStarter; - mSeekBarViewModel = new SeekBarViewModel(backgroundExecutor); + mSeekBarViewModel = seekBarViewModel; mMediaViewController = new MediaViewController(context, mediaHostStatesManager); loadDimens(); } diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt index 3557b04a57bc..9b9a6b4b13ab 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewManager.kt @@ -11,14 +11,12 @@ import android.widget.HorizontalScrollView import android.widget.LinearLayout import androidx.core.view.GestureDetectorCompat import com.android.systemui.R -import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.plugins.ActivityStarter import com.android.systemui.qs.PageIndicator import com.android.systemui.statusbar.notification.VisualStabilityManager import com.android.systemui.util.animation.UniqueObjectHostView import com.android.systemui.util.animation.requiresRemeasuring -import com.android.systemui.util.concurrency.DelayableExecutor import javax.inject.Inject +import javax.inject.Provider import javax.inject.Singleton private const val FLING_SLOP = 1000000 @@ -30,9 +28,8 @@ private const val FLING_SLOP = 1000000 @Singleton class MediaViewManager @Inject constructor( private val context: Context, - @Background private val backgroundExecutor: DelayableExecutor, + private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityManager: VisualStabilityManager, - private val activityStarter: ActivityStarter, private val mediaHostStatesManager: MediaHostStatesManager, mediaManager: MediaDataCombineLatest ) { @@ -265,8 +262,7 @@ class MediaViewManager @Inject constructor( } var existingPlayer = mediaPlayers[key] if (existingPlayer == null) { - existingPlayer = MediaControlPanel(context, backgroundExecutor, activityStarter, - mediaHostStatesManager) + existingPlayer = mediaControlPanelFactory.get() existingPlayer.attach(PlayerViewHolder.create(LayoutInflater.from(context), mediaContent)) mediaPlayers[key] = existingPlayer @@ -289,7 +285,7 @@ class MediaViewManager @Inject constructor( needsReordering = true } } - existingPlayer.bind(data) + existingPlayer?.bind(data) updateMediaPaddings() updatePageIndicator() } diff --git a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt index 75ad06962afe..efc476d0c86f 100644 --- a/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/media/SeekBarViewModel.kt @@ -27,8 +27,10 @@ import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import androidx.lifecycle.MutableLiveData import androidx.lifecycle.LiveData - -import com.android.systemui.util.concurrency.DelayableExecutor +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.util.concurrency.RepeatableExecutor +import java.util.concurrent.Executor +import javax.inject.Inject private const val POSITION_UPDATE_INTERVAL_MILLIS = 100L @@ -65,7 +67,7 @@ private fun PlaybackState.computePosition(duration: Long): Long { } /** ViewModel for seek bar in QS media player. */ -class SeekBarViewModel(val bgExecutor: DelayableExecutor) { +class SeekBarViewModel @Inject constructor(@Background private val bgExecutor: RepeatableExecutor) { private var _data = Progress(false, false, null, null) set(value) { @@ -89,10 +91,10 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { private var callback = object : MediaController.Callback() { override fun onPlaybackStateChanged(state: PlaybackState) { playbackState = state - if (shouldPollPlaybackPosition()) { - checkPlaybackPosition() - } else if (PlaybackState.STATE_NONE.equals(playbackState)) { + if (PlaybackState.STATE_NONE.equals(playbackState)) { clearController() + } else { + checkIfPollingNeeded() } } @@ -100,12 +102,14 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { clearController() } } + private var cancel: Runnable? = null /** Listening state (QS open or closed) is used to control polling of progress. */ var listening = true - set(value) { - if (value) { - checkPlaybackPosition() + set(value) = bgExecutor.execute { + if (field != value) { + field = value + checkIfPollingNeeded() } } @@ -137,9 +141,7 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { playbackState?.getState() == PlaybackState.STATE_NONE || (duration != null && duration <= 0)) false else true _data = Progress(enabled, seekAvailable, position, duration) - if (shouldPollPlaybackPosition()) { - checkPlaybackPosition() - } + checkIfPollingNeeded() } /** @@ -151,6 +153,8 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { fun clearController() = bgExecutor.execute { controller = null playbackState = null + cancel?.run() + cancel = null _data = _data.copy(enabled = false) } @@ -158,26 +162,34 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { * Call to clean up any resources. */ @AnyThread - fun onDestroy() { + fun onDestroy() = bgExecutor.execute { controller = null playbackState = null + cancel?.run() + cancel = null } - @AnyThread - private fun checkPlaybackPosition(): Runnable = bgExecutor.executeDelayed({ + @WorkerThread + private fun checkPlaybackPosition() { val duration = _data.duration ?: -1 val currentPosition = playbackState?.computePosition(duration.toLong())?.toInt() if (currentPosition != null && _data.elapsedTime != currentPosition) { _data = _data.copy(elapsedTime = currentPosition) } - if (shouldPollPlaybackPosition()) { - checkPlaybackPosition() - } - }, POSITION_UPDATE_INTERVAL_MILLIS) + } @WorkerThread - private fun shouldPollPlaybackPosition(): Boolean { - return listening && playbackState?.isInMotion() ?: false + private fun checkIfPollingNeeded() { + val needed = listening && playbackState?.isInMotion() ?: false + if (needed) { + if (cancel == null) { + cancel = bgExecutor.executeRepeatedly(this::checkPlaybackPosition, 0L, + POSITION_UPDATE_INTERVAL_MILLIS) + } + } else { + cancel?.run() + cancel = null + } } /** Gets a listener to attach to the seek bar to handle seeking. */ @@ -194,7 +206,7 @@ class SeekBarViewModel(val bgExecutor: DelayableExecutor) { private class SeekBarChangeListener( val viewModel: SeekBarViewModel, - val bgExecutor: DelayableExecutor + val bgExecutor: Executor ) : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged(bar: SeekBar, progress: Int, fromUser: Boolean) { if (fromUser) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java index 3dda15b5ce39..a8c03243c117 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java @@ -42,7 +42,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain import com.android.systemui.statusbar.phone.KeyguardBypassController; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.util.Assert; -import com.android.systemui.util.Utils; import java.util.ArrayList; import java.util.HashMap; @@ -150,9 +149,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle final int N = activeNotifications.size(); for (int i = 0; i < N; i++) { NotificationEntry ent = activeNotifications.get(i); - boolean hideMedia = Utils.useQsMediaPlayer(mContext); if (ent.isRowDismissed() || ent.isRowRemoved() - || (ent.isMediaNotification() && hideMedia) || mBubbleController.isBubbleNotificationSuppressedFromShade(ent) || mFgsSectionController.hasEntry(ent)) { // we don't want to update removed notifications because they could diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java index 3afd6235b287..6335a09cde2b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationFilter.java @@ -16,6 +16,8 @@ package com.android.systemui.statusbar.notification; +import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification; + import android.Manifest; import android.app.AppGlobals; import android.app.Notification; @@ -27,6 +29,7 @@ import android.service.notification.StatusBarNotification; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.Dependency; import com.android.systemui.ForegroundServiceController; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -46,6 +49,7 @@ public class NotificationFilter { private final NotificationGroupManager mGroupManager = Dependency.get( NotificationGroupManager.class); private final StatusBarStateController mStatusBarStateController; + private final Boolean mIsMediaFlagEnabled; private NotificationEntryManager.KeyguardEnvironment mEnvironment; private ShadeController mShadeController; @@ -53,8 +57,11 @@ public class NotificationFilter { private NotificationLockscreenUserManager mUserManager; @Inject - public NotificationFilter(StatusBarStateController statusBarStateController) { + public NotificationFilter( + StatusBarStateController statusBarStateController, + MediaFeatureFlag mediaFeatureFlag) { mStatusBarStateController = statusBarStateController; + mIsMediaFlagEnabled = mediaFeatureFlag.getEnabled(); } private NotificationEntryManager.KeyguardEnvironment getEnvironment() { @@ -133,6 +140,10 @@ public class NotificationFilter { } } } + + if (mIsMediaFlagEnabled && isMediaNotification(sbn)) { + return true; + } return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java new file mode 100644 index 000000000000..026a3ffb73cd --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinator.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static com.android.systemui.media.MediaDataManagerKt.isMediaNotification; + +import com.android.systemui.media.MediaFeatureFlag; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import javax.inject.Inject; + +/** + * Coordinates hiding (filtering) of media notifications. + */ +public class MediaCoordinator implements Coordinator { + private static final String TAG = "MediaCoordinator"; + + private final Boolean mIsMediaFeatureEnabled; + + private final NotifFilter mMediaFilter = new NotifFilter(TAG) { + @Override + public boolean shouldFilterOut(NotificationEntry entry, long now) { + return mIsMediaFeatureEnabled && isMediaNotification(entry.getSbn()); + } + }; + + @Inject + public MediaCoordinator(MediaFeatureFlag featureFlag) { + mIsMediaFeatureEnabled = featureFlag.getEnabled(); + } + + @Override + public void attach(NotifPipeline pipeline) { + pipeline.addFinalizeFilter(mMediaFilter); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java index 2b279bbd553a..ac4296439507 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.java @@ -57,7 +57,8 @@ public class NotifCoordinators implements Dumpable { BubbleCoordinator bubbleCoordinator, HeadsUpCoordinator headsUpCoordinator, ConversationCoordinator conversationCoordinator, - PreparationCoordinator preparationCoordinator) { + PreparationCoordinator preparationCoordinator, + MediaCoordinator mediaCoordinator) { dumpManager.registerDumpable(TAG, this); mCoordinators.add(new HideLocallyDismissedNotifsCoordinator()); mCoordinators.add(hideNotifsForOtherUsersCoordinator); @@ -72,6 +73,7 @@ public class NotifCoordinators implements Dumpable { mCoordinators.add(preparationCoordinator); } // TODO: add new Coordinators here! (b/112656837) + mCoordinators.add(mediaCoordinator); // TODO: add the sections in a particular ORDER (HeadsUp < People < Alerting) for (Coordinator c : mCoordinators) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 8a4fdc24dc1b..b0861bfbd643 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -1348,7 +1348,9 @@ public class NotificationContentView extends FrameLayout { } ImageView bubbleButton = layout.findViewById(com.android.internal.R.id.bubble_button); View actionContainer = layout.findViewById(com.android.internal.R.id.actions_container); - if (bubbleButton == null || actionContainer == null) { + LinearLayout actionContainerLayout = + layout.findViewById(com.android.internal.R.id.actions_container_layout); + if (bubbleButton == null || actionContainer == null || actionContainerLayout == null) { return; } boolean isPersonWithShortcut = @@ -1374,8 +1376,16 @@ public class NotificationContentView extends FrameLayout { bubbleButton.setOnClickListener(mContainingNotification.getBubbleClickListener()); bubbleButton.setVisibility(VISIBLE); actionContainer.setVisibility(VISIBLE); + + int paddingEnd = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.bubble_visible_padding_end); + actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0); } else { bubbleButton.setVisibility(GONE); + + int paddingEnd = getResources().getDimensionPixelSize( + com.android.internal.R.dimen.bubble_gone_padding_end); + actionContainerLayout.setPaddingRelative(0, 0, paddingEnd, 0); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt index f46819252fac..2aed75e6519f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubblePersistentRepositoryTest.kt @@ -29,9 +29,9 @@ import org.junit.runner.RunWith class BubblePersistentRepositoryTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1"), - BubbleEntity(10, "com.example.chat", "alice and bob", "key-2"), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3") + BubbleEntity(0, "com.example.messenger", "shortcut-1", "key-1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "key-2", 0, 16537428), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "key-3", 120, 0) ) private lateinit var repository: BubblePersistentRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt index 2bb6bb8ebe14..f9d611c2bb33 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleVolatileRepositoryTest.kt @@ -37,9 +37,9 @@ class BubbleVolatileRepositoryTest : SysuiTestCase() { private val user0 = UserHandle.of(0) private val user10 = UserHandle.of(10) - private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1") - private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2") - private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3") + private val bubble1 = BubbleEntity(0, PKG_MESSENGER, "shortcut-1", "k1", 120, 0) + private val bubble2 = BubbleEntity(10, PKG_CHAT, "alice and bob", "k2", 0, 16537428) + private val bubble3 = BubbleEntity(0, PKG_MESSENGER, "shortcut-2", "k3", 120, 0) private val bubbles = listOf(bubble1, bubble2, bubble3) diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt index 79701ecf70f8..49467874dd8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/storage/BubbleXmlHelperTest.kt @@ -31,17 +31,17 @@ import java.io.ByteArrayOutputStream class BubbleXmlHelperTest : SysuiTestCase() { private val bubbles = listOf( - BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1"), - BubbleEntity(10, "com.example.chat", "alice and bob", "k2"), - BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3") + BubbleEntity(0, "com.example.messenger", "shortcut-1", "k1", 120, 0), + BubbleEntity(10, "com.example.chat", "alice and bob", "k2", 0, 16537428), + BubbleEntity(0, "com.example.messenger", "shortcut-2", "k3", 120, 0) ) @Test fun testWriteXml() { val expectedEntries = """ - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" /> + <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" /> + <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" h="120" hid="0" /> """.trimIndent() ByteArrayOutputStream().use { writeXml(it, bubbles) @@ -56,9 +56,9 @@ class BubbleXmlHelperTest : SysuiTestCase() { val src = """ <?xml version='1.0' encoding='utf-8' standalone='yes' ?> <bs> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-1" key="k1" /> - <bb uid="10" pkg="com.example.chat" sid="alice and bob" key="k2" /> - <bb uid="0" pkg="com.example.messenger" sid="shortcut-2" key="k3" /> + <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" /> + <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))) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index 1ba36e19b404..737ced63eed0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -31,6 +31,7 @@ import android.widget.ImageButton import android.widget.ImageView import android.widget.SeekBar import android.widget.TextView +import androidx.lifecycle.LiveData import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -41,6 +42,7 @@ import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentCaptor @@ -48,6 +50,7 @@ import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever +import org.mockito.junit.MockitoJUnit private const val KEY = "TEST_KEY" private const val APP = "APP" @@ -73,6 +76,8 @@ public class MediaControlPanelTest : SysuiTestCase() { @Mock private lateinit var holder: PlayerViewHolder @Mock private lateinit var view: TransitionLayout @Mock private lateinit var mediaHostStatesManager: MediaHostStatesManager + @Mock private lateinit var seekBarViewModel: SeekBarViewModel + @Mock private lateinit var seekBarData: LiveData<SeekBarViewModel.Progress> private lateinit var appIcon: ImageView private lateinit var appName: TextView private lateinit var albumView: ImageView @@ -94,18 +99,17 @@ public class MediaControlPanelTest : SysuiTestCase() { private val device = MediaDeviceData(true, null, DEVICE_NAME) private val disabledDevice = MediaDeviceData(false, null, null) + @JvmField @Rule val mockito = MockitoJUnit.rule() + @Before fun setUp() { bgExecutor = FakeExecutor(FakeSystemClock()) - activityStarter = mock(ActivityStarter::class.java) - mediaHostStatesManager = mock(MediaHostStatesManager::class.java) - - player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager) + player = MediaControlPanel(context, bgExecutor, activityStarter, mediaHostStatesManager, + seekBarViewModel) + whenever(seekBarViewModel.progress).thenReturn(seekBarData) // Mock out a view holder for the player to attach to. - holder = mock(PlayerViewHolder::class.java) - view = mock(TransitionLayout::class.java) whenever(holder.player).thenReturn(view) appIcon = ImageView(context) whenever(holder.appIcon).thenReturn(appIcon) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt index 19e15b3c4307..24e9bd837d5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/SeekBarViewModelTest.kt @@ -29,6 +29,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.concurrency.FakeExecutor +import com.android.systemui.util.concurrency.FakeRepeatableExecutor import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -71,7 +72,7 @@ public class SeekBarViewModelTest : SysuiTestCase() { @Before fun setUp() { fakeExecutor = FakeExecutor(FakeSystemClock()) - viewModel = SeekBarViewModel(fakeExecutor) + viewModel = SeekBarViewModel(FakeRepeatableExecutor(fakeExecutor)) mockController = mock(MediaController::class.java) whenever(mockController.sessionToken).thenReturn(token1) mockTransport = mock(MediaController.TransportControls::class.java) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java index 595ba89ca3b6..5a81d36ea744 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java @@ -27,8 +27,10 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.app.Notification; +import android.app.Notification.MediaStyle; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; +import android.media.session.MediaSession; import android.os.Bundle; import android.service.notification.StatusBarNotification; import android.testing.AndroidTestingRunner; @@ -40,6 +42,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.ForegroundServiceController; import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaFeatureFlag; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment; @@ -51,6 +54,7 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper; import com.android.systemui.statusbar.phone.NotificationGroupManager; import com.android.systemui.statusbar.phone.ShadeController; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -73,10 +77,16 @@ public class NotificationFilterTest extends SysuiTestCase { ForegroundServiceController mFsc; @Mock KeyguardEnvironment mEnvironment; + @Mock + MediaFeatureFlag mMediaFeatureFlag; + @Mock + StatusBarStateController mStatusBarStateController; private final IPackageManager mMockPackageManager = mock(IPackageManager.class); private NotificationFilter mNotificationFilter; private ExpandableNotificationRow mRow; + private NotificationEntry mMediaEntry; + private MediaSession mMediaSession; @Before public void setUp() throws Exception { @@ -84,6 +94,12 @@ public class NotificationFilterTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); when(mMockStatusBarNotification.getUid()).thenReturn(UID_NORMAL); + mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION"); + NotificationEntryBuilder builder = new NotificationEntryBuilder(); + builder.modifyNotification(mContext).setStyle( + new MediaStyle().setMediaSession(mMediaSession.getSessionToken())); + mMediaEntry = builder.build(); + when(mMockPackageManager.checkUidPermission( eq(Manifest.permission.NOTIFICATION_DURING_SETUP), eq(UID_NORMAL))) @@ -107,7 +123,12 @@ public class NotificationFilterTest extends SysuiTestCase { mDependency, TestableLooper.get(this)); mRow = testHelper.createRow(); - mNotificationFilter = new NotificationFilter(mock(StatusBarStateController.class)); + mNotificationFilter = new NotificationFilter(mStatusBarStateController, mMediaFeatureFlag); + } + + @After + public void tearDown() { + mMediaSession.release(); } @Test @@ -218,6 +239,56 @@ public class NotificationFilterTest extends SysuiTestCase { assertFalse(mNotificationFilter.shouldFilterOut(entry)); } + @Test + public void shouldFilterOtherNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotificationEntry otherEntry = new NotificationEntryBuilder().build(); + final boolean shouldFilter = filter.shouldFilterOut(otherEntry); + // THEN it shouldn't be filtered + assertFalse(shouldFilter); + } + + @Test + public void shouldFilterOtherNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotificationEntry otherEntry = new NotificationEntryBuilder().build(); + final boolean shouldFilter = filter.shouldFilterOut(otherEntry); + // THEN it shouldn't be filtered + assertFalse(shouldFilter); + } + + @Test + public void shouldFilterMediaNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry); + // THEN it shouldn't be filtered + assertFalse(shouldFilter); + } + + @Test + public void shouldFilterMediaNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + NotificationFilter filter = new NotificationFilter(mStatusBarStateController, + mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry); + // THEN it should be filtered + assertTrue(shouldFilter); + } + private void initStatusBarNotification(boolean allowDuringSetup) { Bundle bundle = new Bundle(); bundle.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, allowDuringSetup); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java new file mode 100644 index 000000000000..c5dc2b4d4f03 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/MediaCoordinatorTest.java @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.statusbar.notification.collection.coordinator; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.Notification.MediaStyle; +import android.media.session.MediaSession; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.media.MediaFeatureFlag; +import com.android.systemui.statusbar.notification.collection.NotifPipeline; +import com.android.systemui.statusbar.notification.collection.NotificationEntry; +import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public final class MediaCoordinatorTest extends SysuiTestCase { + + private MediaSession mMediaSession; + private NotificationEntry mOtherEntry; + private NotificationEntry mMediaEntry; + + @Mock private NotifPipeline mNotifPipeline; + @Mock private MediaFeatureFlag mMediaFeatureFlag; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mOtherEntry = new NotificationEntryBuilder().build(); + mMediaSession = new MediaSession(mContext, "TEST_MEDIA_SESSION"); + NotificationEntryBuilder builder = new NotificationEntryBuilder(); + builder.modifyNotification(mContext).setStyle( + new MediaStyle().setMediaSession(mMediaSession.getSessionToken())); + mMediaEntry = builder.build(); + } + + @After + public void tearDown() { + mMediaSession.release(); + } + + @Test + public void shouldFilterOtherNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mOtherEntry, 0); + // THEN it shouldn't be filtered + assertThat(shouldFilter).isFalse(); + } + + @Test + public void shouldFilterOtherNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about an entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mOtherEntry, 0); + // THEN it shouldn't be filtered + assertThat(shouldFilter).isFalse(); + } + + @Test + public void shouldFilterMediaNotificationWhenDisabled() { + // GIVEN that the media feature is disabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(false); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry, 0); + // THEN it shouldn't be filtered + assertThat(shouldFilter).isFalse(); + } + + @Test + public void shouldFilterMediaNotificationWhenEnabled() { + // GIVEN that the media feature is enabled + when(mMediaFeatureFlag.getEnabled()).thenReturn(true); + MediaCoordinator coordinator = new MediaCoordinator(mMediaFeatureFlag); + // WHEN the media filter is asked about a media entry + NotifFilter filter = captureFilter(coordinator); + final boolean shouldFilter = filter.shouldFilterOut(mMediaEntry, 0); + // THEN it should be filtered + assertThat(shouldFilter).isTrue(); + } + + private NotifFilter captureFilter(MediaCoordinator coordinator) { + ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class); + coordinator.attach(mNotifPipeline); + verify(mNotifPipeline).addFinalizeFilter(filterCaptor.capture()); + return filterCaptor.getValue(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java new file mode 100644 index 000000000000..477f615faf2b --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeRepeatableExecutor.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.util.concurrency; + +/** + * A fake to use in tests. + */ +public class FakeRepeatableExecutor extends RepeatableExecutorImpl { + + /** + * Initializes a fake RepeatableExecutor from a fake executor. + * + * Use the fake executor to actually process tasks. + * + * @param executor fake executor. + */ + public FakeRepeatableExecutor(FakeExecutor executor) { + super(executor); + } +} |