1 /* 2 * Copyright (C) 2019 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_START; 20 import static androidx.lifecycle.Lifecycle.Event.ON_STOP; 21 22 import static com.android.settings.media.MediaOutputSlice.MEDIA_PACKAGE_NAME; 23 import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_SLICE_URI; 24 25 import android.app.settings.SettingsEnums; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.graphics.Bitmap; 30 import android.graphics.Canvas; 31 import android.graphics.drawable.BitmapDrawable; 32 import android.graphics.drawable.Drawable; 33 import android.media.MediaMetadata; 34 import android.media.session.MediaController; 35 import android.media.session.MediaSessionManager; 36 import android.media.session.PlaybackState; 37 import android.net.Uri; 38 import android.text.TextUtils; 39 import android.util.Log; 40 41 import androidx.core.graphics.drawable.IconCompat; 42 import androidx.lifecycle.LifecycleObserver; 43 import androidx.lifecycle.OnLifecycleEvent; 44 45 import com.android.internal.annotations.VisibleForTesting; 46 import com.android.settings.R; 47 import com.android.settings.Utils; 48 import com.android.settingslib.media.InfoMediaDevice; 49 import com.android.settingslib.media.LocalMediaManager; 50 import com.android.settingslib.media.MediaDevice; 51 52 import java.util.ArrayList; 53 import java.util.List; 54 55 /** 56 * Represents the Media output Panel. 57 * 58 * <p> 59 * Displays Media output item 60 * </p> 61 */ 62 public class MediaOutputPanel implements PanelContent, LocalMediaManager.DeviceCallback, 63 LifecycleObserver { 64 65 private static final String TAG = "MediaOutputPanel"; 66 67 private final Context mContext; 68 private final String mPackageName; 69 70 @VisibleForTesting 71 LocalMediaManager mLocalMediaManager; 72 73 private PanelContentCallback mCallback; 74 private boolean mIsCustomizedButtonUsed = true; 75 private MediaSessionManager mMediaSessionManager; 76 private MediaController mMediaController; 77 create(Context context, String packageName)78 public static MediaOutputPanel create(Context context, String packageName) { 79 return new MediaOutputPanel(context, packageName); 80 } 81 MediaOutputPanel(Context context, String packageName)82 private MediaOutputPanel(Context context, String packageName) { 83 mContext = context.getApplicationContext(); 84 mPackageName = TextUtils.isEmpty(packageName) ? "" : packageName; 85 } 86 87 @Override getTitle()88 public CharSequence getTitle() { 89 if (mMediaController != null) { 90 final MediaMetadata metadata = mMediaController.getMetadata(); 91 if (metadata != null) { 92 return metadata.getDescription().getTitle(); 93 } 94 } 95 return mContext.getText(R.string.media_volume_title); 96 } 97 98 @Override getSubTitle()99 public CharSequence getSubTitle() { 100 if (mMediaController != null) { 101 final MediaMetadata metadata = mMediaController.getMetadata(); 102 if (metadata != null) { 103 return metadata.getDescription().getSubtitle(); 104 } 105 } 106 return mContext.getText(R.string.media_output_panel_title); 107 } 108 109 @Override getIcon()110 public IconCompat getIcon() { 111 if (mMediaController == null) { 112 return IconCompat.createWithResource(mContext, R.drawable.ic_media_stream).setTint( 113 Utils.getColorAccentDefaultColor(mContext)); 114 } 115 final MediaMetadata metadata = mMediaController.getMetadata(); 116 if (metadata != null) { 117 final Bitmap bitmap = metadata.getDescription().getIconBitmap(); 118 if (bitmap != null) { 119 final Bitmap roundBitmap = Utils.convertCornerRadiusBitmap(mContext, bitmap, 120 (float) mContext.getResources().getDimensionPixelSize( 121 R.dimen.output_switcher_panel_icon_corner_radius)); 122 123 return IconCompat.createWithBitmap(roundBitmap); 124 } 125 } 126 Log.d(TAG, "Media meta data does not contain icon information"); 127 return getPackageIcon(); 128 } 129 getPackageIcon()130 private IconCompat getPackageIcon() { 131 try { 132 final Drawable drawable = mContext.getPackageManager().getApplicationIcon(mPackageName); 133 if (drawable instanceof BitmapDrawable) { 134 return IconCompat.createWithBitmap(((BitmapDrawable) drawable).getBitmap()); 135 } 136 final Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), 137 drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); 138 final Canvas canvas = new Canvas(bitmap); 139 drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 140 drawable.draw(canvas); 141 142 return IconCompat.createWithBitmap(bitmap); 143 } catch (PackageManager.NameNotFoundException e) { 144 Log.e(TAG, "Package is not found. Unable to get package icon."); 145 } 146 return null; 147 } 148 149 @Override getSlices()150 public List<Uri> getSlices() { 151 final List<Uri> uris = new ArrayList<>(); 152 MEDIA_OUTPUT_SLICE_URI = 153 MEDIA_OUTPUT_SLICE_URI 154 .buildUpon() 155 .clearQuery() 156 .appendQueryParameter(MEDIA_PACKAGE_NAME, mPackageName) 157 .build(); 158 uris.add(MEDIA_OUTPUT_SLICE_URI); 159 return uris; 160 } 161 162 @Override getSeeMoreIntent()163 public Intent getSeeMoreIntent() { 164 return null; 165 } 166 167 @Override isCustomizedButtonUsed()168 public boolean isCustomizedButtonUsed() { 169 return mIsCustomizedButtonUsed; 170 } 171 172 @Override getCustomizedButtonTitle()173 public CharSequence getCustomizedButtonTitle() { 174 return mContext.getText(R.string.service_stop); 175 } 176 177 @Override onClickCustomizedButton()178 public void onClickCustomizedButton() { 179 mLocalMediaManager.releaseSession(); 180 } 181 182 @Override registerCallback(PanelContentCallback callback)183 public void registerCallback(PanelContentCallback callback) { 184 mCallback = callback; 185 } 186 187 @Override getMetricsCategory()188 public int getMetricsCategory() { 189 return SettingsEnums.PANEL_MEDIA_OUTPUT; 190 } 191 192 @Override onSelectedDeviceStateChanged(MediaDevice device, int state)193 public void onSelectedDeviceStateChanged(MediaDevice device, int state) { 194 dispatchCustomButtonStateChanged(); 195 } 196 197 @Override onDeviceListUpdate(List<MediaDevice> devices)198 public void onDeviceListUpdate(List<MediaDevice> devices) { 199 dispatchCustomButtonStateChanged(); 200 } 201 202 @Override onDeviceAttributesChanged()203 public void onDeviceAttributesChanged() { 204 dispatchCustomButtonStateChanged(); 205 } 206 dispatchCustomButtonStateChanged()207 private void dispatchCustomButtonStateChanged() { 208 hideCustomButtonIfNecessary(); 209 if (mCallback != null) { 210 mCallback.onCustomizedButtonStateChanged(); 211 } 212 } 213 hideCustomButtonIfNecessary()214 private void hideCustomButtonIfNecessary() { 215 final MediaDevice device = mLocalMediaManager.getCurrentConnectedDevice(); 216 mIsCustomizedButtonUsed = device instanceof InfoMediaDevice; 217 } 218 219 @OnLifecycleEvent(ON_START) onStart()220 public void onStart() { 221 if (!TextUtils.isEmpty(mPackageName)) { 222 mMediaSessionManager = mContext.getSystemService(MediaSessionManager.class); 223 for (MediaController controller : mMediaSessionManager.getActiveSessions(null)) { 224 if (TextUtils.equals(controller.getPackageName(), mPackageName)) { 225 mMediaController = controller; 226 mMediaController.registerCallback(mCb); 227 mCallback.onHeaderChanged(); 228 break; 229 } 230 } 231 } 232 if (mMediaController == null) { 233 Log.d(TAG, "No media controller for " + mPackageName); 234 } 235 if (mLocalMediaManager == null) { 236 mLocalMediaManager = new LocalMediaManager(mContext, mPackageName, null); 237 } 238 mLocalMediaManager.registerCallback(this); 239 mLocalMediaManager.startScan(); 240 } 241 242 @OnLifecycleEvent(ON_STOP) onStop()243 public void onStop() { 244 if (mMediaController != null) { 245 mMediaController.unregisterCallback(mCb); 246 } 247 mLocalMediaManager.unregisterCallback(this); 248 mLocalMediaManager.stopScan(); 249 } 250 251 @Override getViewType()252 public int getViewType() { 253 return PanelContent.VIEW_TYPE_SLIDER_LARGE_ICON; 254 } 255 256 private final MediaController.Callback mCb = new MediaController.Callback() { 257 @Override 258 public void onMetadataChanged(MediaMetadata metadata) { 259 if (mCallback != null) { 260 mCallback.onHeaderChanged(); 261 } 262 } 263 264 @Override 265 public void onPlaybackStateChanged(PlaybackState state) { 266 final int playState = state.getState(); 267 if (mCallback != null && (playState == PlaybackState.STATE_STOPPED 268 || playState == PlaybackState.STATE_PAUSED)) { 269 mCallback.forceClose(); 270 } 271 } 272 }; 273 } 274