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.media;
18 
19 import static com.android.settings.slices.CustomSliceRegistry.MEDIA_OUTPUT_INDICATOR_SLICE_URI;
20 import static com.android.settingslib.media.flags.Flags.enableOutputSwitcherForSystemRouting;
21 
22 import android.annotation.ColorInt;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.graphics.Bitmap;
26 import android.media.session.MediaController;
27 import android.net.Uri;
28 import android.util.Log;
29 
30 import androidx.annotation.VisibleForTesting;
31 import androidx.core.graphics.drawable.IconCompat;
32 import androidx.slice.Slice;
33 import androidx.slice.builders.ListBuilder;
34 import androidx.slice.builders.SliceAction;
35 
36 import com.android.settings.R;
37 import com.android.settings.Utils;
38 import com.android.settings.slices.CustomSliceable;
39 import com.android.settings.slices.SliceBackgroundWorker;
40 import com.android.settingslib.media.MediaOutputConstants;
41 
42 public class MediaOutputIndicatorSlice implements CustomSliceable {
43 
44     private static final String TAG = "MediaOutputIndSlice";
45 
46     private Context mContext;
47     private MediaOutputIndicatorWorker mWorker;
48 
MediaOutputIndicatorSlice(Context context)49     public MediaOutputIndicatorSlice(Context context) {
50         mContext = context;
51     }
52 
53     @Override
getSlice()54     public Slice getSlice() {
55         if (!isVisible()) {
56             return new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
57                     .setIsError(true)
58                     .build();
59         }
60         final IconCompat icon = IconCompat.createWithResource(mContext,
61                 com.android.internal.R.drawable.ic_settings_bluetooth);
62         final int stringRes = enableOutputSwitcherForSystemRouting()
63                 ? (getWorker().getActiveLocalMediaController() != null
64                         ? R.string.media_output_label_title
65                         : R.string.media_output_title_without_playing)
66                 : R.string.media_output_label_title;
67         final CharSequence title = mContext.getString(stringRes,
68                 Utils.getApplicationLabel(mContext, getWorker().getPackageName()));
69         final SliceAction primarySliceAction = SliceAction.create(
70                 getBroadcastIntent(mContext), icon, ListBuilder.ICON_IMAGE, title);
71 
72         @ColorInt final int color = Utils.getColorAccentDefaultColor(mContext);
73         // To set an empty icon to indent the row
74         final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
75                 .setAccentColor(color)
76                 .addRow(new ListBuilder.RowBuilder()
77                         .setTitle(title)
78                         .setTitleItem(createEmptyIcon(), ListBuilder.ICON_IMAGE)
79                         .setSubtitle(getWorker().getCurrentConnectedMediaDevice().getName())
80                         .setPrimaryAction(primarySliceAction));
81         return listBuilder.build();
82     }
83 
createEmptyIcon()84     private IconCompat createEmptyIcon() {
85         final Bitmap bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888);
86         return IconCompat.createWithBitmap(bitmap);
87     }
88 
89     @Override
getUri()90     public Uri getUri() {
91         return MEDIA_OUTPUT_INDICATOR_SLICE_URI;
92     }
93 
94     @Override
getIntent()95     public Intent getIntent() {
96         // This Slice reflects active media device information and launch MediaOutputDialog. It does
97         // not contain its owned Slice data
98         return null;
99     }
100 
101     @Override
getSliceHighlightMenuRes()102     public int getSliceHighlightMenuRes() {
103         return R.string.menu_key_connected_devices;
104     }
105 
106     @Override
getBackgroundWorkerClass()107     public Class getBackgroundWorkerClass() {
108         return MediaOutputIndicatorWorker.class;
109     }
110 
getWorker()111     private MediaOutputIndicatorWorker getWorker() {
112         if (mWorker == null) {
113             mWorker = SliceBackgroundWorker.getInstance(getUri());
114         }
115         return mWorker;
116     }
117 
118     @VisibleForTesting
isVisible()119     boolean isVisible() {
120         // To decide Slice's visibility.
121         // Return true if
122         // 1. AudioMode is not in on-going call
123         // 2. worker is not null
124         // 3. Available devices are more than 0
125         // 4. The local media session is active and the state is playing.
126         //    - if !enableOutputSwitcherForSystemRouting(), (4) will be bypass.
127         return getWorker() != null
128                 && !com.android.settingslib.Utils.isAudioModeOngoingCall(mContext)
129                 && getWorker().getMediaDevices().size() > 0
130                 && (enableOutputSwitcherForSystemRouting()
131                         ? true : getWorker().getActiveLocalMediaController() != null);
132     }
133 
134     @Override
onNotifyChange(Intent intent)135     public void onNotifyChange(Intent intent) {
136         final MediaController mediaController = getWorker().getActiveLocalMediaController();
137 
138         // Launch media output dialog
139         if (enableOutputSwitcherForSystemRouting() && mediaController == null) {
140             mContext.sendBroadcast(new Intent()
141                     .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
142                     .setAction(MediaOutputConstants.ACTION_LAUNCH_SYSTEM_MEDIA_OUTPUT_DIALOG));
143         } else if (mediaController != null) {
144             mContext.sendBroadcast(new Intent()
145                     .setPackage(MediaOutputConstants.SYSTEMUI_PACKAGE_NAME)
146                     .setAction(MediaOutputConstants.ACTION_LAUNCH_MEDIA_OUTPUT_DIALOG)
147                     .putExtra(MediaOutputConstants.KEY_MEDIA_SESSION_TOKEN,
148                             mediaController.getSessionToken())
149                     .putExtra(MediaOutputConstants.EXTRA_PACKAGE_NAME,
150                             mediaController.getPackageName()));
151         } else {
152             Log.d(TAG, "No active local media controller");
153             return;
154         }
155 
156         // Dismiss volume panel
157         mContext.sendBroadcast(new Intent()
158                 .setPackage(MediaOutputConstants.SETTINGS_PACKAGE_NAME)
159                 .setAction(MediaOutputConstants.ACTION_CLOSE_PANEL));
160     }
161 }
162