1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.panel; 18 19 import static androidx.lifecycle.Lifecycle.Event.ON_PAUSE; 20 import static androidx.lifecycle.Lifecycle.Event.ON_RESUME; 21 22 import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI; 23 import static com.android.settings.slices.CustomSliceRegistry.REMOTE_MEDIA_SLICE_URI; 24 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_ALARM_URI; 25 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_CALL_URI; 26 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_MEDIA_URI; 27 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_NOTIFICATION_URI; 28 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_RINGER_URI; 29 import static com.android.settings.slices.CustomSliceRegistry.VOLUME_SEPARATE_RING_URI; 30 31 import android.app.Activity; 32 import android.app.settings.SettingsEnums; 33 import android.bluetooth.BluetoothDevice; 34 import android.content.BroadcastReceiver; 35 import android.content.Context; 36 import android.content.Intent; 37 import android.content.IntentFilter; 38 import android.net.Uri; 39 import android.provider.Settings; 40 import android.text.TextUtils; 41 import android.util.Log; 42 43 import androidx.lifecycle.LifecycleObserver; 44 import androidx.lifecycle.OnLifecycleEvent; 45 46 import com.android.settings.R; 47 import com.android.settings.bluetooth.Utils; 48 import com.android.settingslib.bluetooth.A2dpProfile; 49 import com.android.settingslib.bluetooth.BluetoothUtils; 50 import com.android.settingslib.bluetooth.LocalBluetoothManager; 51 import com.android.settingslib.bluetooth.LocalBluetoothProfileManager; 52 import com.android.settingslib.media.MediaOutputConstants; 53 54 import java.util.ArrayList; 55 import java.util.List; 56 import java.util.concurrent.ExecutionException; 57 import java.util.concurrent.FutureTask; 58 59 /** 60 * Panel data class for Volume settings. 61 */ 62 public class VolumePanel implements PanelContent, LifecycleObserver { 63 private static final String TAG = "VolumePanel"; 64 65 private final Context mContext; 66 67 private PanelContentCallback mCallback; 68 private LocalBluetoothProfileManager mProfileManager; 69 private int mControlSliceWidth; 70 71 private final BroadcastReceiver mReceiver = new BroadcastReceiver() { 72 @Override 73 public void onReceive(Context context, Intent intent) { 74 if (MediaOutputConstants.ACTION_CLOSE_PANEL.equals(intent.getAction())) { 75 mCallback.forceClose(); 76 } 77 } 78 }; 79 create(Context context)80 public static VolumePanel create(Context context) { 81 return new VolumePanel(context); 82 } 83 VolumePanel(Context context)84 private VolumePanel(Context context) { 85 mContext = context.getApplicationContext(); 86 if (context instanceof Activity) { 87 int panelWidth = 88 ((Activity) context).getWindowManager().getCurrentWindowMetrics().getBounds() 89 .width(); 90 // The control slice width = panel width - two left and right horizontal paddings 91 mControlSliceWidth = panelWidth - context.getResources().getDimensionPixelSize( 92 R.dimen.panel_slice_Horizontal_padding) * 2; 93 } 94 95 final FutureTask<LocalBluetoothManager> localBtManagerFutureTask = new FutureTask<>( 96 // Avoid StrictMode ThreadPolicy violation 97 () -> Utils.getLocalBtManager(mContext)); 98 LocalBluetoothManager localBluetoothManager; 99 try { 100 localBtManagerFutureTask.run(); 101 localBluetoothManager = localBtManagerFutureTask.get(); 102 } catch (InterruptedException | ExecutionException e) { 103 Log.w(TAG, "Error getting LocalBluetoothManager."); 104 return; 105 } 106 if (localBluetoothManager != null) { 107 mProfileManager = localBluetoothManager.getProfileManager(); 108 } 109 } 110 111 /** Invoked when the panel is resumed. */ 112 @OnLifecycleEvent(ON_RESUME) onResume()113 public void onResume() { 114 final IntentFilter filter = new IntentFilter(); 115 filter.addAction(MediaOutputConstants.ACTION_CLOSE_PANEL); 116 mContext.registerReceiver(mReceiver, filter, Context.RECEIVER_EXPORTED_UNAUDITED); 117 } 118 119 /** Invoked when the panel is paused. */ 120 @OnLifecycleEvent(ON_PAUSE) onPause()121 public void onPause() { 122 mContext.unregisterReceiver(mReceiver); 123 } 124 125 @Override getTitle()126 public CharSequence getTitle() { 127 return mContext.getText(R.string.sound_settings); 128 } 129 130 /** 131 * When considering ring and notification, we include all controllers unconditionally and rely 132 * on getAvailability to govern visibility 133 */ 134 @Override getSlices()135 public List<Uri> getSlices() { 136 final List<Uri> uris = new ArrayList<>(); 137 138 uris.add(REMOTE_MEDIA_SLICE_URI); 139 uris.add(VOLUME_MEDIA_URI); 140 Uri controlUri = getExtraControlUri(); 141 if (controlUri != null) { 142 Log.d(TAG, "add extra control slice"); 143 uris.add(controlUri); 144 } 145 uris.add(MEDIA_OUTPUT_INDICATOR_SLICE_URI); 146 uris.add(VOLUME_CALL_URI); 147 uris.add(VOLUME_RINGER_URI); 148 uris.add(VOLUME_SEPARATE_RING_URI); 149 uris.add(VOLUME_NOTIFICATION_URI); 150 uris.add(VOLUME_ALARM_URI); 151 return uris; 152 } 153 154 @Override getSeeMoreIntent()155 public Intent getSeeMoreIntent() { 156 return new Intent(Settings.ACTION_SOUND_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 157 } 158 159 @Override getMetricsCategory()160 public int getMetricsCategory() { 161 return SettingsEnums.PANEL_VOLUME; 162 } 163 164 @Override getViewType()165 public int getViewType() { 166 return PanelContent.VIEW_TYPE_SLIDER; 167 } 168 169 @Override registerCallback(PanelContentCallback callback)170 public void registerCallback(PanelContentCallback callback) { 171 mCallback = callback; 172 } 173 getExtraControlUri()174 private Uri getExtraControlUri() { 175 Uri controlUri = null; 176 final BluetoothDevice bluetoothDevice = findActiveDevice(); 177 if (bluetoothDevice != null) { 178 final String uri = BluetoothUtils.getControlUriMetaData(bluetoothDevice); 179 if (!TextUtils.isEmpty(uri)) { 180 try { 181 controlUri = Uri.parse(uri + mControlSliceWidth); 182 } catch (NullPointerException exception) { 183 Log.d(TAG, "unable to parse uri"); 184 controlUri = null; 185 } 186 } 187 } 188 return controlUri; 189 } 190 findActiveDevice()191 private BluetoothDevice findActiveDevice() { 192 if (mProfileManager != null) { 193 final A2dpProfile a2dpProfile = mProfileManager.getA2dpProfile(); 194 if (a2dpProfile != null) { 195 return a2dpProfile.getActiveDevice(); 196 } 197 } 198 return null; 199 } 200 } 201