Merge "[VolumePanel] Move the Settings' volume panel into a SystemUIDialog" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
16219f2ea4
@@ -58,6 +58,12 @@ public class FeatureFlagUtils {
|
|||||||
public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
|
public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED =
|
||||||
"settings_app_locale_opt_in_enabled";
|
"settings_app_locale_opt_in_enabled";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launch the Volume panel in SystemUI.
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final String SETTINGS_VOLUME_PANEL_IN_SYSTEMUI =
|
||||||
|
"settings_volume_panel_in_systemui";
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
|
public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS =
|
||||||
@@ -105,6 +111,7 @@ public class FeatureFlagUtils {
|
|||||||
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
|
DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true");
|
||||||
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
|
DEFAULT_FLAGS.put("settings_search_always_expand", "true");
|
||||||
DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
|
DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true");
|
||||||
|
DEFAULT_FLAGS.put(SETTINGS_VOLUME_PANEL_IN_SYSTEMUI, "false");
|
||||||
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
|
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
|
||||||
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
|
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
|
||||||
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
|
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
|
||||||
|
|||||||
@@ -81,5 +81,6 @@
|
|||||||
<permission name="android.permission.READ_DEVICE_CONFIG" />
|
<permission name="android.permission.READ_DEVICE_CONFIG" />
|
||||||
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
|
<permission name="android.permission.READ_SAFETY_CENTER_STATUS" />
|
||||||
<permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
|
<permission name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS" />
|
||||||
|
<permission name="android.permission.READ_SEARCH_INDEXABLES" />
|
||||||
</privapp-permissions>
|
</privapp-permissions>
|
||||||
</permissions>
|
</permissions>
|
||||||
|
|||||||
@@ -152,6 +152,9 @@
|
|||||||
<uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
|
<uses-permission android:name="android.permission.CONTROL_KEYGUARD_SECURE_NOTIFICATIONS" />
|
||||||
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
|
<uses-permission android:name="android.permission.GET_RUNTIME_PERMISSIONS" />
|
||||||
|
|
||||||
|
<!-- For auto-grant the access to the Settings' slice preferences, e.g. volume slices. -->
|
||||||
|
<uses-permission android:name="android.permission.READ_SEARCH_INDEXABLES" />
|
||||||
|
|
||||||
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
|
<!-- Needed for WallpaperManager.clear in ImageWallpaper.updateWallpaperLocked -->
|
||||||
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
||||||
|
|
||||||
@@ -956,5 +959,13 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".volume.VolumePanelDialogReceiver"
|
||||||
|
android:exported="true">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.settings.panel.action.VOLUME" />
|
||||||
|
<action android:name="com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG" />
|
||||||
|
<action android:name="com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|||||||
101
packages/SystemUI/res/layout/volume_panel_dialog.xml
Normal file
101
packages/SystemUI/res/layout/volume_panel_dialog.xml
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2022 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.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/volume_panel_dialog"
|
||||||
|
android:layout_width="@dimen/large_dialog_width"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Widget.SliceView.Panel"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:layout_marginTop="@dimen/dialog_top_padding"
|
||||||
|
android:layout_marginBottom="@dimen/dialog_bottom_padding"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/volume_panel_dialog_title"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/sound_settings"
|
||||||
|
android:textAppearance="@style/TextAppearance.Dialog.Title"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/volume_panel_parent_layout"
|
||||||
|
android:scrollbars="vertical"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:minHeight="304dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:overScrollMode="never"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/button_layout"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="@dimen/dialog_button_vertical_padding"
|
||||||
|
android:layout_marginStart="@dimen/dialog_side_padding"
|
||||||
|
android:layout_marginEnd="@dimen/dialog_side_padding"
|
||||||
|
android:layout_marginBottom="@dimen/dialog_bottom_padding"
|
||||||
|
android:baselineAligned="false"
|
||||||
|
android:clickable="false"
|
||||||
|
android:focusable="false">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="start|center_vertical"
|
||||||
|
android:orientation="vertical">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/settings_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/volume_panel_dialog_settings_button"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
style="@style/Widget.Dialog.Button.BorderButton"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="@dimen/dialog_button_horizontal_padding"
|
||||||
|
android:layout_gravity="end|center_vertical">
|
||||||
|
<Button
|
||||||
|
android:id="@+id/done_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/inline_done_button"
|
||||||
|
style="@style/Widget.Dialog.Button"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"/>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
|
</LinearLayout>
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Copyright (C) 2022 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.
|
||||||
|
-->
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/slice_slider_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<androidx.slice.widget.SliceView
|
||||||
|
android:id="@+id/slice_view"
|
||||||
|
style="@style/Widget.SliceView.Panel.Slider"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingVertical="@dimen/volume_panel_slice_vertical_padding"
|
||||||
|
android:paddingHorizontal="@dimen/volume_panel_slice_horizontal_padding"/>
|
||||||
|
</LinearLayout>
|
||||||
@@ -479,6 +479,10 @@
|
|||||||
|
|
||||||
<dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
|
<dimen name="volume_tool_tip_arrow_corner_radius">2dp</dimen>
|
||||||
|
|
||||||
|
<!-- Volume panel slices dimensions -->
|
||||||
|
<dimen name="volume_panel_slice_vertical_padding">8dp</dimen>
|
||||||
|
<dimen name="volume_panel_slice_horizontal_padding">24dp</dimen>
|
||||||
|
|
||||||
<!-- Size of each item in the ringer selector drawer. -->
|
<!-- Size of each item in the ringer selector drawer. -->
|
||||||
<dimen name="volume_ringer_drawer_item_size">42dp</dimen>
|
<dimen name="volume_ringer_drawer_item_size">42dp</dimen>
|
||||||
<dimen name="volume_ringer_drawer_item_size_half">21dp</dimen>
|
<dimen name="volume_ringer_drawer_item_size_half">21dp</dimen>
|
||||||
|
|||||||
@@ -1139,6 +1139,11 @@
|
|||||||
<!-- Content description for accessibility: Hint if click will disable. [CHAR LIMIT=NONE] -->
|
<!-- Content description for accessibility: Hint if click will disable. [CHAR LIMIT=NONE] -->
|
||||||
<string name="volume_odi_captions_hint_disable">disable</string>
|
<string name="volume_odi_captions_hint_disable">disable</string>
|
||||||
|
|
||||||
|
<!-- Sound and vibration settings dialog title. [CHAR LIMIT=30] -->
|
||||||
|
<string name="sound_settings">Sound & vibration</string>
|
||||||
|
<!-- Label for button to go to sound settings screen [CHAR_LIMIT=30] -->
|
||||||
|
<string name="volume_panel_dialog_settings_button">Settings</string>
|
||||||
|
|
||||||
<!-- content description for audio output chooser [CHAR LIMIT=NONE]-->
|
<!-- content description for audio output chooser [CHAR LIMIT=NONE]-->
|
||||||
|
|
||||||
<!-- Screen pinning dialog title. -->
|
<!-- Screen pinning dialog title. -->
|
||||||
|
|||||||
@@ -928,6 +928,10 @@
|
|||||||
<item name="rowStyle">@style/SliceRow</item>
|
<item name="rowStyle">@style/SliceRow</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="Widget.SliceView.Panel.Slider">
|
||||||
|
<item name="rowStyle">@style/SliceRow.Slider</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="SliceRow">
|
<style name="SliceRow">
|
||||||
<!-- 2dp start padding for the start icon -->
|
<!-- 2dp start padding for the start icon -->
|
||||||
<item name="titleItemStartPadding">2dp</item>
|
<item name="titleItemStartPadding">2dp</item>
|
||||||
@@ -949,6 +953,26 @@
|
|||||||
<item name="actionDividerHeight">32dp</item>
|
<item name="actionDividerHeight">32dp</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="SliceRow.Slider">
|
||||||
|
<!-- Padding between content and the start icon is 5dp -->
|
||||||
|
<item name="contentStartPadding">5dp</item>
|
||||||
|
<item name="contentEndPadding">0dp</item>
|
||||||
|
|
||||||
|
<!-- 0dp start padding for the end item -->
|
||||||
|
<item name="endItemStartPadding">0dp</item>
|
||||||
|
<!-- 8dp end padding for the end item -->
|
||||||
|
<item name="endItemEndPadding">8dp</item>
|
||||||
|
|
||||||
|
<item name="titleSize">20sp</item>
|
||||||
|
<!-- Align text with slider -->
|
||||||
|
<item name="titleStartPadding">11dp</item>
|
||||||
|
<item name="subContentStartPadding">11dp</item>
|
||||||
|
|
||||||
|
<!-- Padding for indeterminate progress bar -->
|
||||||
|
<item name="progressBarStartPadding">12dp</item>
|
||||||
|
<item name="progressBarEndPadding">16dp</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
|
<style name="TextAppearance.Dialog.Title" parent="@android:style/TextAppearance.DeviceDefault.Large">
|
||||||
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
<item name="android:textColor">?android:attr/textColorPrimary</item>
|
||||||
<item name="android:textSize">24sp</item>
|
<item name="android:textSize">24sp</item>
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
|
|||||||
import com.android.systemui.screenshot.ActionProxyReceiver;
|
import com.android.systemui.screenshot.ActionProxyReceiver;
|
||||||
import com.android.systemui.screenshot.DeleteScreenshotReceiver;
|
import com.android.systemui.screenshot.DeleteScreenshotReceiver;
|
||||||
import com.android.systemui.screenshot.SmartActionsReceiver;
|
import com.android.systemui.screenshot.SmartActionsReceiver;
|
||||||
|
import com.android.systemui.volume.VolumePanelDialogReceiver;
|
||||||
|
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
@@ -73,6 +74,15 @@ public abstract class DefaultBroadcastReceiverBinder {
|
|||||||
public abstract BroadcastReceiver bindMediaOutputDialogReceiver(
|
public abstract BroadcastReceiver bindMediaOutputDialogReceiver(
|
||||||
MediaOutputDialogReceiver broadcastReceiver);
|
MediaOutputDialogReceiver broadcastReceiver);
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(VolumePanelDialogReceiver.class)
|
||||||
|
public abstract BroadcastReceiver bindVolumePanelDialogReceiver(
|
||||||
|
VolumePanelDialogReceiver broadcastReceiver);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -48,7 +48,6 @@ import android.app.KeyguardManager;
|
|||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.ColorStateList;
|
import android.content.res.ColorStateList;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
@@ -248,6 +247,7 @@ public class VolumeDialogImpl implements VolumeDialog,
|
|||||||
|
|
||||||
private final ConfigurationController mConfigurationController;
|
private final ConfigurationController mConfigurationController;
|
||||||
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
|
private final MediaOutputDialogFactory mMediaOutputDialogFactory;
|
||||||
|
private final VolumePanelFactory mVolumePanelFactory;
|
||||||
private final ActivityStarter mActivityStarter;
|
private final ActivityStarter mActivityStarter;
|
||||||
|
|
||||||
private boolean mShowing;
|
private boolean mShowing;
|
||||||
@@ -279,6 +279,7 @@ public class VolumeDialogImpl implements VolumeDialog,
|
|||||||
DeviceProvisionedController deviceProvisionedController,
|
DeviceProvisionedController deviceProvisionedController,
|
||||||
ConfigurationController configurationController,
|
ConfigurationController configurationController,
|
||||||
MediaOutputDialogFactory mediaOutputDialogFactory,
|
MediaOutputDialogFactory mediaOutputDialogFactory,
|
||||||
|
VolumePanelFactory volumePanelFactory,
|
||||||
ActivityStarter activityStarter,
|
ActivityStarter activityStarter,
|
||||||
InteractionJankMonitor interactionJankMonitor) {
|
InteractionJankMonitor interactionJankMonitor) {
|
||||||
mContext =
|
mContext =
|
||||||
@@ -290,6 +291,7 @@ public class VolumeDialogImpl implements VolumeDialog,
|
|||||||
mDeviceProvisionedController = deviceProvisionedController;
|
mDeviceProvisionedController = deviceProvisionedController;
|
||||||
mConfigurationController = configurationController;
|
mConfigurationController = configurationController;
|
||||||
mMediaOutputDialogFactory = mediaOutputDialogFactory;
|
mMediaOutputDialogFactory = mediaOutputDialogFactory;
|
||||||
|
mVolumePanelFactory = volumePanelFactory;
|
||||||
mActivityStarter = activityStarter;
|
mActivityStarter = activityStarter;
|
||||||
mShowActiveStreamOnly = showActiveStreamOnly();
|
mShowActiveStreamOnly = showActiveStreamOnly();
|
||||||
mHasSeenODICaptionsTooltip =
|
mHasSeenODICaptionsTooltip =
|
||||||
@@ -1043,10 +1045,9 @@ public class VolumeDialogImpl implements VolumeDialog,
|
|||||||
if (mSettingsIcon != null) {
|
if (mSettingsIcon != null) {
|
||||||
mSettingsIcon.setOnClickListener(v -> {
|
mSettingsIcon.setOnClickListener(v -> {
|
||||||
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
|
Events.writeEvent(Events.EVENT_SETTINGS_CLICK);
|
||||||
Intent intent = new Intent(Settings.Panel.ACTION_VOLUME);
|
|
||||||
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
|
dismissH(DISMISS_REASON_SETTINGS_CLICKED);
|
||||||
mMediaOutputDialogFactory.dismiss();
|
mMediaOutputDialogFactory.dismiss();
|
||||||
mActivityStarter.startActivity(intent, true /* dismissShade */);
|
mVolumePanelFactory.create(true /* aboveStatusBar */, null);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.volume;
|
||||||
|
|
||||||
|
import android.bluetooth.BluetoothDevice;
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.provider.SettingsSlicesContract;
|
||||||
|
import android.text.TextUtils;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
import android.widget.Button;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.Lifecycle;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LifecycleRegistry;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.slice.Slice;
|
||||||
|
import androidx.slice.SliceMetadata;
|
||||||
|
import androidx.slice.widget.EventInfo;
|
||||||
|
import androidx.slice.widget.SliceLiveData;
|
||||||
|
|
||||||
|
import com.android.settingslib.bluetooth.A2dpProfile;
|
||||||
|
import com.android.settingslib.bluetooth.BluetoothUtils;
|
||||||
|
import com.android.settingslib.bluetooth.LocalBluetoothManager;
|
||||||
|
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
|
||||||
|
import com.android.settingslib.media.MediaOutputConstants;
|
||||||
|
import com.android.systemui.R;
|
||||||
|
import com.android.systemui.statusbar.phone.SystemUIDialog;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visual presentation of the volume panel dialog.
|
||||||
|
*/
|
||||||
|
public class VolumePanelDialog extends SystemUIDialog implements LifecycleOwner {
|
||||||
|
private static final String TAG = "VolumePanelDialog";
|
||||||
|
|
||||||
|
private static final int DURATION_SLICE_BINDING_TIMEOUT_MS = 200;
|
||||||
|
private static final int DEFAULT_SLICE_SIZE = 4;
|
||||||
|
|
||||||
|
private RecyclerView mVolumePanelSlices;
|
||||||
|
private VolumePanelSlicesAdapter mVolumePanelSlicesAdapter;
|
||||||
|
private final LifecycleRegistry mLifecycleRegistry;
|
||||||
|
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||||
|
private final Map<Uri, LiveData<Slice>> mSliceLiveData = new LinkedHashMap<>();
|
||||||
|
private final HashSet<Uri> mLoadedSlices = new HashSet<>();
|
||||||
|
private boolean mSlicesReadyToLoad;
|
||||||
|
private LocalBluetoothProfileManager mProfileManager;
|
||||||
|
|
||||||
|
public VolumePanelDialog(Context context, boolean aboveStatusBar) {
|
||||||
|
super(context);
|
||||||
|
mLifecycleRegistry = new LifecycleRegistry(this);
|
||||||
|
if (!aboveStatusBar) {
|
||||||
|
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
Log.d(TAG, "onCreate");
|
||||||
|
|
||||||
|
View dialogView = LayoutInflater.from(getContext()).inflate(R.layout.volume_panel_dialog,
|
||||||
|
null);
|
||||||
|
final Window window = getWindow();
|
||||||
|
window.setContentView(dialogView);
|
||||||
|
|
||||||
|
Button doneButton = dialogView.findViewById(R.id.done_button);
|
||||||
|
doneButton.setOnClickListener(v -> dismiss());
|
||||||
|
Button settingsButton = dialogView.findViewById(R.id.settings_button);
|
||||||
|
settingsButton.setOnClickListener(v -> {
|
||||||
|
getContext().startActivity(new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(
|
||||||
|
Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||||
|
dismiss();
|
||||||
|
});
|
||||||
|
|
||||||
|
LocalBluetoothManager localBluetoothManager = LocalBluetoothManager.getInstance(
|
||||||
|
getContext(), null);
|
||||||
|
if (localBluetoothManager != null) {
|
||||||
|
mProfileManager = localBluetoothManager.getProfileManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
mVolumePanelSlices = dialogView.findViewById(R.id.volume_panel_parent_layout);
|
||||||
|
mVolumePanelSlices.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|
||||||
|
loadAllSlices();
|
||||||
|
|
||||||
|
mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadAllSlices() {
|
||||||
|
mSliceLiveData.clear();
|
||||||
|
mLoadedSlices.clear();
|
||||||
|
final List<Uri> sliceUris = getSlices();
|
||||||
|
|
||||||
|
for (Uri uri : sliceUris) {
|
||||||
|
final LiveData<Slice> sliceLiveData = SliceLiveData.fromUri(getContext(), uri,
|
||||||
|
(int type, Throwable source) -> {
|
||||||
|
if (!removeSliceLiveData(uri)) {
|
||||||
|
mLoadedSlices.add(uri);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add slice first to make it in order. Will remove it later if there's an error.
|
||||||
|
mSliceLiveData.put(uri, sliceLiveData);
|
||||||
|
|
||||||
|
sliceLiveData.observe(this, slice -> {
|
||||||
|
if (mLoadedSlices.contains(uri)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.d(TAG, "received slice: " + (slice == null ? null : slice.getUri()));
|
||||||
|
final SliceMetadata metadata = SliceMetadata.from(getContext(), slice);
|
||||||
|
if (slice == null || metadata.isErrorSlice()) {
|
||||||
|
if (!removeSliceLiveData(uri)) {
|
||||||
|
mLoadedSlices.add(uri);
|
||||||
|
}
|
||||||
|
} else if (metadata.getLoadingState() == SliceMetadata.LOADED_ALL) {
|
||||||
|
mLoadedSlices.add(uri);
|
||||||
|
} else {
|
||||||
|
mHandler.postDelayed(() -> {
|
||||||
|
mLoadedSlices.add(uri);
|
||||||
|
setupAdapterWhenReady();
|
||||||
|
}, DURATION_SLICE_BINDING_TIMEOUT_MS);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupAdapterWhenReady();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupAdapterWhenReady() {
|
||||||
|
if (mLoadedSlices.size() == mSliceLiveData.size() && !mSlicesReadyToLoad) {
|
||||||
|
mSlicesReadyToLoad = true;
|
||||||
|
mVolumePanelSlicesAdapter = new VolumePanelSlicesAdapter(this, mSliceLiveData);
|
||||||
|
mVolumePanelSlicesAdapter.setOnSliceActionListener((eventInfo, sliceItem) -> {
|
||||||
|
if (eventInfo.actionType == EventInfo.ACTION_TYPE_SLIDER) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.dismiss();
|
||||||
|
});
|
||||||
|
if (mSliceLiveData.size() < DEFAULT_SLICE_SIZE) {
|
||||||
|
mVolumePanelSlices.setMinimumHeight(0);
|
||||||
|
}
|
||||||
|
mVolumePanelSlices.setAdapter(mVolumePanelSlicesAdapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean removeSliceLiveData(Uri uri) {
|
||||||
|
boolean removed = false;
|
||||||
|
// Keeps observe media output slice
|
||||||
|
if (!uri.equals(MEDIA_OUTPUT_INDICATOR_SLICE_URI)) {
|
||||||
|
Log.d(TAG, "remove uri: " + uri);
|
||||||
|
removed = mSliceLiveData.remove(uri) != null;
|
||||||
|
if (mVolumePanelSlicesAdapter != null) {
|
||||||
|
mVolumePanelSlicesAdapter.updateDataSet(new ArrayList<>(mSliceLiveData.values()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return removed;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
Log.d(TAG, "onStart");
|
||||||
|
mLifecycleRegistry.setCurrentState(Lifecycle.State.STARTED);
|
||||||
|
mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
Log.d(TAG, "onStop");
|
||||||
|
mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Uri> getSlices() {
|
||||||
|
final List<Uri> uris = new ArrayList<>();
|
||||||
|
uris.add(REMOTE_MEDIA_SLICE_URI);
|
||||||
|
uris.add(VOLUME_MEDIA_URI);
|
||||||
|
Uri controlUri = getExtraControlUri();
|
||||||
|
if (controlUri != null) {
|
||||||
|
Log.d(TAG, "add extra control slice");
|
||||||
|
uris.add(controlUri);
|
||||||
|
}
|
||||||
|
uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI);
|
||||||
|
uris.add(VOLUME_CALL_URI);
|
||||||
|
uris.add(VOLUME_RINGER_URI);
|
||||||
|
uris.add(VOLUME_ALARM_URI);
|
||||||
|
return uris;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String SETTINGS_SLICE_AUTHORITY = "com.android.settings.slices";
|
||||||
|
private static final Uri REMOTE_MEDIA_SLICE_URI = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(SETTINGS_SLICE_AUTHORITY)
|
||||||
|
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
|
||||||
|
.appendPath(MediaOutputConstants.KEY_REMOTE_MEDIA)
|
||||||
|
.build();
|
||||||
|
private static final Uri VOLUME_MEDIA_URI = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(SETTINGS_SLICE_AUTHORITY)
|
||||||
|
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
|
||||||
|
.appendPath("media_volume")
|
||||||
|
.build();
|
||||||
|
private static final Uri MEDIA_OUTPUT_INDICATOR_SLICE_URI = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(SETTINGS_SLICE_AUTHORITY)
|
||||||
|
.appendPath(SettingsSlicesContract.PATH_SETTING_INTENT)
|
||||||
|
.appendPath("media_output_indicator")
|
||||||
|
.build();
|
||||||
|
private static final Uri VOLUME_CALL_URI = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(SETTINGS_SLICE_AUTHORITY)
|
||||||
|
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
|
||||||
|
.appendPath("call_volume")
|
||||||
|
.build();
|
||||||
|
private static final Uri VOLUME_RINGER_URI = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(SETTINGS_SLICE_AUTHORITY)
|
||||||
|
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
|
||||||
|
.appendPath("ring_volume")
|
||||||
|
.build();
|
||||||
|
private static final Uri VOLUME_ALARM_URI = new Uri.Builder()
|
||||||
|
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||||
|
.authority(SETTINGS_SLICE_AUTHORITY)
|
||||||
|
.appendPath(SettingsSlicesContract.PATH_SETTING_ACTION)
|
||||||
|
.appendPath("alarm_volume")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private Uri getExtraControlUri() {
|
||||||
|
Uri controlUri = null;
|
||||||
|
final BluetoothDevice bluetoothDevice = findActiveDevice();
|
||||||
|
if (bluetoothDevice != null) {
|
||||||
|
// The control slice width = dialog width - horizontal padding of two sides
|
||||||
|
final int dialogWidth =
|
||||||
|
getWindow().getWindowManager().getCurrentWindowMetrics().getBounds().width();
|
||||||
|
final int controlSliceWidth = dialogWidth
|
||||||
|
- getContext().getResources().getDimensionPixelSize(
|
||||||
|
R.dimen.volume_panel_slice_horizontal_padding) * 2;
|
||||||
|
final String uri = BluetoothUtils.getControlUriMetaData(bluetoothDevice);
|
||||||
|
if (!TextUtils.isEmpty(uri)) {
|
||||||
|
try {
|
||||||
|
controlUri = Uri.parse(uri + controlSliceWidth);
|
||||||
|
} catch (NullPointerException exception) {
|
||||||
|
Log.d(TAG, "unable to parse extra control uri");
|
||||||
|
controlUri = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return controlUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothDevice findActiveDevice() {
|
||||||
|
if (mProfileManager != null) {
|
||||||
|
final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile();
|
||||||
|
if (a2dpProfile != null) {
|
||||||
|
return a2dpProfile.getActiveDevice();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Lifecycle getLifecycle() {
|
||||||
|
return mLifecycleRegistry;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.volume
|
||||||
|
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.provider.Settings
|
||||||
|
import android.text.TextUtils
|
||||||
|
import android.util.Log
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val TAG = "VolumePanelDialogReceiver"
|
||||||
|
private const val LAUNCH_ACTION = "com.android.systemui.action.LAUNCH_VOLUME_PANEL_DIALOG"
|
||||||
|
private const val DISMISS_ACTION = "com.android.systemui.action.DISMISS_VOLUME_PANEL_DIALOG"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BroadcastReceiver for handling volume panel dialog intent
|
||||||
|
*/
|
||||||
|
class VolumePanelDialogReceiver @Inject constructor(
|
||||||
|
private val volumePanelFactory: VolumePanelFactory
|
||||||
|
) : BroadcastReceiver() {
|
||||||
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
|
Log.d(TAG, "onReceive intent" + intent.action)
|
||||||
|
if (TextUtils.equals(LAUNCH_ACTION, intent.action) ||
|
||||||
|
TextUtils.equals(Settings.Panel.ACTION_VOLUME, intent.action)) {
|
||||||
|
volumePanelFactory.create(true, null)
|
||||||
|
} else if (TextUtils.equals(DISMISS_ACTION, intent.action)) {
|
||||||
|
volumePanelFactory.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.volume
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import com.android.systemui.animation.DialogLaunchAnimator
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private const val TAG = "VolumePanelFactory"
|
||||||
|
private val DEBUG = Log.isLoggable(TAG, Log.DEBUG)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory to create [VolumePanelDialog] objects. This is the dialog that allows the user to adjust
|
||||||
|
* multiple streams with sliders.
|
||||||
|
*/
|
||||||
|
@SysUISingleton
|
||||||
|
class VolumePanelFactory @Inject constructor(
|
||||||
|
private val context: Context,
|
||||||
|
private val dialogLaunchAnimator: DialogLaunchAnimator
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
var volumePanelDialog: VolumePanelDialog? = null
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a [VolumePanelDialog]. The dialog will be animated from [view] if it is not null. */
|
||||||
|
fun create(aboveStatusBar: Boolean, view: View? = null) {
|
||||||
|
if (volumePanelDialog?.isShowing == true) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = VolumePanelDialog(context, aboveStatusBar)
|
||||||
|
volumePanelDialog = dialog
|
||||||
|
|
||||||
|
// Show the dialog.
|
||||||
|
if (view != null) {
|
||||||
|
dialogLaunchAnimator.showFromView(dialog, view, animateBackgroundBoundsChange = true)
|
||||||
|
} else {
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Dismiss [VolumePanelDialog] if exist. */
|
||||||
|
fun dismiss() {
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG, "dismiss dialog")
|
||||||
|
}
|
||||||
|
volumePanelDialog?.dismiss()
|
||||||
|
volumePanelDialog = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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.volume;
|
||||||
|
|
||||||
|
import static android.app.slice.Slice.HINT_ERROR;
|
||||||
|
import static android.app.slice.SliceItem.FORMAT_SLICE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
import androidx.slice.Slice;
|
||||||
|
import androidx.slice.SliceItem;
|
||||||
|
import androidx.slice.widget.SliceView;
|
||||||
|
|
||||||
|
import com.android.systemui.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RecyclerView adapter for Slices in Settings Panels.
|
||||||
|
*/
|
||||||
|
public class VolumePanelSlicesAdapter extends
|
||||||
|
RecyclerView.Adapter<VolumePanelSlicesAdapter.SliceRowViewHolder> {
|
||||||
|
|
||||||
|
private final List<LiveData<Slice>> mSliceLiveData;
|
||||||
|
private final LifecycleOwner mLifecycleOwner;
|
||||||
|
private SliceView.OnSliceActionListener mOnSliceActionListener;
|
||||||
|
|
||||||
|
public VolumePanelSlicesAdapter(LifecycleOwner lifecycleOwner,
|
||||||
|
Map<Uri, LiveData<Slice>> sliceLiveData) {
|
||||||
|
mLifecycleOwner = lifecycleOwner;
|
||||||
|
mSliceLiveData = new ArrayList<>(sliceLiveData.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public SliceRowViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
|
||||||
|
final Context context = viewGroup.getContext();
|
||||||
|
final LayoutInflater inflater = LayoutInflater.from(context);
|
||||||
|
View view = inflater.inflate(R.layout.volume_panel_slice_slider_row, viewGroup, false);
|
||||||
|
return new SliceRowViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(@NonNull SliceRowViewHolder sliceRowViewHolder, int position) {
|
||||||
|
sliceRowViewHolder.onBind(mSliceLiveData.get(position), position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return mSliceLiveData.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemViewType(int position) {
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setOnSliceActionListener(SliceView.OnSliceActionListener listener) {
|
||||||
|
mOnSliceActionListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateDataSet(ArrayList<LiveData<Slice>> list) {
|
||||||
|
mSliceLiveData.clear();
|
||||||
|
mSliceLiveData.addAll(list);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewHolder for binding Slices to SliceViews.
|
||||||
|
*/
|
||||||
|
public class SliceRowViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
|
private final SliceView mSliceView;
|
||||||
|
|
||||||
|
public SliceRowViewHolder(View view) {
|
||||||
|
super(view);
|
||||||
|
mSliceView = view.findViewById(R.id.slice_view);
|
||||||
|
mSliceView.setMode(SliceView.MODE_LARGE);
|
||||||
|
mSliceView.setShowTitleItems(true);
|
||||||
|
mSliceView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
|
||||||
|
mSliceView.setOnSliceActionListener(mOnSliceActionListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the view is displayed.
|
||||||
|
*/
|
||||||
|
public void onBind(LiveData<Slice> sliceLiveData, int position) {
|
||||||
|
sliceLiveData.observe(mLifecycleOwner, mSliceView);
|
||||||
|
|
||||||
|
// Do not show the divider above media devices switcher slice per request
|
||||||
|
final Slice slice = sliceLiveData.getValue();
|
||||||
|
|
||||||
|
// Hides slice which reports with error hint or not contain any slice sub-item.
|
||||||
|
if (slice == null || !isValidSlice(slice)) {
|
||||||
|
mSliceView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
mSliceView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidSlice(Slice slice) {
|
||||||
|
if (slice.getHints().contains(HINT_ERROR)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (SliceItem item : slice.getItems()) {
|
||||||
|
if (item.getFormat().equals(FORMAT_SLICE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.policy.DeviceProvisionedController;
|
|||||||
import com.android.systemui.volume.VolumeComponent;
|
import com.android.systemui.volume.VolumeComponent;
|
||||||
import com.android.systemui.volume.VolumeDialogComponent;
|
import com.android.systemui.volume.VolumeDialogComponent;
|
||||||
import com.android.systemui.volume.VolumeDialogImpl;
|
import com.android.systemui.volume.VolumeDialogImpl;
|
||||||
|
import com.android.systemui.volume.VolumePanelFactory;
|
||||||
|
|
||||||
import dagger.Binds;
|
import dagger.Binds;
|
||||||
import dagger.Module;
|
import dagger.Module;
|
||||||
@@ -52,6 +53,7 @@ public interface VolumeModule {
|
|||||||
DeviceProvisionedController deviceProvisionedController,
|
DeviceProvisionedController deviceProvisionedController,
|
||||||
ConfigurationController configurationController,
|
ConfigurationController configurationController,
|
||||||
MediaOutputDialogFactory mediaOutputDialogFactory,
|
MediaOutputDialogFactory mediaOutputDialogFactory,
|
||||||
|
VolumePanelFactory volumePanelFactory,
|
||||||
ActivityStarter activityStarter,
|
ActivityStarter activityStarter,
|
||||||
InteractionJankMonitor interactionJankMonitor) {
|
InteractionJankMonitor interactionJankMonitor) {
|
||||||
VolumeDialogImpl impl = new VolumeDialogImpl(
|
VolumeDialogImpl impl = new VolumeDialogImpl(
|
||||||
@@ -61,6 +63,7 @@ public interface VolumeModule {
|
|||||||
deviceProvisionedController,
|
deviceProvisionedController,
|
||||||
configurationController,
|
configurationController,
|
||||||
mediaOutputDialogFactory,
|
mediaOutputDialogFactory,
|
||||||
|
volumePanelFactory,
|
||||||
activityStarter,
|
activityStarter,
|
||||||
interactionJankMonitor);
|
interactionJankMonitor);
|
||||||
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
|
impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
|
||||||
|
|||||||
@@ -85,6 +85,8 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
MediaOutputDialogFactory mMediaOutputDialogFactory;
|
MediaOutputDialogFactory mMediaOutputDialogFactory;
|
||||||
@Mock
|
@Mock
|
||||||
|
VolumePanelFactory mVolumePanelFactory;
|
||||||
|
@Mock
|
||||||
ActivityStarter mActivityStarter;
|
ActivityStarter mActivityStarter;
|
||||||
@Mock
|
@Mock
|
||||||
InteractionJankMonitor mInteractionJankMonitor;
|
InteractionJankMonitor mInteractionJankMonitor;
|
||||||
@@ -102,6 +104,7 @@ public class VolumeDialogImplTest extends SysuiTestCase {
|
|||||||
mDeviceProvisionedController,
|
mDeviceProvisionedController,
|
||||||
mConfigurationController,
|
mConfigurationController,
|
||||||
mMediaOutputDialogFactory,
|
mMediaOutputDialogFactory,
|
||||||
|
mVolumePanelFactory,
|
||||||
mActivityStarter,
|
mActivityStarter,
|
||||||
mInteractionJankMonitor);
|
mInteractionJankMonitor);
|
||||||
mDialog.init(0, null);
|
mDialog.init(0, null);
|
||||||
|
|||||||
Reference in New Issue
Block a user