/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.media; import android.Manifest; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.media.RemoteDisplayState; import android.os.Handler; import android.os.UserHandle; import android.util.Log; import android.util.Slog; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Collections; /** * Watches for remote display provider services to be installed. * Adds a provider to the media router for each registered service. * * @see RemoteDisplayProviderProxy */ public final class RemoteDisplayProviderWatcher { private static final String TAG = "RemoteDisplayProvider"; // max. 23 chars private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); private final Context mContext; private final Callback mCallback; private final Handler mHandler; private final int mUserId; private final PackageManager mPackageManager; private final ArrayList mProviders = new ArrayList(); private boolean mRunning; public RemoteDisplayProviderWatcher(Context context, Callback callback, Handler handler, int userId) { mContext = context; mCallback = callback; mHandler = handler; mUserId = userId; mPackageManager = context.getPackageManager(); } public void dump(PrintWriter pw, String prefix) { pw.println(prefix + "Watcher"); pw.println(prefix + " mUserId=" + mUserId); pw.println(prefix + " mRunning=" + mRunning); pw.println(prefix + " mProviders.size()=" + mProviders.size()); } public void start() { if (!mRunning) { mRunning = true; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_ADDED); filter.addAction(Intent.ACTION_PACKAGE_REMOVED); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); filter.addAction(Intent.ACTION_PACKAGE_REPLACED); filter.addAction(Intent.ACTION_PACKAGE_RESTARTED); filter.addDataScheme("package"); mContext.registerReceiverAsUser(mScanPackagesReceiver, new UserHandle(mUserId), filter, null, mHandler); // Scan packages. // Also has the side-effect of restarting providers if needed. mHandler.post(mScanPackagesRunnable); } } public void stop() { if (mRunning) { mRunning = false; mContext.unregisterReceiver(mScanPackagesReceiver); mHandler.removeCallbacks(mScanPackagesRunnable); // Stop all providers. for (int i = mProviders.size() - 1; i >= 0; i--) { mProviders.get(i).stop(); } } } private void scanPackages() { if (!mRunning) { return; } // Add providers for all new services. // Reorder the list so that providers left at the end will be the ones to remove. int targetIndex = 0; Intent intent = new Intent(RemoteDisplayState.SERVICE_INTERFACE); for (ResolveInfo resolveInfo : mPackageManager.queryIntentServicesAsUser( intent, 0, mUserId)) { ServiceInfo serviceInfo = resolveInfo.serviceInfo; if (serviceInfo != null && verifyServiceTrusted(serviceInfo)) { int sourceIndex = findProvider(serviceInfo.packageName, serviceInfo.name); if (sourceIndex < 0) { RemoteDisplayProviderProxy provider = new RemoteDisplayProviderProxy(mContext, new ComponentName(serviceInfo.packageName, serviceInfo.name), mUserId); provider.start(); mProviders.add(targetIndex++, provider); mCallback.addProvider(provider); } else if (sourceIndex >= targetIndex) { RemoteDisplayProviderProxy provider = mProviders.get(sourceIndex); provider.start(); // restart the provider if needed provider.rebindIfDisconnected(); Collections.swap(mProviders, sourceIndex, targetIndex++); } } } // Remove providers for missing services. if (targetIndex < mProviders.size()) { for (int i = mProviders.size() - 1; i >= targetIndex; i--) { RemoteDisplayProviderProxy provider = mProviders.get(i); mCallback.removeProvider(provider); mProviders.remove(provider); provider.stop(); } } } private boolean verifyServiceTrusted(ServiceInfo serviceInfo) { if (serviceInfo.permission == null || !serviceInfo.permission.equals( Manifest.permission.BIND_REMOTE_DISPLAY)) { // If the service does not require this permission then any app could // potentially bind to it and cause the remote display service to // misbehave. So we only want to trust providers that require the // correct permissions. Slog.w(TAG, "Ignoring remote display provider service because it did not " + "require the BIND_REMOTE_DISPLAY permission in its manifest: " + serviceInfo.packageName + "/" + serviceInfo.name); return false; } if (!hasCaptureVideoPermission(serviceInfo.packageName)) { // If the service does not have permission to capture video then it // isn't going to be terribly useful as a remote display, is it? // Kind of makes you wonder what it's doing there in the first place. Slog.w(TAG, "Ignoring remote display provider service because it does not " + "have the CAPTURE_VIDEO_OUTPUT or CAPTURE_SECURE_VIDEO_OUTPUT " + "permission: " + serviceInfo.packageName + "/" + serviceInfo.name); return false; } // Looks good. return true; } private boolean hasCaptureVideoPermission(String packageName) { if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_VIDEO_OUTPUT, packageName) == PackageManager.PERMISSION_GRANTED) { return true; } if (mPackageManager.checkPermission(Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT, packageName) == PackageManager.PERMISSION_GRANTED) { return true; } return false; } private int findProvider(String packageName, String className) { int count = mProviders.size(); for (int i = 0; i < count; i++) { RemoteDisplayProviderProxy provider = mProviders.get(i); if (provider.hasComponentName(packageName, className)) { return i; } } return -1; } private final BroadcastReceiver mScanPackagesReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Received package manager broadcast: " + intent); } scanPackages(); } }; private final Runnable mScanPackagesRunnable = new Runnable() { @Override public void run() { scanPackages(); } }; public interface Callback { void addProvider(RemoteDisplayProviderProxy provider); void removeProvider(RemoteDisplayProviderProxy provider); } }