diff options
Diffstat (limited to 'services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java')
-rw-r--r-- | services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java | 219 |
1 files changed, 219 insertions, 0 deletions
diff --git a/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java new file mode 100644 index 0000000..6a5f563 --- /dev/null +++ b/services/core/java/com/android/server/media/RemoteDisplayProviderWatcher.java @@ -0,0 +1,219 @@ +/* + * 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<RemoteDisplayProviderProxy> mProviders = + new ArrayList<RemoteDisplayProviderProxy>(); + 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); + } +} |