1 /* 2 * Copyright 2018 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.car.media.common; 18 19 import android.content.BroadcastReceiver; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.IntentFilter; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.service.media.MediaBrowserService; 26 import android.util.Log; 27 28 import java.util.ArrayList; 29 import java.util.Comparator; 30 import java.util.HashSet; 31 import java.util.List; 32 import java.util.Set; 33 import java.util.function.Consumer; 34 import java.util.stream.Collectors; 35 36 /** 37 * This manager provides access to the list of all possible media sources that can be selected 38 * to be played. 39 * <p> 40 * It also provides means to set and retrieve the last played media source, in order to disambiguate 41 * in cases where there is no media application currently playing. 42 */ 43 public class MediaSourcesManager { 44 private static final String TAG = "MediaSourcesManager"; 45 private final Context mContext; 46 private List<MediaSource> mMediaSources; 47 private List<Observer> mObservers = new ArrayList<>(); 48 private static AppInstallUninstallReceiver sReceiver; 49 50 /** 51 * Observer of media source changes 52 */ 53 public interface Observer { 54 /** 55 * Invoked when the list of media sources has changed 56 */ 57 void onMediaSourcesChanged(); 58 } 59 60 /** 61 * Creates a new instance of the manager for the given context 62 */ 63 public MediaSourcesManager(Context context) { 64 mContext = context; 65 } 66 67 /** 68 * Registers an observer. Consumers must remember to unregister their observers to avoid 69 * memory leaks. 70 */ 71 public void registerObserver(Observer observer) { 72 mObservers.add(observer); 73 if (sReceiver == null) { 74 registerBroadcastReceiver(); 75 updateMediaSources(); 76 } 77 } 78 79 /** 80 * Unregisters an observer. 81 */ 82 public void unregisterObserver(Observer observer) { 83 mObservers.remove(observer); 84 if (mObservers.isEmpty() && sReceiver != null) { 85 unregisterBroadcastReceiver(); 86 } 87 } 88 89 private void notify(Consumer<Observer> notification) { 90 for (Observer observer : mObservers) { 91 notification.accept(observer); 92 } 93 } 94 95 /** 96 * Returns the list of available media sources. 97 */ 98 public List<MediaSource> getMediaSources() { 99 if (sReceiver == null) { 100 updateMediaSources(); 101 } 102 return mMediaSources; 103 } 104 105 private void registerBroadcastReceiver() { 106 sReceiver = new AppInstallUninstallReceiver(); 107 IntentFilter filter = new IntentFilter(); 108 filter.addAction(Intent.ACTION_PACKAGE_ADDED); 109 filter.addAction(Intent.ACTION_PACKAGE_REMOVED); 110 filter.addDataScheme("package"); 111 mContext.getApplicationContext().registerReceiver(sReceiver, filter); 112 } 113 114 private void unregisterBroadcastReceiver() { 115 mContext.getApplicationContext().unregisterReceiver(sReceiver); 116 sReceiver = null; 117 } 118 119 private void updateMediaSources() { 120 mMediaSources = getPackageNames().stream() 121 .filter(packageName -> packageName != null) 122 .map(packageName -> new MediaSource(mContext, packageName)) 123 .filter(mediaSource -> { 124 if (mediaSource.getName() == null) { 125 Log.w(TAG, "Found media source without name: " 126 + mediaSource.getPackageName()); 127 return false; 128 } 129 return true; 130 }) 131 .sorted(Comparator.comparing(mediaSource -> mediaSource.getName().toString())) 132 .collect(Collectors.toList()); 133 } 134 135 /** 136 * Generates a set of all possible apps to choose from, including the ones that are just 137 * media services. 138 */ 139 private Set<String> getPackageNames() { 140 PackageManager packageManager = mContext.getPackageManager(); 141 Intent intent = new Intent(Intent.ACTION_MAIN, null); 142 intent.addCategory(Intent.CATEGORY_APP_MUSIC); 143 144 Intent mediaIntent = new Intent(); 145 mediaIntent.setAction(MediaBrowserService.SERVICE_INTERFACE); 146 147 List<ResolveInfo> availableActivities = packageManager.queryIntentActivities(intent, 0); 148 List<ResolveInfo> mediaServices = packageManager.queryIntentServices(mediaIntent, 149 PackageManager.GET_RESOLVED_FILTER); 150 151 Set<String> apps = new HashSet<>(); 152 for (ResolveInfo info : mediaServices) { 153 apps.add(info.serviceInfo.packageName); 154 } 155 for (ResolveInfo info : availableActivities) { 156 apps.add(info.activityInfo.packageName); 157 } 158 return apps; 159 } 160 161 private class AppInstallUninstallReceiver extends BroadcastReceiver { 162 @Override 163 public void onReceive(Context context, Intent intent) { 164 updateMediaSources(); 165 MediaSourcesManager.this.notify(Observer::onMediaSourcesChanged); 166 } 167 } 168 } 169