Home | History | Annotate | Download | only in common
      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