summaryrefslogtreecommitdiffstats
path: root/services/java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java')
-rw-r--r--services/java/com/android/server/SystemServer.java19
-rw-r--r--services/java/com/android/server/am/ActiveServices.java52
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java26
-rw-r--r--services/java/com/android/server/am/ActivityStack.java33
-rw-r--r--services/java/com/android/server/am/BatteryStatsService.java14
-rw-r--r--services/java/com/android/server/am/ConnectionRecord.java2
-rw-r--r--services/java/com/android/server/am/ProcessStatsService.java32
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java21
-rw-r--r--services/java/com/android/server/input/InputManagerService.java1
-rw-r--r--services/java/com/android/server/media/MediaRouterService.java1351
-rw-r--r--services/java/com/android/server/media/RemoteDisplayProviderProxy.java443
-rw-r--r--services/java/com/android/server/media/RemoteDisplayProviderWatcher.java181
-rw-r--r--services/java/com/android/server/wifi/WifiService.java59
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java71
14 files changed, 2206 insertions, 99 deletions
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index bb14259..a42cbcf 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -55,6 +55,7 @@ import com.android.server.content.ContentService;
import com.android.server.display.DisplayManagerService;
import com.android.server.dreams.DreamManagerService;
import com.android.server.input.InputManagerService;
+import com.android.server.media.MediaRouterService;
import com.android.server.net.NetworkPolicyManagerService;
import com.android.server.net.NetworkStatsService;
import com.android.server.os.SchedulingPolicyService;
@@ -356,6 +357,7 @@ class ServerThread {
DreamManagerService dreamy = null;
AssetAtlasService atlas = null;
PrintManagerService printManager = null;
+ MediaRouterService mediaRouter = null;
// Bring up services needed for UI.
if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
@@ -804,6 +806,16 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("starting Print Service", e);
}
+
+ if (!disableNonCoreServices) {
+ try {
+ Slog.i(TAG, "Media Router Service");
+ mediaRouter = new MediaRouterService(context);
+ ServiceManager.addService(Context.MEDIA_ROUTER_SERVICE, mediaRouter);
+ } catch (Throwable e) {
+ reportWtf("starting MediaRouterService", e);
+ }
+ }
}
// Before things start rolling, be sure we have decided whether
@@ -916,6 +928,7 @@ class ServerThread {
final InputManagerService inputManagerF = inputManager;
final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
final PrintManagerService printManagerF = printManager;
+ final MediaRouterService mediaRouterF = mediaRouter;
// We now tell the activity manager it is okay to run third party
// code. It will call back into us once it has gotten to the state
@@ -1063,6 +1076,12 @@ class ServerThread {
} catch (Throwable e) {
reportWtf("Notifying PrintManagerService running", e);
}
+
+ try {
+ if (mediaRouterF != null) mediaRouterF.systemRunning();
+ } catch (Throwable e) {
+ reportWtf("Notifying MediaRouterService running", e);
+ }
}
});
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index ea0b978a..5476fde 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -1187,6 +1187,7 @@ public final class ActiveServices {
if (!mRestartingServices.contains(r)) {
r.createdFromFg = false;
mRestartingServices.add(r);
+ r.makeRestarting(mAm.mProcessStats.getMemFactorLocked(), now);
}
r.cancelNotification();
@@ -1220,6 +1221,9 @@ public final class ActiveServices {
if (removed || callingUid != r.appInfo.uid) {
r.resetRestartCounter();
}
+ if (removed) {
+ r.clearRestarting(mAm.mProcessStats.getMemFactorLocked(), SystemClock.uptimeMillis());
+ }
mAm.mHandler.removeCallbacks(r.restarter);
return true;
}
@@ -1243,7 +1247,9 @@ public final class ActiveServices {
// We are now bringing the service up, so no longer in the
// restarting state.
- mRestartingServices.remove(r);
+ if (mRestartingServices.remove(r)) {
+ r.clearRestarting(mAm.mProcessStats.getMemFactorLocked(), SystemClock.uptimeMillis());
+ }
// Make sure this service is no longer considered delayed, we are starting it now.
if (r.delayed) {
@@ -1581,6 +1587,7 @@ public final class ActiveServices {
}
r.app.services.remove(r);
if (r.app.thread != null) {
+ updateServiceForegroundLocked(r.app, false);
try {
bumpServiceExecutingLocked(r, false, "destroy");
mDestroyingServices.add(r);
@@ -1591,7 +1598,6 @@ public final class ActiveServices {
+ r.shortName, e);
serviceProcessGoneLocked(r);
}
- updateServiceForegroundLocked(r.app, false);
} else {
if (DEBUG_SERVICE) Slog.v(
TAG, "Removed service that has no process: " + r);
@@ -1816,6 +1822,9 @@ public final class ActiveServices {
r.tracker = null;
}
}
+ if (finishing) {
+ r.app = null;
+ }
}
}
@@ -1960,8 +1969,7 @@ public final class ActiveServices {
}
}
- final void killServicesLocked(ProcessRecord app,
- boolean allowRestart) {
+ final void killServicesLocked(ProcessRecord app, boolean allowRestart) {
// Report disconnected services.
if (false) {
// XXX we are letting the client link to the service for
@@ -1990,16 +1998,8 @@ public final class ActiveServices {
}
}
- // Clean up any connections this application has to other services.
- for (int i=app.connections.size()-1; i>=0; i--) {
- ConnectionRecord r = app.connections.valueAt(i);
- removeConnectionLocked(r, app, null);
- }
- app.connections.clear();
-
+ // First clear app state from services.
for (int i=app.services.size()-1; i>=0; i--) {
- // Any services running in the application need to be placed
- // back in the pending list.
ServiceRecord sr = app.services.valueAt(i);
synchronized (sr.stats.getBatteryStats()) {
sr.stats.stopLaunchedLocked();
@@ -2020,8 +2020,21 @@ public final class ActiveServices {
b.binder = null;
b.requested = b.received = b.hasBound = false;
}
+ }
- if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
+ // Clean up any connections this application has to other services.
+ for (int i=app.connections.size()-1; i>=0; i--) {
+ ConnectionRecord r = app.connections.valueAt(i);
+ removeConnectionLocked(r, app, null);
+ }
+ app.connections.clear();
+
+ // Now do remaining service cleanup.
+ for (int i=app.services.size()-1; i>=0; i--) {
+ // Any services running in the application may need to be placed
+ // back in the pending list.
+ ServiceRecord sr = app.services.valueAt(i);
+ if (allowRestart && sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags
&ApplicationInfo.FLAG_PERSISTENT) == 0) {
Slog.w(TAG, "Service crashed " + sr.crashCount
+ " times, stopping: " + sr);
@@ -2054,6 +2067,17 @@ public final class ActiveServices {
if (!allowRestart) {
app.services.clear();
+
+ // Make sure there are no more restarting services for this process.
+ for (int i=mRestartingServices.size()-1; i>=0; i--) {
+ ServiceRecord r = mRestartingServices.get(i);
+ if (r.processName.equals(app.processName) &&
+ r.serviceInfo.applicationInfo.uid == app.info.uid) {
+ mRestartingServices.remove(i);
+ r.clearRestarting(mAm.mProcessStats.getMemFactorLocked(),
+ SystemClock.uptimeMillis());
+ }
+ }
}
// Make sure we have no more records on the stopping list.
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 164e7b7..fe83e9f 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -11901,6 +11901,7 @@ public final class ActivityManagerService extends ActivityManagerNative
boolean dumpDalvik = false;
boolean oomOnly = false;
boolean isCompact = false;
+ boolean localOnly = false;
int opti = 0;
while (opti < args.length) {
@@ -11919,12 +11920,15 @@ public final class ActivityManagerService extends ActivityManagerNative
isCompact = true;
} else if ("--oom".equals(opt)) {
oomOnly = true;
+ } else if ("--local".equals(opt)) {
+ localOnly = true;
} else if ("-h".equals(opt)) {
pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]");
pw.println(" -a: include all available information for each process.");
pw.println(" -d: include dalvik details when dumping process details.");
pw.println(" -c: dump in a compact machine-parseable representation.");
pw.println(" --oom: only show processes organized by oom adj.");
+ pw.println(" --local: only collect details locally, don't call process.");
pw.println("If [process] is specified it can be the name or ");
pw.println("pid of a specific process to dump.");
return;
@@ -12041,14 +12045,22 @@ public final class ActivityManagerService extends ActivityManagerNative
mi.dalvikPrivateDirty = (int)tmpLong[0];
}
if (dumpDetails) {
- try {
- pw.flush();
- thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
- dumpDalvik, innerArgs);
- } catch (RemoteException e) {
- if (!isCheckinRequest) {
- pw.println("Got RemoteException!");
+ if (localOnly) {
+ ActivityThread.dumpMemInfoTable(pw, mi, isCheckinRequest, dumpFullDetails,
+ dumpDalvik, pid, r.processName, 0, 0, 0, 0, 0, 0);
+ if (isCheckinRequest) {
+ pw.println();
+ }
+ } else {
+ try {
pw.flush();
+ thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails,
+ dumpDalvik, innerArgs);
+ } catch (RemoteException e) {
+ if (!isCheckinRequest) {
+ pw.println("Got RemoteException!");
+ pw.flush();
+ }
}
}
}
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 809452c..569440d 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -36,7 +36,6 @@ import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE;
import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES;
import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID;
-import android.os.Trace;
import com.android.internal.os.BatteryStatsImpl;
import com.android.internal.util.Objects;
import com.android.server.Watchdog;
@@ -64,12 +63,14 @@ import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
+import android.os.Debug;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.os.Trace;
import android.os.UserHandle;
import android.util.EventLog;
import android.util.Slog;
@@ -1143,7 +1144,7 @@ final class ActivityStack {
} else if (isActivityOverHome(r)) {
if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r);
showHomeBehindStack = true;
- behindFullscreen = true;
+ behindFullscreen = !isHomeStack();
}
} else {
if (DEBUG_VISBILITY) Slog.v(
@@ -1910,26 +1911,38 @@ final class ActivityStack {
// bottom of the activity stack. This also keeps it
// correctly ordered with any activities we previously
// moved.
+ final ThumbnailHolder newThumbHolder;
+ final TaskRecord targetTask;
final ActivityRecord bottom =
!mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ?
- mTaskHistory.get(0).mActivities.get(0) : null;
+ mTaskHistory.get(0).mActivities.get(0) : null;
if (bottom != null && target.taskAffinity != null
&& target.taskAffinity.equals(bottom.task.affinity)) {
// If the activity currently at the bottom has the
// same task affinity as the one we are moving,
// then merge it into the same task.
- target.setTask(bottom.task, bottom.thumbHolder, false);
+ targetTask = bottom.task;
+ newThumbHolder = bottom.thumbHolder == null ? targetTask : bottom.thumbHolder;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ " out to bottom task " + bottom.task);
} else {
- target.setTask(createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
- null, false), null, false);
- target.task.affinityIntent = target.intent;
+ targetTask = createTaskRecord(mStackSupervisor.getNextTaskId(), target.info,
+ null, false);
+ newThumbHolder = targetTask;
+ targetTask.affinityIntent = target.intent;
if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target
+ " out to new task " + target.task);
}
- final TaskRecord targetTask = target.task;
+ if (clearWhenTaskReset) {
+ // This is the start of a new sub-task.
+ if (target.thumbHolder == null) {
+ target.thumbHolder = new ThumbnailHolder();
+ }
+ } else {
+ target.thumbHolder = newThumbHolder;
+ }
+
final int targetTaskId = targetTask.taskId;
mWindowManager.setAppGroupId(target.appToken, targetTaskId);
@@ -1950,8 +1963,8 @@ final class ActivityStack {
}
}
if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task="
- + task + " adding to task=" + targetTask,
- new RuntimeException("here").fillInStackTrace());
+ + task + " adding to task=" + targetTask
+ + " Callers=" + Debug.getCallers(4));
if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p
+ " out to target's task " + target.task);
p.setTask(targetTask, curThumbHolder, false);
diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java
index 0dd950e..2d59678 100644
--- a/services/java/com/android/server/am/BatteryStatsService.java
+++ b/services/java/com/android/server/am/BatteryStatsService.java
@@ -419,6 +419,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub {
}
}
+ public void noteWifiBatchedScanStartedFromSource(WorkSource ws, int csph) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteWifiBatchedScanStartedFromSourceLocked(ws, csph);
+ }
+ }
+
+ public void noteWifiBatchedScanStoppedFromSource(WorkSource ws) {
+ enforceCallingPermission();
+ synchronized (mStats) {
+ mStats.noteWifiBatchedScanStoppedFromSourceLocked(ws);
+ }
+ }
+
public void noteWifiMulticastEnabledFromSource(WorkSource ws) {
enforceCallingPermission();
synchronized (mStats) {
diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java
index 576adc2..423e540 100644
--- a/services/java/com/android/server/am/ConnectionRecord.java
+++ b/services/java/com/android/server/am/ConnectionRecord.java
@@ -27,7 +27,7 @@ import java.io.PrintWriter;
*/
final class ConnectionRecord {
final AppBindRecord binding; // The application/service binding.
- final ActivityRecord activity; // If non-null, the owning activity.
+ final ActivityRecord activity; // If non-null, the owning activity.
final IServiceConnection conn; // The client connection.
final int flags; // Binding options.
final int clientLabel; // String resource labeling this client.
diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java
index 8d16880..e05fcda 100644
--- a/services/java/com/android/server/am/ProcessStatsService.java
+++ b/services/java/com/android/server/am/ProcessStatsService.java
@@ -750,23 +750,12 @@ public final class ProcessStatsService extends IProcessStats.Stub {
return;
} else {
// Not an option, last argument must be a package name.
- try {
- IPackageManager pm = AppGlobals.getPackageManager();
- if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) {
- reqPackage = arg;
- // Include all details, since we know we are only going to
- // be dumping a smaller set of data. In fact only the details
- // container per-package data, so that are needed to be able
- // to dump anything at all when filtering by package.
- dumpDetails = true;
- }
- } catch (RemoteException e) {
- }
- if (reqPackage == null) {
- pw.println("Unknown package: " + arg);
- dumpHelp(pw);
- return;
- }
+ reqPackage = arg;
+ // Include all details, since we know we are only going to
+ // be dumping a smaller set of data. In fact only the details
+ // container per-package data, so that are needed to be able
+ // to dump anything at all when filtering by package.
+ dumpDetails = true;
}
}
}
@@ -816,13 +805,14 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
return;
} else if (aggregateHours != 0) {
+ pw.print("AGGREGATED OVER LAST "); pw.print(aggregateHours); pw.println(" HOURS:");
dumpAggregatedStats(pw, aggregateHours, now, reqPackage, isCompact,
dumpDetails, dumpFullDetails, dumpAll, activeOnly);
return;
}
boolean sepNeeded = false;
- if (!currentOnly || isCheckin) {
+ if (dumpAll || isCheckin) {
mWriteLock.lock();
try {
ArrayList<String> files = getCommittedFiles(0, false, !isCheckin);
@@ -882,11 +872,11 @@ public final class ProcessStatsService extends IProcessStats.Stub {
}
}
if (!isCheckin) {
- if (dumpAll) {
+ if (!currentOnly) {
if (sepNeeded) {
pw.println();
- pw.println("AGGREGATED OVER LAST 24 HOURS:");
}
+ pw.println("AGGREGATED OVER LAST 24 HOURS:");
dumpAggregatedStats(pw, 24, now, reqPackage, isCompact,
dumpDetails, dumpFullDetails, dumpAll, activeOnly);
pw.println();
@@ -901,8 +891,8 @@ public final class ProcessStatsService extends IProcessStats.Stub {
} else {
if (sepNeeded) {
pw.println();
- pw.println("CURRENT STATS:");
}
+ pw.println("CURRENT STATS:");
if (dumpDetails || dumpFullDetails) {
mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll,
activeOnly);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index cc1172a..84b1c3a 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -85,6 +85,7 @@ final class ServiceRecord extends Binder {
ProcessRecord app; // where this service is running or null.
ProcessRecord isolatedProc; // keep track of isolated process, if requested
ProcessStats.ServiceState tracker; // tracking service execution, may be null
+ ProcessStats.ServiceState restartTracker; // tracking service restart
boolean delayed; // are we waiting to start this service in the background?
boolean isForeground; // is service currently in foreground mode?
int foregroundId; // Notification ID of last foreground req.
@@ -340,6 +341,26 @@ final class ServiceRecord extends Binder {
}
}
+ public void makeRestarting(int memFactor, long now) {
+ if (restartTracker == null) {
+ if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) {
+ restartTracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName,
+ serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name);
+ }
+ if (restartTracker == null) {
+ return;
+ }
+ }
+ restartTracker.setRestarting(true, memFactor, now);
+ }
+
+ public void clearRestarting(int memFactor, long now) {
+ if (restartTracker != null) {
+ restartTracker.setRestarting(false, memFactor, now);
+ restartTracker = null;
+ }
+ }
+
public AppBindRecord retrieveAppBindingLocked(Intent intent,
ProcessRecord app) {
Intent.FilterComparison filter = new Intent.FilterComparison(intent);
diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java
index d749e6c..3145805 100644
--- a/services/java/com/android/server/input/InputManagerService.java
+++ b/services/java/com/android/server/input/InputManagerService.java
@@ -294,6 +294,7 @@ public class InputManagerService extends IInputManager.Stub
IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+ filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
filter.addDataScheme("package");
mContext.registerReceiver(new BroadcastReceiver() {
@Override
diff --git a/services/java/com/android/server/media/MediaRouterService.java b/services/java/com/android/server/media/MediaRouterService.java
new file mode 100644
index 0000000..2caab40
--- /dev/null
+++ b/services/java/com/android/server/media/MediaRouterService.java
@@ -0,0 +1,1351 @@
+/*
+ * 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 com.android.internal.util.Objects;
+import com.android.server.Watchdog;
+
+import android.Manifest;
+import android.app.ActivityManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.media.AudioSystem;
+import android.media.IMediaRouterClient;
+import android.media.IMediaRouterService;
+import android.media.MediaRouter;
+import android.media.MediaRouterClientState;
+import android.media.RemoteDisplayState;
+import android.media.RemoteDisplayState.RemoteDisplayInfo;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.TimeUtils;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Provides a mechanism for discovering media routes and manages media playback
+ * behalf of applications.
+ * <p>
+ * Currently supports discovering remote displays via remote display provider
+ * services that have been registered by applications.
+ * </p>
+ */
+public final class MediaRouterService extends IMediaRouterService.Stub
+ implements Watchdog.Monitor {
+ private static final String TAG = "MediaRouterService";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Timeout in milliseconds for a selected route to transition from a
+ * disconnected state to a connecting state. If we don't observe any
+ * progress within this interval, then we will give up and unselect the route.
+ */
+ static final long CONNECTING_TIMEOUT = 5000;
+
+ /**
+ * Timeout in milliseconds for a selected route to transition from a
+ * connecting state to a connected state. If we don't observe any
+ * progress within this interval, then we will give up and unselect the route.
+ */
+ static final long CONNECTED_TIMEOUT = 60000;
+
+ private final Context mContext;
+
+ // State guarded by mLock.
+ private final Object mLock = new Object();
+ private final SparseArray<UserRecord> mUserRecords = new SparseArray<UserRecord>();
+ private final ArrayMap<IBinder, ClientRecord> mAllClientRecords =
+ new ArrayMap<IBinder, ClientRecord>();
+ private int mCurrentUserId = -1;
+
+ public MediaRouterService(Context context) {
+ mContext = context;
+ Watchdog.getInstance().addMonitor(this);
+ }
+
+ public void systemRunning() {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_USER_SWITCHED);
+ mContext.registerReceiver(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(Intent.ACTION_USER_SWITCHED)) {
+ switchUser();
+ }
+ }
+ }, filter);
+
+ switchUser();
+ }
+
+ @Override
+ public void monitor() {
+ synchronized (mLock) { /* check for deadlock */ }
+ }
+
+ // Binder call
+ @Override
+ public void registerClientAsUser(IMediaRouterClient client, String packageName, int userId) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final int uid = Binder.getCallingUid();
+ if (!validatePackageName(uid, packageName)) {
+ throw new SecurityException("packageName must match the calling uid");
+ }
+
+ final int pid = Binder.getCallingPid();
+ final int resolvedUserId = ActivityManager.handleIncomingUser(pid, uid, userId,
+ false /*allowAll*/, true /*requireFull*/, "registerClientAsUser", packageName);
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ registerClientLocked(client, pid, packageName, resolvedUserId);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void unregisterClient(IMediaRouterClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ unregisterClientLocked(client, false);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public MediaRouterClientState getState(IMediaRouterClient client) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ return getStateLocked(client);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void setDiscoveryRequest(IMediaRouterClient client,
+ int routeTypes, boolean activeScan) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setDiscoveryRequestLocked(client, routeTypes, activeScan);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ // A null routeId means that the client wants to unselect its current route.
+ // The explicit flag indicates whether the change was explicitly requested by the
+ // user or the application which may cause changes to propagate out to the rest
+ // of the system. Should be false when the change is in response to a new globally
+ // selected route or a default selection.
+ @Override
+ public void setSelectedRoute(IMediaRouterClient client, String routeId, boolean explicit) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ setSelectedRouteLocked(client, routeId, explicit);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void requestSetVolume(IMediaRouterClient client, String routeId, int volume) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestSetVolumeLocked(client, routeId, volume);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void requestUpdateVolume(IMediaRouterClient client, String routeId, int direction) {
+ if (client == null) {
+ throw new IllegalArgumentException("client must not be null");
+ }
+ if (routeId == null) {
+ throw new IllegalArgumentException("routeId must not be null");
+ }
+
+ final long token = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ requestUpdateVolumeLocked(client, routeId, direction);
+ }
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ // Binder call
+ @Override
+ public void dump(FileDescriptor fd, final PrintWriter pw, String[] args) {
+ if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP)
+ != PackageManager.PERMISSION_GRANTED) {
+ pw.println("Permission Denial: can't dump MediaRouterService from from pid="
+ + Binder.getCallingPid()
+ + ", uid=" + Binder.getCallingUid());
+ return;
+ }
+
+ pw.println("MEDIA ROUTER SERVICE (dumpsys media_router)");
+ pw.println();
+ pw.println("Global state");
+ pw.println(" mCurrentUserId=" + mCurrentUserId);
+
+ synchronized (mLock) {
+ final int count = mUserRecords.size();
+ for (int i = 0; i < count; i++) {
+ UserRecord userRecord = mUserRecords.valueAt(i);
+ pw.println();
+ userRecord.dump(pw, "");
+ }
+ }
+ }
+
+ void switchUser() {
+ synchronized (mLock) {
+ int userId = ActivityManager.getCurrentUser();
+ if (mCurrentUserId != userId) {
+ final int oldUserId = mCurrentUserId;
+ mCurrentUserId = userId; // do this first
+
+ UserRecord oldUser = mUserRecords.get(oldUserId);
+ if (oldUser != null) {
+ oldUser.mHandler.sendEmptyMessage(UserHandler.MSG_STOP);
+ disposeUserIfNeededLocked(oldUser); // since no longer current user
+ }
+
+ UserRecord newUser = mUserRecords.get(userId);
+ if (newUser != null) {
+ newUser.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ }
+ }
+ }
+ }
+
+ void clientDied(ClientRecord clientRecord) {
+ synchronized (mLock) {
+ unregisterClientLocked(clientRecord.mClient, true);
+ }
+ }
+
+ private void registerClientLocked(IMediaRouterClient client,
+ int pid, String packageName, int userId) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord == null) {
+ boolean newUser = false;
+ UserRecord userRecord = mUserRecords.get(userId);
+ if (userRecord == null) {
+ userRecord = new UserRecord(userId);
+ newUser = true;
+ }
+ clientRecord = new ClientRecord(userRecord, client, pid, packageName);
+ try {
+ binder.linkToDeath(clientRecord, 0);
+ } catch (RemoteException ex) {
+ throw new RuntimeException("Media router client died prematurely.", ex);
+ }
+
+ if (newUser) {
+ mUserRecords.put(userId, userRecord);
+ initializeUserLocked(userRecord);
+ }
+
+ userRecord.mClientRecords.add(clientRecord);
+ mAllClientRecords.put(binder, clientRecord);
+ initializeClientLocked(clientRecord);
+ }
+ }
+
+ private void unregisterClientLocked(IMediaRouterClient client, boolean died) {
+ ClientRecord clientRecord = mAllClientRecords.remove(client.asBinder());
+ if (clientRecord != null) {
+ UserRecord userRecord = clientRecord.mUserRecord;
+ userRecord.mClientRecords.remove(clientRecord);
+ disposeClientLocked(clientRecord, died);
+ disposeUserIfNeededLocked(userRecord); // since client removed from user
+ }
+ }
+
+ private MediaRouterClientState getStateLocked(IMediaRouterClient client) {
+ ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+ if (clientRecord != null) {
+ return clientRecord.mUserRecord.mState;
+ }
+ return null;
+ }
+
+ private void setDiscoveryRequestLocked(IMediaRouterClient client,
+ int routeTypes, boolean activeScan) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ if (clientRecord.mRouteTypes != routeTypes
+ || clientRecord.mActiveScan != activeScan) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Set discovery request, routeTypes=0x"
+ + Integer.toHexString(routeTypes) + ", activeScan=" + activeScan);
+ }
+ clientRecord.mRouteTypes = routeTypes;
+ clientRecord.mActiveScan = activeScan;
+ clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+ UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+ }
+ }
+ }
+
+ private void setSelectedRouteLocked(IMediaRouterClient client,
+ String routeId, boolean explicit) {
+ ClientRecord clientRecord = mAllClientRecords.get(client.asBinder());
+ if (clientRecord != null) {
+ final String oldRouteId = clientRecord.mSelectedRouteId;
+ if (!Objects.equal(routeId, oldRouteId)) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Set selected route, routeId=" + routeId
+ + ", oldRouteId=" + oldRouteId
+ + ", explicit=" + explicit);
+ }
+
+ clientRecord.mSelectedRouteId = routeId;
+ if (explicit) {
+ if (oldRouteId != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_UNSELECT_ROUTE, oldRouteId).sendToTarget();
+ }
+ if (routeId != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_SELECT_ROUTE, routeId).sendToTarget();
+ }
+ }
+ }
+ }
+ }
+
+ private void requestSetVolumeLocked(IMediaRouterClient client,
+ String routeId, int volume) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_REQUEST_SET_VOLUME, volume, 0, routeId).sendToTarget();
+ }
+ }
+
+ private void requestUpdateVolumeLocked(IMediaRouterClient client,
+ String routeId, int direction) {
+ final IBinder binder = client.asBinder();
+ ClientRecord clientRecord = mAllClientRecords.get(binder);
+ if (clientRecord != null) {
+ clientRecord.mUserRecord.mHandler.obtainMessage(
+ UserHandler.MSG_REQUEST_UPDATE_VOLUME, direction, 0, routeId).sendToTarget();
+ }
+ }
+
+ private void initializeUserLocked(UserRecord userRecord) {
+ if (DEBUG) {
+ Slog.d(TAG, userRecord + ": Initialized");
+ }
+ if (userRecord.mUserId == mCurrentUserId) {
+ userRecord.mHandler.sendEmptyMessage(UserHandler.MSG_START);
+ }
+ }
+
+ private void disposeUserIfNeededLocked(UserRecord userRecord) {
+ // If there are no records left and the user is no longer current then go ahead
+ // and purge the user record and all of its associated state. If the user is current
+ // then leave it alone since we might be connected to a route or want to query
+ // the same route information again soon.
+ if (userRecord.mUserId != mCurrentUserId
+ && userRecord.mClientRecords.isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG, userRecord + ": Disposed");
+ }
+ mUserRecords.remove(userRecord.mUserId);
+ // Note: User already stopped (by switchUser) so no need to send stop message here.
+ }
+ }
+
+ private void initializeClientLocked(ClientRecord clientRecord) {
+ if (DEBUG) {
+ Slog.d(TAG, clientRecord + ": Registered");
+ }
+ }
+
+ private void disposeClientLocked(ClientRecord clientRecord, boolean died) {
+ if (DEBUG) {
+ if (died) {
+ Slog.d(TAG, clientRecord + ": Died!");
+ } else {
+ Slog.d(TAG, clientRecord + ": Unregistered");
+ }
+ }
+ if (clientRecord.mRouteTypes != 0 || clientRecord.mActiveScan) {
+ clientRecord.mUserRecord.mHandler.sendEmptyMessage(
+ UserHandler.MSG_UPDATE_DISCOVERY_REQUEST);
+ }
+ clientRecord.dispose();
+ }
+
+ private boolean validatePackageName(int uid, String packageName) {
+ if (packageName != null) {
+ String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid);
+ if (packageNames != null) {
+ for (String n : packageNames) {
+ if (n.equals(packageName)) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Information about a particular client of the media router.
+ * The contents of this object is guarded by mLock.
+ */
+ final class ClientRecord implements DeathRecipient {
+ public final UserRecord mUserRecord;
+ public final IMediaRouterClient mClient;
+ public final int mPid;
+ public final String mPackageName;
+
+ public int mRouteTypes;
+ public boolean mActiveScan;
+ public String mSelectedRouteId;
+
+ public ClientRecord(UserRecord userRecord, IMediaRouterClient client,
+ int pid, String packageName) {
+ mUserRecord = userRecord;
+ mClient = client;
+ mPid = pid;
+ mPackageName = packageName;
+ }
+
+ public void dispose() {
+ mClient.asBinder().unlinkToDeath(this, 0);
+ }
+
+ @Override
+ public void binderDied() {
+ clientDied(this);
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mRouteTypes=0x" + Integer.toHexString(mRouteTypes));
+ pw.println(indent + "mActiveScan=" + mActiveScan);
+ pw.println(indent + "mSelectedRouteId=" + mSelectedRouteId);
+ }
+
+ @Override
+ public String toString() {
+ return "Client " + mPackageName + " (pid " + mPid + ")";
+ }
+ }
+
+ /**
+ * Information about a particular user.
+ * The contents of this object is guarded by mLock.
+ */
+ final class UserRecord {
+ public final int mUserId;
+ public final ArrayList<ClientRecord> mClientRecords = new ArrayList<ClientRecord>();
+ public final UserHandler mHandler;
+ public MediaRouterClientState mState;
+
+ public UserRecord(int userId) {
+ mUserId = userId;
+ mHandler = new UserHandler(MediaRouterService.this, this);
+ }
+
+ public void dump(final PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ final int clientCount = mClientRecords.size();
+ if (clientCount != 0) {
+ for (int i = 0; i < clientCount; i++) {
+ mClientRecords.get(i).dump(pw, indent);
+ }
+ } else {
+ pw.println(indent + "<no clients>");
+ }
+
+ if (!mHandler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ mHandler.dump(pw, indent);
+ }
+ }, 1000)) {
+ pw.println(indent + "<could not dump handler state>");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "User " + mUserId;
+ }
+ }
+
+ /**
+ * Media router handler
+ * <p>
+ * Since remote display providers are designed to be single-threaded by nature,
+ * this class encapsulates all of the associated functionality and exports state
+ * to the service as it evolves.
+ * </p><p>
+ * One important task of this class is to keep track of the current globally selected
+ * route id for certain routes that have global effects, such as remote displays.
+ * Global route selections override local selections made within apps. The change
+ * is propagated to all apps so that they are all in sync. Synchronization works
+ * both ways. Whenever the globally selected route is explicitly unselected by any
+ * app, then it becomes unselected globally and all apps are informed.
+ * </p><p>
+ * This class is currently hardcoded to work with remote display providers but
+ * it is intended to be eventually extended to support more general route providers
+ * similar to the support library media router.
+ * </p>
+ */
+ static final class UserHandler extends Handler
+ implements RemoteDisplayProviderWatcher.Callback,
+ RemoteDisplayProviderProxy.Callback {
+ public static final int MSG_START = 1;
+ public static final int MSG_STOP = 2;
+ public static final int MSG_UPDATE_DISCOVERY_REQUEST = 3;
+ public static final int MSG_SELECT_ROUTE = 4;
+ public static final int MSG_UNSELECT_ROUTE = 5;
+ public static final int MSG_REQUEST_SET_VOLUME = 6;
+ public static final int MSG_REQUEST_UPDATE_VOLUME = 7;
+ private static final int MSG_UPDATE_CLIENT_STATE = 8;
+ private static final int MSG_CONNECTION_TIMED_OUT = 9;
+
+ private static final int TIMEOUT_REASON_NOT_AVAILABLE = 1;
+ private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTING = 2;
+ private static final int TIMEOUT_REASON_WAITING_FOR_CONNECTED = 3;
+
+ private final MediaRouterService mService;
+ private final UserRecord mUserRecord;
+ private final RemoteDisplayProviderWatcher mWatcher;
+ private final ArrayList<ProviderRecord> mProviderRecords =
+ new ArrayList<ProviderRecord>();
+ private final ArrayList<IMediaRouterClient> mTempClients =
+ new ArrayList<IMediaRouterClient>();
+
+ private boolean mRunning;
+ private int mDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+ private RouteRecord mGloballySelectedRouteRecord;
+ private int mConnectionTimeoutReason;
+ private long mConnectionTimeoutStartTime;
+ private boolean mClientStateUpdateScheduled;
+
+ public UserHandler(MediaRouterService service, UserRecord userRecord) {
+ super(Looper.getMainLooper(), null, true);
+ mService = service;
+ mUserRecord = userRecord;
+ mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this,
+ this, mUserRecord.mUserId);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_START: {
+ start();
+ break;
+ }
+ case MSG_STOP: {
+ stop();
+ break;
+ }
+ case MSG_UPDATE_DISCOVERY_REQUEST: {
+ updateDiscoveryRequest();
+ break;
+ }
+ case MSG_SELECT_ROUTE: {
+ selectRoute((String)msg.obj);
+ break;
+ }
+ case MSG_UNSELECT_ROUTE: {
+ unselectRoute((String)msg.obj);
+ break;
+ }
+ case MSG_REQUEST_SET_VOLUME: {
+ requestSetVolume((String)msg.obj, msg.arg1);
+ break;
+ }
+ case MSG_REQUEST_UPDATE_VOLUME: {
+ requestUpdateVolume((String)msg.obj, msg.arg1);
+ break;
+ }
+ case MSG_UPDATE_CLIENT_STATE: {
+ updateClientState();
+ break;
+ }
+ case MSG_CONNECTION_TIMED_OUT: {
+ connectionTimedOut();
+ break;
+ }
+ }
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Handler");
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mRunning=" + mRunning);
+ pw.println(indent + "mDiscoveryMode=" + mDiscoveryMode);
+ pw.println(indent + "mGloballySelectedRouteRecord=" + mGloballySelectedRouteRecord);
+ pw.println(indent + "mConnectionTimeoutReason=" + mConnectionTimeoutReason);
+ pw.println(indent + "mConnectionTimeoutStartTime=" + (mConnectionTimeoutReason != 0 ?
+ TimeUtils.formatUptime(mConnectionTimeoutStartTime) : "<n/a>"));
+
+ mWatcher.dump(pw, prefix);
+
+ final int providerCount = mProviderRecords.size();
+ if (providerCount != 0) {
+ for (int i = 0; i < providerCount; i++) {
+ mProviderRecords.get(i).dump(pw, prefix);
+ }
+ } else {
+ pw.println(indent + "<no providers>");
+ }
+ }
+
+ private void start() {
+ if (!mRunning) {
+ mRunning = true;
+ mWatcher.start(); // also starts all providers
+ }
+ }
+
+ private void stop() {
+ if (mRunning) {
+ mRunning = false;
+ unselectGloballySelectedRoute();
+ mWatcher.stop(); // also stops all providers
+ }
+ }
+
+ private void updateDiscoveryRequest() {
+ int routeTypes = 0;
+ boolean activeScan = false;
+ synchronized (mService.mLock) {
+ final int count = mUserRecord.mClientRecords.size();
+ for (int i = 0; i < count; i++) {
+ ClientRecord clientRecord = mUserRecord.mClientRecords.get(i);
+ routeTypes |= clientRecord.mRouteTypes;
+ activeScan |= clientRecord.mActiveScan;
+ }
+ }
+
+ final int newDiscoveryMode;
+ if ((routeTypes & (MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+ | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) != 0) {
+ if (activeScan) {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_ACTIVE;
+ } else {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_PASSIVE;
+ }
+ } else {
+ newDiscoveryMode = RemoteDisplayState.DISCOVERY_MODE_NONE;
+ }
+
+ if (mDiscoveryMode != newDiscoveryMode) {
+ mDiscoveryMode = newDiscoveryMode;
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ mProviderRecords.get(i).getProvider().setDiscoveryMode(mDiscoveryMode);
+ }
+ }
+ }
+
+ private void selectRoute(String routeId) {
+ if (routeId != null
+ && (mGloballySelectedRouteRecord == null
+ || !routeId.equals(mGloballySelectedRouteRecord.getUniqueId()))) {
+ RouteRecord routeRecord = findRouteRecord(routeId);
+ if (routeRecord != null) {
+ unselectGloballySelectedRoute();
+
+ Slog.i(TAG, "Selected global route:" + routeRecord);
+ mGloballySelectedRouteRecord = routeRecord;
+ checkGloballySelectedRouteState();
+ routeRecord.getProvider().setSelectedDisplay(routeRecord.getDescriptorId());
+
+ scheduleUpdateClientState();
+ }
+ }
+ }
+
+ private void unselectRoute(String routeId) {
+ if (routeId != null
+ && mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ unselectGloballySelectedRoute();
+ }
+ }
+
+ private void unselectGloballySelectedRoute() {
+ if (mGloballySelectedRouteRecord != null) {
+ Slog.i(TAG, "Unselected global route:" + mGloballySelectedRouteRecord);
+ mGloballySelectedRouteRecord.getProvider().setSelectedDisplay(null);
+ mGloballySelectedRouteRecord = null;
+ checkGloballySelectedRouteState();
+
+ scheduleUpdateClientState();
+ }
+ }
+
+ private void requestSetVolume(String routeId, int volume) {
+ if (mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ mGloballySelectedRouteRecord.getProvider().setDisplayVolume(volume);
+ }
+ }
+
+ private void requestUpdateVolume(String routeId, int direction) {
+ if (mGloballySelectedRouteRecord != null
+ && routeId.equals(mGloballySelectedRouteRecord.getUniqueId())) {
+ mGloballySelectedRouteRecord.getProvider().adjustDisplayVolume(direction);
+ }
+ }
+
+ @Override
+ public void addProvider(RemoteDisplayProviderProxy provider) {
+ provider.setCallback(this);
+ provider.setDiscoveryMode(mDiscoveryMode);
+ provider.setSelectedDisplay(null); // just to be safe
+
+ ProviderRecord providerRecord = new ProviderRecord(provider);
+ mProviderRecords.add(providerRecord);
+ providerRecord.updateDescriptor(provider.getDisplayState());
+
+ scheduleUpdateClientState();
+ }
+
+ @Override
+ public void removeProvider(RemoteDisplayProviderProxy provider) {
+ int index = findProviderRecord(provider);
+ if (index >= 0) {
+ ProviderRecord providerRecord = mProviderRecords.remove(index);
+ providerRecord.updateDescriptor(null); // mark routes invalid
+ provider.setCallback(null);
+ provider.setDiscoveryMode(RemoteDisplayState.DISCOVERY_MODE_NONE);
+
+ checkGloballySelectedRouteState();
+ scheduleUpdateClientState();
+ }
+ }
+
+ @Override
+ public void onDisplayStateChanged(RemoteDisplayProviderProxy provider,
+ RemoteDisplayState state) {
+ updateProvider(provider, state);
+ }
+
+ private void updateProvider(RemoteDisplayProviderProxy provider,
+ RemoteDisplayState state) {
+ int index = findProviderRecord(provider);
+ if (index >= 0) {
+ ProviderRecord providerRecord = mProviderRecords.get(index);
+ if (providerRecord.updateDescriptor(state)) {
+ checkGloballySelectedRouteState();
+ scheduleUpdateClientState();
+ }
+ }
+ }
+
+ /**
+ * This function is called whenever the state of the globally selected route
+ * may have changed. It checks the state and updates timeouts or unselects
+ * the route as appropriate.
+ */
+ private void checkGloballySelectedRouteState() {
+ // Unschedule timeouts when the route is unselected.
+ if (mGloballySelectedRouteRecord == null) {
+ updateConnectionTimeout(0);
+ return;
+ }
+
+ // Ensure that the route is still present and enabled.
+ if (!mGloballySelectedRouteRecord.isValid()
+ || !mGloballySelectedRouteRecord.isEnabled()) {
+ updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+ return;
+ }
+
+ // Check the route status.
+ switch (mGloballySelectedRouteRecord.getStatus()) {
+ case MediaRouter.RouteInfo.STATUS_NONE:
+ case MediaRouter.RouteInfo.STATUS_CONNECTED:
+ if (mConnectionTimeoutReason != 0) {
+ Slog.i(TAG, "Connected to global route: "
+ + mGloballySelectedRouteRecord);
+ }
+ updateConnectionTimeout(0);
+ break;
+ case MediaRouter.RouteInfo.STATUS_CONNECTING:
+ if (mConnectionTimeoutReason != 0) {
+ Slog.i(TAG, "Connecting to global route: "
+ + mGloballySelectedRouteRecord);
+ }
+ updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTED);
+ break;
+ case MediaRouter.RouteInfo.STATUS_SCANNING:
+ case MediaRouter.RouteInfo.STATUS_AVAILABLE:
+ updateConnectionTimeout(TIMEOUT_REASON_WAITING_FOR_CONNECTING);
+ break;
+ case MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE:
+ case MediaRouter.RouteInfo.STATUS_IN_USE:
+ default:
+ updateConnectionTimeout(TIMEOUT_REASON_NOT_AVAILABLE);
+ break;
+ }
+ }
+
+ private void updateConnectionTimeout(int reason) {
+ if (reason != mConnectionTimeoutReason) {
+ if (mConnectionTimeoutReason != 0) {
+ removeMessages(MSG_CONNECTION_TIMED_OUT);
+ }
+ mConnectionTimeoutReason = reason;
+ mConnectionTimeoutStartTime = SystemClock.uptimeMillis();
+ switch (reason) {
+ case TIMEOUT_REASON_NOT_AVAILABLE:
+ // Route became unavailable. Unselect it immediately.
+ sendEmptyMessage(MSG_CONNECTION_TIMED_OUT);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+ // Waiting for route to start connecting.
+ sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTING_TIMEOUT);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+ // Waiting for route to complete connection.
+ sendEmptyMessageDelayed(MSG_CONNECTION_TIMED_OUT, CONNECTED_TIMEOUT);
+ break;
+ }
+ }
+ }
+
+ private void connectionTimedOut() {
+ if (mConnectionTimeoutReason == 0 || mGloballySelectedRouteRecord == null) {
+ // Shouldn't get here. There must be a bug somewhere.
+ Log.wtf(TAG, "Handled connection timeout for no reason.");
+ return;
+ }
+
+ switch (mConnectionTimeoutReason) {
+ case TIMEOUT_REASON_NOT_AVAILABLE:
+ Slog.i(TAG, "Global route no longer available: "
+ + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTING:
+ Slog.i(TAG, "Global route timed out while waiting for "
+ + "connection attempt to begin after "
+ + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+ + " ms: " + mGloballySelectedRouteRecord);
+ break;
+ case TIMEOUT_REASON_WAITING_FOR_CONNECTED:
+ Slog.i(TAG, "Global route timed out while connecting after "
+ + (SystemClock.uptimeMillis() - mConnectionTimeoutStartTime)
+ + " ms: " + mGloballySelectedRouteRecord);
+ break;
+ }
+ mConnectionTimeoutReason = 0;
+
+ unselectGloballySelectedRoute();
+ }
+
+ private void scheduleUpdateClientState() {
+ if (!mClientStateUpdateScheduled) {
+ mClientStateUpdateScheduled = true;
+ sendEmptyMessage(MSG_UPDATE_CLIENT_STATE);
+ }
+ }
+
+ private void updateClientState() {
+ mClientStateUpdateScheduled = false;
+
+ // Build a new client state.
+ MediaRouterClientState state = new MediaRouterClientState();
+ state.globallySelectedRouteId = mGloballySelectedRouteRecord != null ?
+ mGloballySelectedRouteRecord.getUniqueId() : null;
+ final int providerCount = mProviderRecords.size();
+ for (int i = 0; i < providerCount; i++) {
+ mProviderRecords.get(i).appendClientState(state);
+ }
+
+ try {
+ synchronized (mService.mLock) {
+ // Update the UserRecord.
+ mUserRecord.mState = state;
+
+ // Collect all clients.
+ final int count = mUserRecord.mClientRecords.size();
+ for (int i = 0; i < count; i++) {
+ mTempClients.add(mUserRecord.mClientRecords.get(i).mClient);
+ }
+ }
+
+ // Notify all clients (outside of the lock).
+ final int count = mTempClients.size();
+ for (int i = 0; i < count; i++) {
+ try {
+ mTempClients.get(i).onStateChanged();
+ } catch (RemoteException ex) {
+ // ignore errors, client probably died
+ }
+ }
+ } finally {
+ // Clear the list in preparation for the next time.
+ mTempClients.clear();
+ }
+ }
+
+ private int findProviderRecord(RemoteDisplayProviderProxy provider) {
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ ProviderRecord record = mProviderRecords.get(i);
+ if (record.getProvider() == provider) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ private RouteRecord findRouteRecord(String uniqueId) {
+ final int count = mProviderRecords.size();
+ for (int i = 0; i < count; i++) {
+ RouteRecord record = mProviderRecords.get(i).findRouteByUniqueId(uniqueId);
+ if (record != null) {
+ return record;
+ }
+ }
+ return null;
+ }
+
+ static final class ProviderRecord {
+ private final RemoteDisplayProviderProxy mProvider;
+ private final String mUniquePrefix;
+ private final ArrayList<RouteRecord> mRoutes = new ArrayList<RouteRecord>();
+ private RemoteDisplayState mDescriptor;
+
+ public ProviderRecord(RemoteDisplayProviderProxy provider) {
+ mProvider = provider;
+ mUniquePrefix = provider.getFlattenedComponentName() + ":";
+ }
+
+ public RemoteDisplayProviderProxy getProvider() {
+ return mProvider;
+ }
+
+ public String getUniquePrefix() {
+ return mUniquePrefix;
+ }
+
+ public boolean updateDescriptor(RemoteDisplayState descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+
+ // Update all existing routes and reorder them to match
+ // the order of their descriptors.
+ int targetIndex = 0;
+ if (descriptor != null) {
+ if (descriptor.isValid()) {
+ final List<RemoteDisplayInfo> routeDescriptors = descriptor.displays;
+ final int routeCount = routeDescriptors.size();
+ for (int i = 0; i < routeCount; i++) {
+ final RemoteDisplayInfo routeDescriptor =
+ routeDescriptors.get(i);
+ final String descriptorId = routeDescriptor.id;
+ final int sourceIndex = findRouteByDescriptorId(descriptorId);
+ if (sourceIndex < 0) {
+ // Add the route to the provider.
+ String uniqueId = assignRouteUniqueId(descriptorId);
+ RouteRecord route =
+ new RouteRecord(this, descriptorId, uniqueId);
+ mRoutes.add(targetIndex++, route);
+ route.updateDescriptor(routeDescriptor);
+ changed = true;
+ } else if (sourceIndex < targetIndex) {
+ // Ignore route with duplicate id.
+ Slog.w(TAG, "Ignoring route descriptor with duplicate id: "
+ + routeDescriptor);
+ } else {
+ // Reorder existing route within the list.
+ RouteRecord route = mRoutes.get(sourceIndex);
+ Collections.swap(mRoutes, sourceIndex, targetIndex++);
+ changed |= route.updateDescriptor(routeDescriptor);
+ }
+ }
+ } else {
+ Slog.w(TAG, "Ignoring invalid descriptor from media route provider: "
+ + mProvider.getFlattenedComponentName());
+ }
+ }
+
+ // Dispose all remaining routes that do not have matching descriptors.
+ for (int i = mRoutes.size() - 1; i >= targetIndex; i--) {
+ RouteRecord route = mRoutes.remove(i);
+ route.updateDescriptor(null); // mark route invalid
+ changed = true;
+ }
+ }
+ return changed;
+ }
+
+ public void appendClientState(MediaRouterClientState state) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ state.routes.add(mRoutes.get(i).getInfo());
+ }
+ }
+
+ public RouteRecord findRouteByUniqueId(String uniqueId) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteRecord route = mRoutes.get(i);
+ if (route.getUniqueId().equals(uniqueId)) {
+ return route;
+ }
+ }
+ return null;
+ }
+
+ private int findRouteByDescriptorId(String descriptorId) {
+ final int routeCount = mRoutes.size();
+ for (int i = 0; i < routeCount; i++) {
+ RouteRecord route = mRoutes.get(i);
+ if (route.getDescriptorId().equals(descriptorId)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ mProvider.dump(pw, indent);
+
+ final int routeCount = mRoutes.size();
+ if (routeCount != 0) {
+ for (int i = 0; i < routeCount; i++) {
+ mRoutes.get(i).dump(pw, indent);
+ }
+ } else {
+ pw.println(indent + "<no routes>");
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Provider " + mProvider.getFlattenedComponentName();
+ }
+
+ private String assignRouteUniqueId(String descriptorId) {
+ return mUniquePrefix + descriptorId;
+ }
+ }
+
+ static final class RouteRecord {
+ private final ProviderRecord mProviderRecord;
+ private final String mDescriptorId;
+ private final MediaRouterClientState.RouteInfo mMutableInfo;
+ private MediaRouterClientState.RouteInfo mImmutableInfo;
+ private RemoteDisplayInfo mDescriptor;
+
+ public RouteRecord(ProviderRecord providerRecord,
+ String descriptorId, String uniqueId) {
+ mProviderRecord = providerRecord;
+ mDescriptorId = descriptorId;
+ mMutableInfo = new MediaRouterClientState.RouteInfo(uniqueId);
+ }
+
+ public RemoteDisplayProviderProxy getProvider() {
+ return mProviderRecord.getProvider();
+ }
+
+ public ProviderRecord getProviderRecord() {
+ return mProviderRecord;
+ }
+
+ public String getDescriptorId() {
+ return mDescriptorId;
+ }
+
+ public String getUniqueId() {
+ return mMutableInfo.id;
+ }
+
+ public MediaRouterClientState.RouteInfo getInfo() {
+ if (mImmutableInfo == null) {
+ mImmutableInfo = new MediaRouterClientState.RouteInfo(mMutableInfo);
+ }
+ return mImmutableInfo;
+ }
+
+ public boolean isValid() {
+ return mDescriptor != null;
+ }
+
+ public boolean isEnabled() {
+ return mMutableInfo.enabled;
+ }
+
+ public int getStatus() {
+ return mMutableInfo.statusCode;
+ }
+
+ public boolean updateDescriptor(RemoteDisplayInfo descriptor) {
+ boolean changed = false;
+ if (mDescriptor != descriptor) {
+ mDescriptor = descriptor;
+ if (descriptor != null) {
+ final String name = computeName(descriptor);
+ if (!Objects.equal(mMutableInfo.name, name)) {
+ mMutableInfo.name = name;
+ changed = true;
+ }
+ final String description = computeDescription(descriptor);
+ if (!Objects.equal(mMutableInfo.description, description)) {
+ mMutableInfo.description = description;
+ changed = true;
+ }
+ final int supportedTypes = computeSupportedTypes(descriptor);
+ if (mMutableInfo.supportedTypes != supportedTypes) {
+ mMutableInfo.supportedTypes = supportedTypes;
+ changed = true;
+ }
+ final boolean enabled = computeEnabled(descriptor);
+ if (mMutableInfo.enabled != enabled) {
+ mMutableInfo.enabled = enabled;
+ changed = true;
+ }
+ final int statusCode = computeStatusCode(descriptor);
+ if (mMutableInfo.statusCode != statusCode) {
+ mMutableInfo.statusCode = statusCode;
+ changed = true;
+ }
+ final int playbackType = computePlaybackType(descriptor);
+ if (mMutableInfo.playbackType != playbackType) {
+ mMutableInfo.playbackType = playbackType;
+ changed = true;
+ }
+ final int playbackStream = computePlaybackStream(descriptor);
+ if (mMutableInfo.playbackStream != playbackStream) {
+ mMutableInfo.playbackStream = playbackStream;
+ changed = true;
+ }
+ final int volume = computeVolume(descriptor);
+ if (mMutableInfo.volume != volume) {
+ mMutableInfo.volume = volume;
+ changed = true;
+ }
+ final int volumeMax = computeVolumeMax(descriptor);
+ if (mMutableInfo.volumeMax != volumeMax) {
+ mMutableInfo.volumeMax = volumeMax;
+ changed = true;
+ }
+ final int volumeHandling = computeVolumeHandling(descriptor);
+ if (mMutableInfo.volumeHandling != volumeHandling) {
+ mMutableInfo.volumeHandling = volumeHandling;
+ changed = true;
+ }
+ final int presentationDisplayId = computePresentationDisplayId(descriptor);
+ if (mMutableInfo.presentationDisplayId != presentationDisplayId) {
+ mMutableInfo.presentationDisplayId = presentationDisplayId;
+ changed = true;
+ }
+ }
+ }
+ if (changed) {
+ mImmutableInfo = null;
+ }
+ return changed;
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + this);
+
+ final String indent = prefix + " ";
+ pw.println(indent + "mMutableInfo=" + mMutableInfo);
+ pw.println(indent + "mDescriptorId=" + mDescriptorId);
+ pw.println(indent + "mDescriptor=" + mDescriptor);
+ }
+
+ @Override
+ public String toString() {
+ return "Route " + mMutableInfo.name + " (" + mMutableInfo.id + ")";
+ }
+
+ private static String computeName(RemoteDisplayInfo descriptor) {
+ // Note that isValid() already ensures the name is non-empty.
+ return descriptor.name;
+ }
+
+ private static String computeDescription(RemoteDisplayInfo descriptor) {
+ final String description = descriptor.description;
+ return TextUtils.isEmpty(description) ? null : description;
+ }
+
+ private static int computeSupportedTypes(RemoteDisplayInfo descriptor) {
+ return MediaRouter.ROUTE_TYPE_LIVE_AUDIO
+ | MediaRouter.ROUTE_TYPE_LIVE_VIDEO
+ | MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY;
+ }
+
+ private static boolean computeEnabled(RemoteDisplayInfo descriptor) {
+ switch (descriptor.status) {
+ case RemoteDisplayInfo.STATUS_CONNECTED:
+ case RemoteDisplayInfo.STATUS_CONNECTING:
+ case RemoteDisplayInfo.STATUS_AVAILABLE:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ private static int computeStatusCode(RemoteDisplayInfo descriptor) {
+ switch (descriptor.status) {
+ case RemoteDisplayInfo.STATUS_NOT_AVAILABLE:
+ return MediaRouter.RouteInfo.STATUS_NOT_AVAILABLE;
+ case RemoteDisplayInfo.STATUS_AVAILABLE:
+ return MediaRouter.RouteInfo.STATUS_AVAILABLE;
+ case RemoteDisplayInfo.STATUS_IN_USE:
+ return MediaRouter.RouteInfo.STATUS_IN_USE;
+ case RemoteDisplayInfo.STATUS_CONNECTING:
+ return MediaRouter.RouteInfo.STATUS_CONNECTING;
+ case RemoteDisplayInfo.STATUS_CONNECTED:
+ return MediaRouter.RouteInfo.STATUS_CONNECTED;
+ default:
+ return MediaRouter.RouteInfo.STATUS_NONE;
+ }
+ }
+
+ private static int computePlaybackType(RemoteDisplayInfo descriptor) {
+ return MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE;
+ }
+
+ private static int computePlaybackStream(RemoteDisplayInfo descriptor) {
+ return AudioSystem.STREAM_MUSIC;
+ }
+
+ private static int computeVolume(RemoteDisplayInfo descriptor) {
+ final int volume = descriptor.volume;
+ final int volumeMax = descriptor.volumeMax;
+ if (volume < 0) {
+ return 0;
+ } else if (volume > volumeMax) {
+ return volumeMax;
+ }
+ return volume;
+ }
+
+ private static int computeVolumeMax(RemoteDisplayInfo descriptor) {
+ final int volumeMax = descriptor.volumeMax;
+ return volumeMax > 0 ? volumeMax : 0;
+ }
+
+ private static int computeVolumeHandling(RemoteDisplayInfo descriptor) {
+ final int volumeHandling = descriptor.volumeHandling;
+ switch (volumeHandling) {
+ case RemoteDisplayInfo.PLAYBACK_VOLUME_VARIABLE:
+ return MediaRouter.RouteInfo.PLAYBACK_VOLUME_VARIABLE;
+ case RemoteDisplayInfo.PLAYBACK_VOLUME_FIXED:
+ default:
+ return MediaRouter.RouteInfo.PLAYBACK_VOLUME_FIXED;
+ }
+ }
+
+ private static int computePresentationDisplayId(RemoteDisplayInfo descriptor) {
+ // The MediaRouter class validates that the id corresponds to an extant
+ // presentation display. So all we do here is canonicalize the null case.
+ final int displayId = descriptor.presentationDisplayId;
+ return displayId < 0 ? -1 : displayId;
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderProxy.java b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
new file mode 100644
index 0000000..b248ee0
--- /dev/null
+++ b/services/java/com/android/server/media/RemoteDisplayProviderProxy.java
@@ -0,0 +1,443 @@
+/*
+ * 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 com.android.internal.util.Objects;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.media.IRemoteDisplayCallback;
+import android.media.IRemoteDisplayProvider;
+import android.media.RemoteDisplayState;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.IBinder.DeathRecipient;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+
+/**
+ * Maintains a connection to a particular remote display provider service.
+ */
+final class RemoteDisplayProviderProxy implements ServiceConnection {
+ 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 ComponentName mComponentName;
+ private final int mUserId;
+ private final Handler mHandler;
+
+ private Callback mDisplayStateCallback;
+
+ // Connection state
+ private boolean mRunning;
+ private boolean mBound;
+ private Connection mActiveConnection;
+ private boolean mConnectionReady;
+
+ // Logical state
+ private int mDiscoveryMode;
+ private String mSelectedDisplayId;
+ private RemoteDisplayState mDisplayState;
+ private boolean mScheduledDisplayStateChangedCallback;
+
+ public RemoteDisplayProviderProxy(Context context, ComponentName componentName,
+ int userId) {
+ mContext = context;
+ mComponentName = componentName;
+ mUserId = userId;
+ mHandler = new Handler();
+ }
+
+ public void dump(PrintWriter pw, String prefix) {
+ pw.println(prefix + "Proxy");
+ pw.println(prefix + " mUserId=" + mUserId);
+ pw.println(prefix + " mRunning=" + mRunning);
+ pw.println(prefix + " mBound=" + mBound);
+ pw.println(prefix + " mActiveConnection=" + mActiveConnection);
+ pw.println(prefix + " mConnectionReady=" + mConnectionReady);
+ pw.println(prefix + " mDiscoveryMode=" + mDiscoveryMode);
+ pw.println(prefix + " mSelectedDisplayId=" + mSelectedDisplayId);
+ pw.println(prefix + " mDisplayState=" + mDisplayState);
+ }
+
+ public void setCallback(Callback callback) {
+ mDisplayStateCallback = callback;
+ }
+
+ public RemoteDisplayState getDisplayState() {
+ return mDisplayState;
+ }
+
+ public void setDiscoveryMode(int mode) {
+ if (mDiscoveryMode != mode) {
+ mDiscoveryMode = mode;
+ if (mConnectionReady) {
+ mActiveConnection.setDiscoveryMode(mode);
+ }
+ updateBinding();
+ }
+ }
+
+ public void setSelectedDisplay(String id) {
+ if (!Objects.equal(mSelectedDisplayId, id)) {
+ if (mConnectionReady && mSelectedDisplayId != null) {
+ mActiveConnection.disconnect(mSelectedDisplayId);
+ }
+ mSelectedDisplayId = id;
+ if (mConnectionReady && id != null) {
+ mActiveConnection.connect(id);
+ }
+ updateBinding();
+ }
+ }
+
+ public void setDisplayVolume(int volume) {
+ if (mConnectionReady && mSelectedDisplayId != null) {
+ mActiveConnection.setVolume(mSelectedDisplayId, volume);
+ }
+ }
+
+ public void adjustDisplayVolume(int delta) {
+ if (mConnectionReady && mSelectedDisplayId != null) {
+ mActiveConnection.adjustVolume(mSelectedDisplayId, delta);
+ }
+ }
+
+ public boolean hasComponentName(String packageName, String className) {
+ return mComponentName.getPackageName().equals(packageName)
+ && mComponentName.getClassName().equals(className);
+ }
+
+ public String getFlattenedComponentName() {
+ return mComponentName.flattenToShortString();
+ }
+
+ public void start() {
+ if (!mRunning) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Starting");
+ }
+
+ mRunning = true;
+ updateBinding();
+ }
+ }
+
+ public void stop() {
+ if (mRunning) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Stopping");
+ }
+
+ mRunning = false;
+ updateBinding();
+ }
+ }
+
+ public void rebindIfDisconnected() {
+ if (mActiveConnection == null && shouldBind()) {
+ unbind();
+ bind();
+ }
+ }
+
+ private void updateBinding() {
+ if (shouldBind()) {
+ bind();
+ } else {
+ unbind();
+ }
+ }
+
+ private boolean shouldBind() {
+ if (mRunning) {
+ // Bind whenever there is a discovery request or selected display.
+ if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE
+ || mSelectedDisplayId != null) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void bind() {
+ if (!mBound) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Binding");
+ }
+
+ Intent service = new Intent(RemoteDisplayState.SERVICE_INTERFACE);
+ service.setComponent(mComponentName);
+ try {
+ mBound = mContext.bindServiceAsUser(service, this, Context.BIND_AUTO_CREATE,
+ new UserHandle(mUserId));
+ if (!mBound && DEBUG) {
+ Slog.d(TAG, this + ": Bind failed");
+ }
+ } catch (SecurityException ex) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Bind failed", ex);
+ }
+ }
+ }
+ }
+
+ private void unbind() {
+ if (mBound) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Unbinding");
+ }
+
+ mBound = false;
+ disconnect();
+ mContext.unbindService(this);
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Connected");
+ }
+
+ if (mBound) {
+ disconnect();
+
+ IRemoteDisplayProvider provider = IRemoteDisplayProvider.Stub.asInterface(service);
+ if (provider != null) {
+ Connection connection = new Connection(provider);
+ if (connection.register()) {
+ mActiveConnection = connection;
+ } else {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Registration failed");
+ }
+ }
+ } else {
+ Slog.e(TAG, this + ": Service returned invalid remote display provider binder");
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Service disconnected");
+ }
+ disconnect();
+ }
+
+ private void onConnectionReady(Connection connection) {
+ if (mActiveConnection == connection) {
+ mConnectionReady = true;
+
+ if (mDiscoveryMode != RemoteDisplayState.DISCOVERY_MODE_NONE) {
+ mActiveConnection.setDiscoveryMode(mDiscoveryMode);
+ }
+ if (mSelectedDisplayId != null) {
+ mActiveConnection.connect(mSelectedDisplayId);
+ }
+ }
+ }
+
+ private void onConnectionDied(Connection connection) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": Service connection died");
+ }
+ disconnect();
+ }
+ }
+
+ private void onDisplayStateChanged(Connection connection, RemoteDisplayState state) {
+ if (mActiveConnection == connection) {
+ if (DEBUG) {
+ Slog.d(TAG, this + ": State changed, state=" + state);
+ }
+ setDisplayState(state);
+ }
+ }
+
+ private void disconnect() {
+ if (mActiveConnection != null) {
+ if (mSelectedDisplayId != null) {
+ mActiveConnection.disconnect(mSelectedDisplayId);
+ }
+ mConnectionReady = false;
+ mActiveConnection.dispose();
+ mActiveConnection = null;
+ setDisplayState(null);
+ }
+ }
+
+ private void setDisplayState(RemoteDisplayState state) {
+ if (!Objects.equal(mDisplayState, state)) {
+ mDisplayState = state;
+ if (!mScheduledDisplayStateChangedCallback) {
+ mScheduledDisplayStateChangedCallback = true;
+ mHandler.post(mDisplayStateChanged);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Service connection " + mComponentName.flattenToShortString();
+ }
+
+ private final Runnable mDisplayStateChanged = new Runnable() {
+ @Override
+ public void run() {
+ mScheduledDisplayStateChangedCallback = false;
+ if (mDisplayStateCallback != null) {
+ mDisplayStateCallback.onDisplayStateChanged(
+ RemoteDisplayProviderProxy.this, mDisplayState);
+ }
+ }
+ };
+
+ public interface Callback {
+ void onDisplayStateChanged(RemoteDisplayProviderProxy provider, RemoteDisplayState state);
+ }
+
+ private final class Connection implements DeathRecipient {
+ private final IRemoteDisplayProvider mProvider;
+ private final ProviderCallback mCallback;
+
+ public Connection(IRemoteDisplayProvider provider) {
+ mProvider = provider;
+ mCallback = new ProviderCallback(this);
+ }
+
+ public boolean register() {
+ try {
+ mProvider.asBinder().linkToDeath(this, 0);
+ mProvider.setCallback(mCallback);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onConnectionReady(Connection.this);
+ }
+ });
+ return true;
+ } catch (RemoteException ex) {
+ binderDied();
+ }
+ return false;
+ }
+
+ public void dispose() {
+ mProvider.asBinder().unlinkToDeath(this, 0);
+ mCallback.dispose();
+ }
+
+ public void setDiscoveryMode(int mode) {
+ try {
+ mProvider.setDiscoveryMode(mode);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to set discovery mode.", ex);
+ }
+ }
+
+ public void connect(String id) {
+ try {
+ mProvider.connect(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to connect to display.", ex);
+ }
+ }
+
+ public void disconnect(String id) {
+ try {
+ mProvider.disconnect(id);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to disconnect from display.", ex);
+ }
+ }
+
+ public void setVolume(String id, int volume) {
+ try {
+ mProvider.setVolume(id, volume);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to set display volume.", ex);
+ }
+ }
+
+ public void adjustVolume(String id, int volume) {
+ try {
+ mProvider.adjustVolume(id, volume);
+ } catch (RemoteException ex) {
+ Slog.e(TAG, "Failed to deliver request to adjust display volume.", ex);
+ }
+ }
+
+ @Override
+ public void binderDied() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onConnectionDied(Connection.this);
+ }
+ });
+ }
+
+ void postStateChanged(final RemoteDisplayState state) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ onDisplayStateChanged(Connection.this, state);
+ }
+ });
+ }
+ }
+
+ /**
+ * Receives callbacks from the service.
+ * <p>
+ * This inner class is static and only retains a weak reference to the connection
+ * to prevent the client from being leaked in case the service is holding an
+ * active reference to the client's callback.
+ * </p>
+ */
+ private static final class ProviderCallback extends IRemoteDisplayCallback.Stub {
+ private final WeakReference<Connection> mConnectionRef;
+
+ public ProviderCallback(Connection connection) {
+ mConnectionRef = new WeakReference<Connection>(connection);
+ }
+
+ public void dispose() {
+ mConnectionRef.clear();
+ }
+
+ @Override
+ public void onStateChanged(RemoteDisplayState state) throws RemoteException {
+ Connection connection = mConnectionRef.get();
+ if (connection != null) {
+ connection.postStateChanged(state);
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java
new file mode 100644
index 0000000..f3a3c2f
--- /dev/null
+++ b/services/java/com/android/server/media/RemoteDisplayProviderWatcher.java
@@ -0,0 +1,181 @@
+/*
+ * 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.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) {
+ 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 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);
+ }
+}
diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java
index d471b57..f2efde1 100644
--- a/services/java/com/android/server/wifi/WifiService.java
+++ b/services/java/com/android/server/wifi/WifiService.java
@@ -369,15 +369,17 @@ public final class WifiService extends IWifiManager.Stub {
}
private class BatchedScanRequest extends DeathRecipient {
- BatchedScanSettings settings;
- int uid;
- int pid;
+ final BatchedScanSettings settings;
+ final int uid;
+ final int pid;
+ final WorkSource workSource;
- BatchedScanRequest(BatchedScanSettings settings, IBinder binder) {
+ BatchedScanRequest(BatchedScanSettings settings, IBinder binder, WorkSource ws) {
super(0, null, binder, null);
this.settings = settings;
this.uid = getCallingUid();
this.pid = getCallingPid();
+ workSource = ws;
}
public void binderDied() {
stopBatchedScan(settings, uid, pid);
@@ -406,12 +408,19 @@ public final class WifiService extends IWifiManager.Stub {
/**
* see {@link android.net.wifi.WifiManager#requestBatchedScan()}
*/
- public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) {
+ public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder,
+ WorkSource workSource) {
enforceChangePermission();
+ if (workSource != null) {
+ enforceWorkSourcePermission();
+ // WifiManager currently doesn't use names, so need to clear names out of the
+ // supplied WorkSource to allow future WorkSource combining.
+ workSource.clearNames();
+ }
if (mBatchedScanSupported == false) return false;
requested = new BatchedScanSettings(requested);
if (requested.isInvalid()) return false;
- BatchedScanRequest r = new BatchedScanRequest(requested, binder);
+ BatchedScanRequest r = new BatchedScanRequest(requested, binder, workSource);
synchronized(mBatchedScanners) {
mBatchedScanners.add(r);
resolveBatchedScannersLocked();
@@ -468,18 +477,48 @@ public final class WifiService extends IWifiManager.Stub {
private void resolveBatchedScannersLocked() {
BatchedScanSettings setting = new BatchedScanSettings();
+ WorkSource responsibleWorkSource = null;
int responsibleUid = 0;
+ double responsibleCsph = 0; // Channel Scans Per Hour
if (mBatchedScanners.size() == 0) {
- mWifiStateMachine.setBatchedScanSettings(null, 0);
+ mWifiStateMachine.setBatchedScanSettings(null, 0, 0, null);
return;
}
for (BatchedScanRequest r : mBatchedScanners) {
BatchedScanSettings s = r.settings;
+
+ // evaluate responsibility
+ int currentChannelCount;
+ int currentScanInterval;
+ double currentCsph;
+
+ if (s.channelSet == null || s.channelSet.isEmpty()) {
+ // all channels - 11 B and 9 A channels roughly.
+ currentChannelCount = 9 + 11;
+ } else {
+ currentChannelCount = s.channelSet.size();
+ // these are rough est - no real need to correct for reg-domain;
+ if (s.channelSet.contains("A")) currentChannelCount += (9 - 1);
+ if (s.channelSet.contains("B")) currentChannelCount += (11 - 1);
+
+ }
+ if (s.scanIntervalSec == BatchedScanSettings.UNSPECIFIED) {
+ currentScanInterval = BatchedScanSettings.DEFAULT_INTERVAL_SEC;
+ } else {
+ currentScanInterval = s.scanIntervalSec;
+ }
+ currentCsph = 60 * 60 * currentChannelCount / currentScanInterval;
+
+ if (currentCsph > responsibleCsph) {
+ responsibleUid = r.uid;
+ responsibleWorkSource = r.workSource;
+ responsibleCsph = currentCsph;
+ }
+
if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED &&
s.maxScansPerBatch < setting.maxScansPerBatch) {
setting.maxScansPerBatch = s.maxScansPerBatch;
- responsibleUid = r.uid;
}
if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED &&
(setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED ||
@@ -489,7 +528,6 @@ public final class WifiService extends IWifiManager.Stub {
if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED &&
s.scanIntervalSec < setting.scanIntervalSec) {
setting.scanIntervalSec = s.scanIntervalSec;
- responsibleUid = r.uid;
}
if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED &&
(setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED ||
@@ -511,7 +549,8 @@ public final class WifiService extends IWifiManager.Stub {
}
setting.constrain();
- mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid);
+ mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid, (int)responsibleCsph,
+ responsibleWorkSource);
}
private void enforceAccessPermission() {
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index e1e9f5c..55b0b3a 100644
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -578,10 +578,13 @@ public class WindowManagerService extends IWindowManager.Stub
private boolean mUpdateRotation = false;
boolean mWallpaperActionPending = false;
- private static final int DISPLAY_CONTENT_UNKNOWN = 0;
- private static final int DISPLAY_CONTENT_MIRROR = 1;
- private static final int DISPLAY_CONTENT_UNIQUE = 2;
- private int mDisplayHasContent = DISPLAY_CONTENT_UNKNOWN;
+ // Set to true when the display contains content to show the user.
+ // When false, the display manager may choose to mirror or blank the display.
+ boolean mDisplayHasContent = false;
+
+ // Only set while traversing the default display based on its content.
+ // Affects the behavior of mirroring on secondary displays.
+ boolean mObscureApplicationContentOnSecondaryDisplays = false;
}
final LayoutFields mInnerFields = new LayoutFields();
@@ -8784,6 +8787,14 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowManager.LayoutParams attrs = w.mAttrs;
final int attrFlags = attrs.flags;
final boolean canBeSeen = w.isDisplayedLw();
+ final boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
+
+ if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
+ // This window completely covers everything behind it,
+ // so we want to leave all of them as undimmed (for
+ // performance reasons).
+ mInnerFields.mObscured = true;
+ }
if (w.mHasSurface) {
if ((attrFlags&FLAG_KEEP_SCREEN_ON) != 0) {
@@ -8812,22 +8823,24 @@ public class WindowManagerService extends IWindowManager.Stub
}
if (canBeSeen) {
- if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_MIRROR;
- } else if (mInnerFields.mDisplayHasContent
- == LayoutFields.DISPLAY_CONTENT_UNKNOWN) {
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNIQUE;
+ // This function assumes that the contents of the default display are
+ // processed first before secondary displays.
+ if (w.mDisplayContent.isDefaultDisplay) {
+ // While a dream or keyguard is showing, obscure ordinary application
+ // content on secondary displays (by forcibly enabling mirroring unless
+ // there is other content we want to show) but still allow opaque
+ // keyguard dialogs to be shown.
+ if (type == TYPE_DREAM || type == TYPE_KEYGUARD) {
+ mInnerFields.mObscureApplicationContentOnSecondaryDisplays = true;
+ }
+ mInnerFields.mDisplayHasContent = true;
+ } else if (!mInnerFields.mObscureApplicationContentOnSecondaryDisplays
+ || (mInnerFields.mObscured && type == TYPE_KEYGUARD_DIALOG)) {
+ // Allow full screen keyguard presentation dialogs to be seen.
+ mInnerFields.mDisplayHasContent = true;
}
}
}
-
- boolean opaqueDrawn = canBeSeen && w.isOpaqueDrawn();
- if (opaqueDrawn && w.isFullscreen(innerDw, innerDh)) {
- // This window completely covers everything behind it,
- // so we want to leave all of them as undimmed (for
- // performance reasons).
- mInnerFields.mObscured = true;
- }
}
private void handleFlagDimBehind(WindowState w, int innerDw, int innerDh) {
@@ -8905,7 +8918,7 @@ public class WindowManagerService extends IWindowManager.Stub
mInnerFields.mScreenBrightness = -1;
mInnerFields.mButtonBrightness = -1;
mInnerFields.mUserActivityTimeout = -1;
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
+ mInnerFields.mObscureApplicationContentOnSecondaryDisplays = false;
mTransactionSequence++;
@@ -8940,10 +8953,8 @@ public class WindowManagerService extends IWindowManager.Stub
final int innerDh = displayInfo.appHeight;
final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY);
- // Reset for each display unless we are forcing mirroring.
- if (mInnerFields.mDisplayHasContent != LayoutFields.DISPLAY_CONTENT_MIRROR) {
- mInnerFields.mDisplayHasContent = LayoutFields.DISPLAY_CONTENT_UNKNOWN;
- }
+ // Reset for each display.
+ mInnerFields.mDisplayHasContent = false;
int repeats = 0;
do {
@@ -9152,20 +9163,8 @@ public class WindowManagerService extends IWindowManager.Stub
updateResizingWindows(w);
}
- final boolean hasUniqueContent;
- switch (mInnerFields.mDisplayHasContent) {
- case LayoutFields.DISPLAY_CONTENT_MIRROR:
- hasUniqueContent = isDefaultDisplay;
- break;
- case LayoutFields.DISPLAY_CONTENT_UNIQUE:
- hasUniqueContent = true;
- break;
- case LayoutFields.DISPLAY_CONTENT_UNKNOWN:
- default:
- hasUniqueContent = false;
- break;
- }
- mDisplayManagerService.setDisplayHasContent(displayId, hasUniqueContent,
+ mDisplayManagerService.setDisplayHasContent(displayId,
+ mInnerFields.mDisplayHasContent,
true /* inTraversal, must call performTraversalInTrans... below */);
getDisplayContentLocked(displayId).stopDimmingIfNeeded();