• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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_SLICE_URI;
20 
21 import android.app.PendingIntent;
22 import android.bluetooth.BluetoothAdapter;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.graphics.drawable.Drawable;
26 import android.net.Uri;
27 import android.telephony.TelephonyManager;
28 import android.text.TextUtils;
29 import android.util.Log;
30 
31 import androidx.annotation.VisibleForTesting;
32 import androidx.core.graphics.drawable.IconCompat;
33 import androidx.slice.Slice;
34 import androidx.slice.builders.ListBuilder;
35 import androidx.slice.builders.SliceAction;
36 
37 import com.android.settings.R;
38 import com.android.settings.Utils;
39 import com.android.settings.slices.CustomSliceable;
40 import com.android.settings.slices.SliceBackgroundWorker;
41 import com.android.settings.slices.SliceBroadcastReceiver;
42 import com.android.settingslib.media.MediaDevice;
43 
44 import java.util.List;
45 
46 /**
47  * Show the Media device that can be transfer the media.
48  */
49 public class MediaOutputSlice implements CustomSliceable {
50 
51     private static final String TAG = "MediaOutputSlice";
52     private static final String MEDIA_DEVICE_ID = "media_device_id";
53 
54     public static final String MEDIA_PACKAGE_NAME = "media_package_name";
55 
56     private final Context mContext;
57 
58     private MediaDeviceUpdateWorker mWorker;
59     private String mPackageName;
60 
MediaOutputSlice(Context context)61     public MediaOutputSlice(Context context) {
62         mContext = context;
63         mPackageName = getUri().getQueryParameter(MEDIA_PACKAGE_NAME);
64     }
65 
66     @VisibleForTesting
init(String packageName, MediaDeviceUpdateWorker worker)67     void init(String packageName, MediaDeviceUpdateWorker worker) {
68         mPackageName = packageName;
69         mWorker = worker;
70     }
71 
72     @Override
getSlice()73     public Slice getSlice() {
74         // Reload theme for switching dark mode on/off
75         mContext.getTheme().applyStyle(R.style.Theme_Settings_Home, true /* force */);
76 
77         final ListBuilder listBuilder = new ListBuilder(mContext, getUri(), ListBuilder.INFINITY)
78                 .setAccentColor(COLOR_NOT_TINTED);
79 
80         if (!isVisible()) {
81             Log.d(TAG, "getSlice() is not visible");
82             return listBuilder.build();
83         }
84 
85         final List<MediaDevice> devices = getMediaDevices();
86 
87         final MediaDevice connectedDevice = getWorker().getCurrentConnectedMediaDevice();
88         if (connectedDevice != null) {
89             listBuilder.addRow(getActiveDeviceHeaderRow(connectedDevice));
90         }
91 
92         for (MediaDevice device : devices) {
93             if (connectedDevice == null
94                     || !TextUtils.equals(connectedDevice.getId(), device.getId())) {
95                 listBuilder.addRow(getMediaDeviceRow(device));
96             }
97         }
98 
99         return listBuilder.build();
100     }
101 
getActiveDeviceHeaderRow(MediaDevice device)102     private ListBuilder.RowBuilder getActiveDeviceHeaderRow(MediaDevice device) {
103         final String title = device.getName();
104         final IconCompat icon = getDeviceIconCompat(device);
105 
106         final PendingIntent broadcastAction =
107                 getBroadcastIntent(mContext, device.getId(), device.hashCode());
108         final SliceAction primarySliceAction = SliceAction.createDeeplink(broadcastAction, icon,
109                 ListBuilder.ICON_IMAGE, title);
110 
111         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
112                 .setTitleItem(icon, ListBuilder.ICON_IMAGE)
113                 .setTitle(title)
114                 .setSubtitle(device.getSummary())
115                 .setPrimaryAction(primarySliceAction);
116 
117         return rowBuilder;
118     }
119 
getDeviceIconCompat(MediaDevice device)120     private IconCompat getDeviceIconCompat(MediaDevice device) {
121         Drawable drawable = device.getIcon();
122         if (drawable == null) {
123             Log.d(TAG, "getDeviceIconCompat() device : " + device.getName() + ", drawable is null");
124             // Use default Bluetooth device icon to handle getIcon() is null case.
125             drawable = mContext.getDrawable(com.android.internal.R.drawable.ic_bt_headphones_a2dp);
126         }
127 
128         return Utils.createIconWithDrawable(drawable);
129     }
130 
getWorker()131     private MediaDeviceUpdateWorker getWorker() {
132         if (mWorker == null) {
133             mWorker = (MediaDeviceUpdateWorker) SliceBackgroundWorker.getInstance(getUri());
134             if (mWorker != null) {
135                 mWorker.setPackageName(mPackageName);
136             }
137         }
138         return mWorker;
139     }
140 
getMediaDevices()141     private List<MediaDevice> getMediaDevices() {
142         final List<MediaDevice> devices = getWorker().getMediaDevices();
143         return devices;
144     }
145 
getMediaDeviceRow(MediaDevice device)146     private ListBuilder.RowBuilder getMediaDeviceRow(MediaDevice device) {
147         final String title = device.getName();
148         final PendingIntent broadcastAction =
149                 getBroadcastIntent(mContext, device.getId(), device.hashCode());
150         final IconCompat deviceIcon = getDeviceIconCompat(device);
151 
152         final ListBuilder.RowBuilder rowBuilder = new ListBuilder.RowBuilder()
153                 .setTitleItem(deviceIcon, ListBuilder.ICON_IMAGE)
154                 .setPrimaryAction(SliceAction.create(broadcastAction, deviceIcon,
155                         ListBuilder.ICON_IMAGE, title))
156                 .setTitle(title)
157                 .setSubtitle(device.getSummary());
158 
159         return rowBuilder;
160     }
161 
getBroadcastIntent(Context context, String id, int requestCode)162     private PendingIntent getBroadcastIntent(Context context, String id, int requestCode) {
163         final Intent intent = new Intent(getUri().toString());
164         intent.setClass(context, SliceBroadcastReceiver.class);
165         intent.putExtra(MEDIA_DEVICE_ID, id);
166         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
167         return PendingIntent.getBroadcast(context, requestCode /* requestCode */, intent,
168                 PendingIntent.FLAG_CANCEL_CURRENT);
169     }
170 
171     @Override
getUri()172     public Uri getUri() {
173         return MEDIA_OUTPUT_SLICE_URI;
174     }
175 
176     @Override
onNotifyChange(Intent intent)177     public void onNotifyChange(Intent intent) {
178         final MediaDeviceUpdateWorker worker = getWorker();
179         final String id = intent != null ? intent.getStringExtra(MEDIA_DEVICE_ID) : "";
180         final MediaDevice device = worker.getMediaDeviceById(id);
181         if (device != null) {
182             Log.d(TAG, "onNotifyChange() device name : " + device.getName());
183             worker.connectDevice(device);
184         }
185     }
186 
187     @Override
getIntent()188     public Intent getIntent() {
189         return null;
190     }
191 
192     @Override
getBackgroundWorkerClass()193     public Class getBackgroundWorkerClass() {
194         return MediaDeviceUpdateWorker.class;
195     }
196 
isVisible()197     private boolean isVisible() {
198         // To decide Slice's visibility.
199         // Return true if
200         // 1. phone is not in ongoing call mode
201         // 2. worker is not null
202         // 3. Bluetooth is enabled
203         final TelephonyManager telephonyManager =
204                 (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE);
205         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
206 
207         return telephonyManager.getCallState() == TelephonyManager.CALL_STATE_IDLE
208                 && adapter.isEnabled()
209                 && getWorker() != null;
210     }
211 }
212