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