1/*
2 * Copyright (C) 2013 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
17package android.support.v7.media;
18
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
26import android.content.pm.ServiceInfo;
27import android.os.Handler;
28
29import java.util.ArrayList;
30import java.util.Collections;
31
32/**
33 * Watches for media route provider services to be installed.
34 * Adds a provider to the media router for each registered service.
35 *
36 * @see RegisteredMediaRouteProvider
37 */
38final class RegisteredMediaRouteProviderWatcher {
39    private final Context mContext;
40    private final Callback mCallback;
41    private final Handler mHandler;
42    private final PackageManager mPackageManager;
43
44    private final ArrayList<RegisteredMediaRouteProvider> mProviders =
45            new ArrayList<RegisteredMediaRouteProvider>();
46    private boolean mRunning;
47
48    public RegisteredMediaRouteProviderWatcher(Context context, Callback callback) {
49        mContext = context;
50        mCallback = callback;
51        mHandler = new Handler();
52        mPackageManager = context.getPackageManager();
53    }
54
55    public void start() {
56        if (!mRunning) {
57            mRunning = true;
58
59            IntentFilter filter = new IntentFilter();
60            filter.addAction(Intent.ACTION_PACKAGE_ADDED);
61            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
62            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
63            filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
64            filter.addAction(Intent.ACTION_PACKAGE_RESTARTED);
65            filter.addDataScheme("package");
66            mContext.registerReceiver(mScanPackagesReceiver, filter, null, mHandler);
67
68            // Scan packages.
69            // Also has the side-effect of restarting providers if needed.
70            mHandler.post(mScanPackagesRunnable);
71        }
72    }
73
74    public void stop() {
75        if (mRunning) {
76            mRunning = false;
77
78            mContext.unregisterReceiver(mScanPackagesReceiver);
79            mHandler.removeCallbacks(mScanPackagesRunnable);
80
81            // Stop all providers.
82            for (int i = mProviders.size() - 1; i >= 0; i--) {
83                mProviders.get(i).stop();
84            }
85        }
86    }
87
88    private void scanPackages() {
89        if (!mRunning) {
90            return;
91        }
92
93        // Add providers for all new services.
94        // Reorder the list so that providers left at the end will be the ones to remove.
95        int targetIndex = 0;
96        Intent intent = new Intent(MediaRouteProviderService.SERVICE_INTERFACE);
97        for (ResolveInfo resolveInfo : mPackageManager.queryIntentServices(intent, 0)) {
98            ServiceInfo serviceInfo = resolveInfo.serviceInfo;
99            if (serviceInfo != null) {
100                int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name);
101                if (sourceIndex < 0) {
102                    RegisteredMediaRouteProvider provider =
103                            new RegisteredMediaRouteProvider(mContext,
104                            new ComponentName(serviceInfo.packageName, serviceInfo.name));
105                    provider.start();
106                    mProviders.add(targetIndex++, provider);
107                    mCallback.addProvider(provider);
108                } else if (sourceIndex >= targetIndex) {
109                    RegisteredMediaRouteProvider provider = mProviders.get(sourceIndex);
110                    provider.start(); // restart the provider if needed
111                    provider.rebindIfDisconnected();
112                    Collections.swap(mProviders, sourceIndex, targetIndex++);
113                }
114            }
115        }
116
117        // Remove providers for missing services.
118        if (targetIndex < mProviders.size()) {
119            for (int i = mProviders.size() - 1; i >= targetIndex; i--) {
120                RegisteredMediaRouteProvider provider = mProviders.get(i);
121                mCallback.removeProvider(provider);
122                mProviders.remove(provider);
123                provider.stop();
124            }
125        }
126    }
127
128    private int findProvider(String packageName, String className) {
129        int count = mProviders.size();
130        for (int i = 0; i < count; i++) {
131            RegisteredMediaRouteProvider provider = mProviders.get(i);
132            if (provider.hasComponentName(packageName, className)) {
133                return i;
134            }
135        }
136        return -1;
137    }
138
139    private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() {
140        @Override
141        public void onReceive(Context context, Intent intent) {
142            scanPackages();
143        }
144    };
145
146    private final Runnable mScanPackagesRunnable = new Runnable() {
147        @Override
148        public void run() {
149            scanPackages();
150        }
151    };
152
153    public interface Callback {
154        void addProvider(MediaRouteProvider provider);
155        void removeProvider(MediaRouteProvider provider);
156    }
157}
158