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