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.systemui.statusbar;
18 
19 import android.content.Context;
20 import android.content.Intent;
21 import android.content.res.ColorStateList;
22 import android.graphics.drawable.Drawable;
23 import android.graphics.drawable.GradientDrawable;
24 import android.graphics.drawable.RippleDrawable;
25 import android.service.notification.StatusBarNotification;
26 import android.util.FeatureFlagUtils;
27 import android.util.Log;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.ViewParent;
31 import android.widget.ImageView;
32 import android.widget.LinearLayout;
33 import android.widget.TextView;
34 
35 import com.android.settingslib.bluetooth.LocalBluetoothManager;
36 import com.android.settingslib.media.InfoMediaManager;
37 import com.android.settingslib.media.LocalMediaManager;
38 import com.android.settingslib.media.MediaDevice;
39 import com.android.settingslib.media.MediaOutputSliceConstants;
40 import com.android.settingslib.widget.AdaptiveIcon;
41 import com.android.systemui.Dependency;
42 import com.android.systemui.plugins.ActivityStarter;
43 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
44 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * Class for handling MediaTransfer state over a set of notifications.
51  */
52 public class MediaTransferManager {
53     private final Context mContext;
54     private final ActivityStarter mActivityStarter;
55     private MediaDevice mDevice;
56     private List<View> mViews = new ArrayList<>();
57     private LocalMediaManager mLocalMediaManager;
58 
59     private static final String TAG = "MediaTransferManager";
60 
61     private final View.OnClickListener mOnClickHandler = new View.OnClickListener() {
62         @Override
63         public void onClick(View view) {
64             if (handleMediaTransfer(view)) {
65                 return;
66             }
67         }
68 
69         private boolean handleMediaTransfer(View view) {
70             if (view.findViewById(com.android.internal.R.id.media_seamless) == null) {
71                 return false;
72             }
73 
74             ViewParent parent = view.getParent();
75             StatusBarNotification statusBarNotification =
76                     getRowForParent(parent).getEntry().getSbn();
77             final Intent intent = new Intent()
78                     .setAction(MediaOutputSliceConstants.ACTION_MEDIA_OUTPUT)
79                     .putExtra(MediaOutputSliceConstants.EXTRA_PACKAGE_NAME,
80                             statusBarNotification.getPackageName());
81             mActivityStarter.startActivity(intent, false, true /* dismissShade */,
82                     Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
83             return true;
84         }
85     };
86 
87     private final LocalMediaManager.DeviceCallback mMediaDeviceCallback =
88             new LocalMediaManager.DeviceCallback() {
89         @Override
90         public void onDeviceListUpdate(List<MediaDevice> devices) {
91             MediaDevice currentDevice = mLocalMediaManager.getCurrentConnectedDevice();
92             // Check because this can be called several times while changing devices
93             if (mDevice == null || !mDevice.equals(currentDevice)) {
94                 mDevice = currentDevice;
95                 updateAllChips();
96             }
97         }
98 
99         @Override
100         public void onSelectedDeviceStateChanged(MediaDevice device, int state) {
101             if (mDevice == null || !mDevice.equals(device)) {
102                 mDevice = device;
103                 updateAllChips();
104             }
105         }
106     };
107 
MediaTransferManager(Context context)108     public MediaTransferManager(Context context) {
109         mContext = context;
110         mActivityStarter = Dependency.get(ActivityStarter.class);
111         LocalBluetoothManager lbm = Dependency.get(LocalBluetoothManager.class);
112         InfoMediaManager imm = new InfoMediaManager(mContext, null, null, lbm);
113         mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, null);
114     }
115 
116     /**
117      * Mark a view as removed. If no views remain the media device listener will be unregistered.
118      * @param root
119      */
setRemoved(View root)120     public void setRemoved(View root) {
121         if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)
122                 || mLocalMediaManager == null || root == null) {
123             return;
124         }
125         View view = root.findViewById(com.android.internal.R.id.media_seamless);
126         if (mViews.remove(view)) {
127             if (mViews.size() == 0) {
128                 mLocalMediaManager.unregisterCallback(mMediaDeviceCallback);
129             }
130         } else {
131             Log.e(TAG, "Tried to remove unknown view " + view);
132         }
133     }
134 
getRowForParent(ViewParent parent)135     private ExpandableNotificationRow getRowForParent(ViewParent parent) {
136         while (parent != null) {
137             if (parent instanceof ExpandableNotificationRow) {
138                 return ((ExpandableNotificationRow) parent);
139             }
140             parent = parent.getParent();
141         }
142         return null;
143     }
144 
145     /**
146      * apply the action button for MediaTransfer
147      *
148      * @param root  The parent container of the view.
149      * @param entry The entry of MediaTransfer action button.
150      */
applyMediaTransferView(ViewGroup root, NotificationEntry entry)151     public void applyMediaTransferView(ViewGroup root, NotificationEntry entry) {
152         if (!FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SEAMLESS_TRANSFER)
153                 || mLocalMediaManager == null || root == null) {
154             return;
155         }
156 
157         View view = root.findViewById(com.android.internal.R.id.media_seamless);
158         if (view == null) {
159             return;
160         }
161 
162         view.setVisibility(View.VISIBLE);
163         view.setOnClickListener(mOnClickHandler);
164         if (!mViews.contains(view)) {
165             mViews.add(view);
166             if (mViews.size() == 1) {
167                 mLocalMediaManager.registerCallback(mMediaDeviceCallback);
168             }
169         }
170 
171         // Initial update
172         mLocalMediaManager.startScan();
173         mDevice = mLocalMediaManager.getCurrentConnectedDevice();
174         updateChip(view);
175     }
176 
updateAllChips()177     private void updateAllChips() {
178         for (View view : mViews) {
179             updateChip(view);
180         }
181     }
182 
updateChip(View view)183     private void updateChip(View view) {
184         ExpandableNotificationRow enr = getRowForParent(view.getParent());
185         int fgColor = enr.getNotificationHeader().getOriginalIconColor();
186         ColorStateList fgTintList = ColorStateList.valueOf(fgColor);
187         int bgColor = enr.getCurrentBackgroundTint();
188 
189         // Update outline color
190         LinearLayout viewLayout = (LinearLayout) view;
191         RippleDrawable bkgDrawable = (RippleDrawable) viewLayout.getBackground();
192         GradientDrawable rect = (GradientDrawable) bkgDrawable.getDrawable(0);
193         rect.setStroke(2, fgColor);
194         rect.setColor(bgColor);
195 
196         ImageView iconView = view.findViewById(com.android.internal.R.id.media_seamless_image);
197         TextView deviceName = view.findViewById(com.android.internal.R.id.media_seamless_text);
198         deviceName.setTextColor(fgTintList);
199 
200         if (mDevice != null) {
201             Drawable icon = mDevice.getIcon();
202             iconView.setVisibility(View.VISIBLE);
203             iconView.setImageTintList(fgTintList);
204 
205             if (icon instanceof AdaptiveIcon) {
206                 AdaptiveIcon aIcon = (AdaptiveIcon) icon;
207                 aIcon.setBackgroundColor(bgColor);
208                 iconView.setImageDrawable(aIcon);
209             } else {
210                 iconView.setImageDrawable(icon);
211             }
212             deviceName.setText(mDevice.getName());
213         } else {
214             // Reset to default
215             iconView.setVisibility(View.GONE);
216             deviceName.setText(com.android.internal.R.string.ext_media_seamless_action);
217         }
218     }
219 }
220