diff options
Diffstat (limited to 'packages/SystemUI/src')
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java | 445 |
1 files changed, 445 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java new file mode 100644 index 000000000000..64d20a273931 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -0,0 +1,445 @@ +/* + * 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.media.dialog; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.media.MediaMetadata; +import android.media.RoutingSessionInfo; +import android.media.session.MediaController; +import android.media.session.MediaSessionManager; +import android.media.session.PlaybackState; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; +import androidx.core.graphics.drawable.IconCompat; + +import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.Utils; +import com.android.settingslib.bluetooth.BluetoothUtils; +import com.android.settingslib.bluetooth.LocalBluetoothManager; +import com.android.settingslib.media.InfoMediaManager; +import com.android.settingslib.media.LocalMediaManager; +import com.android.settingslib.media.MediaDevice; +import com.android.settingslib.media.MediaOutputSliceConstants; +import com.android.settingslib.utils.ThreadUtils; +import com.android.systemui.R; +import com.android.systemui.plugins.ActivityStarter; +import com.android.systemui.statusbar.phone.ShadeController; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import javax.inject.Inject; + +/** + * Controller for media output dialog + */ +public class MediaOutputController implements LocalMediaManager.DeviceCallback{ + + private static final String TAG = "MediaOutputController"; + private static final boolean DEBUG = false; + + private final String mPackageName; + private final Context mContext; + private final MediaSessionManager mMediaSessionManager; + private final ShadeController mShadeController; + private final ActivityStarter mActivityStarter; + @VisibleForTesting + final List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>(); + + private MediaController mMediaController; + @VisibleForTesting + Callback mCallback; + @VisibleForTesting + LocalMediaManager mLocalMediaManager; + + @Inject + public MediaOutputController(@NonNull Context context, String packageName, + MediaSessionManager mediaSessionManager, LocalBluetoothManager + lbm, ShadeController shadeController, ActivityStarter starter) { + mContext = context; + mPackageName = packageName; + mMediaSessionManager = mediaSessionManager; + mShadeController = shadeController; + mActivityStarter = starter; + InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm); + mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName); + } + + void start(@NonNull Callback cb) { + mMediaDevices.clear(); + if (!TextUtils.isEmpty(mPackageName)) { + for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) { + if (TextUtils.equals(controller.getPackageName(), mPackageName)) { + mMediaController = controller; + mMediaController.unregisterCallback(mCb); + mMediaController.registerCallback(mCb); + break; + } + } + } + if (mMediaController == null) { + if (DEBUG) { + Log.d(TAG, "No media controller for " + mPackageName); + } + } + if (mLocalMediaManager == null) { + if (DEBUG) { + Log.d(TAG, "No local media manager " + mPackageName); + } + return; + } + mCallback = cb; + mLocalMediaManager.unregisterCallback(this); + mLocalMediaManager.stopScan(); + mLocalMediaManager.registerCallback(this); + mLocalMediaManager.startScan(); + } + + void stop() { + if (mMediaController != null) { + mMediaController.unregisterCallback(mCb); + } + if (mLocalMediaManager != null) { + mLocalMediaManager.unregisterCallback(this); + mLocalMediaManager.stopScan(); + } + mMediaDevices.clear(); + } + + @Override + public void onDeviceListUpdate(List<MediaDevice> devices) { + buildMediaDevices(devices); + mCallback.onRouteChanged(); + } + + @Override + public void onSelectedDeviceStateChanged(MediaDevice device, + @LocalMediaManager.MediaDeviceState int state) { + mCallback.onRouteChanged(); + } + + @Override + public void onDeviceAttributesChanged() { + mCallback.onRouteChanged(); + } + + @Override + public void onRequestFailed(int reason) { + mCallback.onRouteChanged(); + } + + CharSequence getHeaderTitle() { + if (mMediaController != null) { + final MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata != null) { + return metadata.getDescription().getTitle(); + } + } + return mContext.getText(R.string.controls_media_title); + } + + CharSequence getHeaderSubTitle() { + if (mMediaController == null) { + return null; + } + final MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata == null) { + return null; + } + return metadata.getDescription().getSubtitle(); + } + + IconCompat getHeaderIcon() { + if (mMediaController == null) { + return null; + } + final MediaMetadata metadata = mMediaController.getMetadata(); + if (metadata != null) { + final Bitmap bitmap = metadata.getDescription().getIconBitmap(); + if (bitmap != null) { + final Bitmap roundBitmap = Utils.convertCornerRadiusBitmap(mContext, bitmap, + (float) mContext.getResources().getDimensionPixelSize( + R.dimen.media_output_dialog_icon_corner_radius)); + return IconCompat.createWithBitmap(roundBitmap); + } + } + if (DEBUG) { + Log.d(TAG, "Media meta data does not contain icon information"); + } + return getPackageIcon(); + } + + IconCompat getDeviceIconCompat(MediaDevice device) { + Drawable drawable = device.getIcon(); + if (drawable == null) { + if (DEBUG) { + Log.d(TAG, "getDeviceIconCompat() device : " + device.getName() + + ", drawable is null"); + } + // Use default Bluetooth device icon to handle getIcon() is null case. + drawable = mContext.getDrawable(com.android.internal.R.drawable.ic_bt_headphones_a2dp); + } + return BluetoothUtils.createIconWithDrawable(drawable); + } + + private IconCompat getPackageIcon() { + if (TextUtils.isEmpty(mPackageName)) { + return null; + } + try { + final Drawable drawable = mContext.getPackageManager().getApplicationIcon(mPackageName); + if (drawable instanceof BitmapDrawable) { + return IconCompat.createWithBitmap(((BitmapDrawable) drawable).getBitmap()); + } + final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + return IconCompat.createWithBitmap(bitmap); + } catch (PackageManager.NameNotFoundException e) { + if (DEBUG) { + Log.e(TAG, "Package is not found. Unable to get package icon."); + } + } + return null; + } + + private void buildMediaDevices(List<MediaDevice> devices) { + // For the first time building list, to make sure the top device is the connected device. + if (mMediaDevices.isEmpty()) { + final MediaDevice connectedMediaDevice = getCurrentConnectedMediaDevice(); + if (connectedMediaDevice == null) { + if (DEBUG) { + Log.d(TAG, "No connected media device."); + } + mMediaDevices.addAll(devices); + return; + } + for (MediaDevice device : devices) { + if (TextUtils.equals(device.getId(), connectedMediaDevice.getId())) { + mMediaDevices.add(0, device); + } else { + mMediaDevices.add(device); + } + } + return; + } + // To keep the same list order + final Collection<MediaDevice> targetMediaDevices = new ArrayList<>(); + for (MediaDevice originalDevice : mMediaDevices) { + for (MediaDevice newDevice : devices) { + if (TextUtils.equals(originalDevice.getId(), newDevice.getId())) { + targetMediaDevices.add(newDevice); + break; + } + } + } + if (targetMediaDevices.size() != devices.size()) { + devices.removeAll(targetMediaDevices); + targetMediaDevices.addAll(devices); + } + mMediaDevices.clear(); + mMediaDevices.addAll(targetMediaDevices); + } + + void connectDevice(MediaDevice device) { + ThreadUtils.postOnBackgroundThread(() -> { + mLocalMediaManager.connectDevice(device); + }); + } + + Collection<MediaDevice> getMediaDevices() { + return mMediaDevices; + } + + MediaDevice getCurrentConnectedMediaDevice() { + return mLocalMediaManager.getCurrentConnectedDevice(); + } + + private MediaDevice getMediaDeviceById(String id) { + return mLocalMediaManager.getMediaDeviceById(new ArrayList<>(mMediaDevices), id); + } + + boolean addDeviceToPlayMedia(MediaDevice device) { + return mLocalMediaManager.addDeviceToPlayMedia(device); + } + + boolean removeDeviceFromPlayMedia(MediaDevice device) { + return mLocalMediaManager.removeDeviceFromPlayMedia(device); + } + + List<MediaDevice> getSelectableMediaDevice() { + return mLocalMediaManager.getSelectableMediaDevice(); + } + + List<MediaDevice> getSelectedMediaDevice() { + return mLocalMediaManager.getSelectedMediaDevice(); + } + + List<MediaDevice> getDeselectableMediaDevice() { + return mLocalMediaManager.getDeselectableMediaDevice(); + } + + boolean isDeviceIncluded(Collection<MediaDevice> deviceCollection, MediaDevice targetDevice) { + for (MediaDevice device : deviceCollection) { + if (TextUtils.equals(device.getId(), targetDevice.getId())) { + return true; + } + } + return false; + } + + void adjustSessionVolume(String sessionId, int volume) { + mLocalMediaManager.adjustSessionVolume(sessionId, volume); + } + + void adjustSessionVolume(int volume) { + mLocalMediaManager.adjustSessionVolume(volume); + } + + int getSessionVolumeMax() { + return mLocalMediaManager.getSessionVolumeMax(); + } + + int getSessionVolume() { + return mLocalMediaManager.getSessionVolume(); + } + + CharSequence getSessionName() { + return mLocalMediaManager.getSessionName(); + } + + void releaseSession() { + mLocalMediaManager.releaseSession(); + } + + List<RoutingSessionInfo> getActiveRemoteMediaDevices() { + final List<RoutingSessionInfo> sessionInfos = new ArrayList<>(); + for (RoutingSessionInfo info : mLocalMediaManager.getActiveMediaSession()) { + if (!info.isSystemSession()) { + sessionInfos.add(info); + } + } + return sessionInfos; + } + + void adjustVolume(MediaDevice device, int volume) { + ThreadUtils.postOnBackgroundThread(() -> { + device.requestSetVolume(volume); + }); + } + + String getPackageName() { + return mPackageName; + } + + boolean hasAdjustVolumeUserRestriction() { + if (RestrictedLockUtilsInternal.checkIfRestrictionEnforced( + mContext, UserManager.DISALLOW_ADJUST_VOLUME, UserHandle.myUserId()) != null) { + return true; + } + final UserManager um = mContext.getSystemService(UserManager.class); + return um.hasBaseUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, + UserHandle.of(UserHandle.myUserId())); + } + + boolean isTransferring() { + for (MediaDevice device : mMediaDevices) { + if (device.getState() == LocalMediaManager.MediaDeviceState.STATE_CONNECTING) { + return true; + } + } + return false; + } + + boolean isZeroMode() { + if (mMediaDevices.size() == 1) { + final MediaDevice device = mMediaDevices.iterator().next(); + // Add "pair new" only when local output device exists + final int type = device.getDeviceType(); + if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE + || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE + || type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) { + return true; + } + } + return false; + } + + void launchBluetoothPairing() { + mCallback.dismissDialog(); + final ActivityStarter.OnDismissAction postKeyguardAction = () -> { + mContext.sendBroadcast(new Intent() + .setAction(MediaOutputSliceConstants.ACTION_LAUNCH_BLUETOOTH_PAIRING) + .setPackage(MediaOutputSliceConstants.SETTINGS_PACKAGE_NAME)); + mShadeController.animateCollapsePanels(); + return true; + }; + mActivityStarter.dismissKeyguardThenExecute(postKeyguardAction, null, true); + } + + private final MediaController.Callback mCb = new MediaController.Callback() { + @Override + public void onMetadataChanged(MediaMetadata metadata) { + mCallback.onMediaChanged(); + } + + @Override + public void onPlaybackStateChanged(PlaybackState playbackState) { + final int state = playbackState.getState(); + if (state == PlaybackState.STATE_STOPPED || state == PlaybackState.STATE_PAUSED) { + mCallback.onMediaStoppedOrPaused(); + } + } + }; + + interface Callback { + /** + * Override to handle the media content updating. + */ + void onMediaChanged(); + + /** + * Override to handle the media state updating. + */ + void onMediaStoppedOrPaused(); + + /** + * Override to handle the device updating. + */ + void onRouteChanged(); + + /** + * Override to dismiss dialog. + */ + void dismissDialog(); + } +} |