Merge "Add support for Wifi display." into jb-mr1-dev
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index db05e10..8f4626f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -259,7 +259,7 @@
private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
private static native IBinder nativeCreateDisplay(String name);
private static native void nativeSetDisplaySurface(
- IBinder displayToken, SurfaceTexture surfaceTexture);
+ IBinder displayToken, Surface surface);
private static native void nativeSetDisplayLayerStack(
IBinder displayToken, int layerStack);
private static native void nativeSetDisplayProjection(
@@ -597,11 +597,11 @@
}
/** @hide */
- public static void setDisplaySurface(IBinder displayToken, SurfaceTexture surfaceTexture) {
+ public static void setDisplaySurface(IBinder displayToken, Surface surface) {
if (displayToken == null) {
throw new IllegalArgumentException("displayToken must not be null");
}
- nativeSetDisplaySurface(displayToken, surfaceTexture);
+ nativeSetDisplaySurface(displayToken, surface);
}
/** @hide */
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
new file mode 100644
index 0000000..7b8c582
--- /dev/null
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 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.internal.util;
+
+import android.os.Handler;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Helper functions for dumping the state of system services.
+ */
+public final class DumpUtils {
+ private DumpUtils() {
+ }
+
+ /**
+ * Helper for dumping state owned by a handler thread.
+ *
+ * Because the caller might be holding an important lock that the handler is
+ * trying to acquire, we use a short timeout to avoid deadlocks. The process
+ * is inelegant but this function is only used for debugging purposes.
+ */
+ public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw, long timeout) {
+ final StringWriter sw = new StringWriter();
+ if (handler.runWithScissors(new Runnable() {
+ @Override
+ public void run() {
+ PrintWriter lpw = new PrintWriter(sw);
+ dump.dump(lpw);
+ lpw.close();
+ }
+ }, timeout)) {
+ pw.print(sw.toString());
+ } else {
+ pw.println("... timed out");
+ }
+ }
+
+ public interface Dump {
+ void dump(PrintWriter pw);
+ }
+}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 699e9b3..dd5918b 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -28,7 +28,7 @@
private final String mIndent;
private StringBuilder mBuilder = new StringBuilder();
- private String mCurrent = new String();
+ private char[] mCurrent;
private boolean mEmptyLine = true;
public IndentingPrintWriter(Writer writer, String indent) {
@@ -38,12 +38,12 @@
public void increaseIndent() {
mBuilder.append(mIndent);
- mCurrent = mBuilder.toString();
+ mCurrent = null;
}
public void decreaseIndent() {
mBuilder.delete(0, mIndent.length());
- mCurrent = mBuilder.toString();
+ mCurrent = null;
}
public void printPair(String key, Object value) {
@@ -51,17 +51,35 @@
}
@Override
- public void println() {
- super.println();
- mEmptyLine = true;
+ public void write(char[] buf, int offset, int count) {
+ final int bufferEnd = offset + count;
+ int lineStart = offset;
+ int lineEnd = offset;
+ while (lineEnd < bufferEnd) {
+ char ch = buf[lineEnd++];
+ if (ch == '\n') {
+ writeIndent();
+ super.write(buf, lineStart, lineEnd - lineStart);
+ lineStart = lineEnd;
+ mEmptyLine = true;
+ }
+ }
+
+ if (lineStart != lineEnd) {
+ writeIndent();
+ super.write(buf, lineStart, lineEnd - lineStart);
+ }
}
- @Override
- public void write(char[] buf, int offset, int count) {
+ private void writeIndent() {
if (mEmptyLine) {
mEmptyLine = false;
- super.print(mCurrent);
+ if (mBuilder.length() != 0) {
+ if (mCurrent == null) {
+ mCurrent = mBuilder.toString().toCharArray();
+ }
+ super.write(mCurrent, 0, mCurrent.length);
+ }
}
- super.write(buf, offset, count);
}
}
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 9d45479..5f6042d 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -126,6 +126,7 @@
android_media_AudioSystem.cpp \
android_media_AudioTrack.cpp \
android_media_JetPlayer.cpp \
+ android_media_RemoteDisplay.cpp \
android_media_ToneGenerator.cpp \
android_hardware_Camera.cpp \
android_hardware_SensorManager.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 55563a8..27684d7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -160,6 +160,7 @@
extern int register_android_app_backup_FullBackup(JNIEnv *env);
extern int register_android_app_ActivityThread(JNIEnv *env);
extern int register_android_app_NativeActivity(JNIEnv *env);
+extern int register_android_media_RemoteDisplay(JNIEnv *env);
extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputDevice(JNIEnv* env);
extern int register_android_view_InputEventReceiver(JNIEnv* env);
@@ -1161,6 +1162,7 @@
REG_JNI(register_android_media_AudioSystem),
REG_JNI(register_android_media_AudioTrack),
REG_JNI(register_android_media_JetPlayer),
+ REG_JNI(register_android_media_RemoteDisplay),
REG_JNI(register_android_media_ToneGenerator),
REG_JNI(register_android_opengl_classes),
diff --git a/core/jni/android_media_RemoteDisplay.cpp b/core/jni/android_media_RemoteDisplay.cpp
new file mode 100644
index 0000000..5d24f61
--- /dev/null
+++ b/core/jni/android_media_RemoteDisplay.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+#define LOG_TAG "RemoteDisplay"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_view_Surface.h>
+
+#include <binder/IServiceManager.h>
+
+#include <gui/ISurfaceTexture.h>
+
+#include <media/IMediaPlayerService.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
+
+#include <utils/Log.h>
+
+#include <ScopedUtfChars.h>
+
+namespace android {
+
+static struct {
+ jmethodID notifyDisplayConnected;
+ jmethodID notifyDisplayDisconnected;
+ jmethodID notifyDisplayError;
+} gRemoteDisplayClassInfo;
+
+// ----------------------------------------------------------------------------
+
+class NativeRemoteDisplayClient : public BnRemoteDisplayClient {
+public:
+ NativeRemoteDisplayClient(JNIEnv* env, jobject remoteDisplayObj) :
+ mRemoteDisplayObjGlobal(env->NewGlobalRef(remoteDisplayObj)) {
+ }
+
+protected:
+ ~NativeRemoteDisplayClient() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+ env->DeleteGlobalRef(mRemoteDisplayObjGlobal);
+ }
+
+public:
+ virtual void onDisplayConnected(const sp<ISurfaceTexture>& surfaceTexture,
+ uint32_t width, uint32_t height, uint32_t flags) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ jobject surfaceObj = android_view_Surface_createFromISurfaceTexture(env, surfaceTexture);
+ if (surfaceObj == NULL) {
+ ALOGE("Could not create Surface from surface texture %p provided by media server.",
+ surfaceTexture.get());
+ return;
+ }
+
+ env->CallVoidMethod(mRemoteDisplayObjGlobal,
+ gRemoteDisplayClassInfo.notifyDisplayConnected,
+ surfaceObj, width, height, flags);
+ env->DeleteLocalRef(surfaceObj);
+ checkAndClearExceptionFromCallback(env, "notifyDisplayConnected");
+ }
+
+ virtual void onDisplayDisconnected() {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ env->CallVoidMethod(mRemoteDisplayObjGlobal,
+ gRemoteDisplayClassInfo.notifyDisplayDisconnected);
+ checkAndClearExceptionFromCallback(env, "notifyDisplayDisconnected");
+ }
+
+ virtual void onDisplayError(int32_t error) {
+ JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+ env->CallVoidMethod(mRemoteDisplayObjGlobal,
+ gRemoteDisplayClassInfo.notifyDisplayError, error);
+ checkAndClearExceptionFromCallback(env, "notifyDisplayError");
+ }
+
+private:
+ jobject mRemoteDisplayObjGlobal;
+
+ static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+ if (env->ExceptionCheck()) {
+ ALOGE("An exception was thrown by callback '%s'.", methodName);
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ }
+};
+
+class NativeRemoteDisplay {
+public:
+ NativeRemoteDisplay(const sp<IRemoteDisplay>& display,
+ const sp<NativeRemoteDisplayClient>& client) :
+ mDisplay(display), mClient(client) {
+ }
+
+ ~NativeRemoteDisplay() {
+ mDisplay->dispose();
+ }
+
+private:
+ sp<IRemoteDisplay> mDisplay;
+ sp<NativeRemoteDisplayClient> mClient;
+};
+
+
+// ----------------------------------------------------------------------------
+
+static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
+ ScopedUtfChars iface(env, ifaceStr);
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
+ sm->getService(String16("media.player")));
+ if (service == NULL) {
+ ALOGE("Could not obtain IMediaPlayerService from service manager");
+ return 0;
+ }
+
+ sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));
+ sp<IRemoteDisplay> display = service->listenForRemoteDisplay(
+ client, String8(iface.c_str()));
+ if (display == NULL) {
+ ALOGE("Media player service rejected request to listen for remote display '%s'.",
+ iface.c_str());
+ return 0;
+ }
+
+ NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);
+ return reinterpret_cast<jint>(wrapper);
+}
+
+static void nativeDispose(JNIEnv* env, jobject remoteDisplayObj, jint ptr) {
+ NativeRemoteDisplay* wrapper = reinterpret_cast<NativeRemoteDisplay*>(ptr);
+ delete wrapper;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+ {"nativeListen", "(Ljava/lang/String;)I",
+ (void*)nativeListen },
+ {"nativeDispose", "(I)V",
+ (void*)nativeDispose },
+};
+
+int register_android_media_RemoteDisplay(JNIEnv* env)
+{
+ int err = AndroidRuntime::registerNativeMethods(env, "android/media/RemoteDisplay",
+ gMethods, NELEM(gMethods));
+
+ jclass clazz = env->FindClass("android/media/RemoteDisplay");
+ gRemoteDisplayClassInfo.notifyDisplayConnected =
+ env->GetMethodID(clazz, "notifyDisplayConnected",
+ "(Landroid/view/Surface;III)V");
+ gRemoteDisplayClassInfo.notifyDisplayDisconnected =
+ env->GetMethodID(clazz, "notifyDisplayDisconnected", "()V");
+ gRemoteDisplayClassInfo.notifyDisplayError =
+ env->GetMethodID(clazz, "notifyDisplayError", "(I)V");
+ return err;
+}
+
+};
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 4fbfab6..90e85e6 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -66,6 +66,7 @@
jfieldID mGenerationId;
jfieldID mCanvas;
jfieldID mCanvasSaveCount;
+ jmethodID ctor;
} gSurfaceClassInfo;
static struct {
@@ -231,6 +232,42 @@
}
}
+static sp<ISurfaceTexture> getISurfaceTexture(JNIEnv* env, jobject surfaceObj) {
+ if (surfaceObj) {
+ sp<Surface> surface(getSurface(env, surfaceObj));
+ if (surface != NULL) {
+ return surface->getSurfaceTexture();
+ }
+ }
+ return NULL;
+}
+
+jobject android_view_Surface_createFromISurfaceTexture(JNIEnv* env,
+ const sp<ISurfaceTexture>& surfaceTexture) {
+ if (surfaceTexture == NULL) {
+ return NULL;
+ }
+
+ sp<Surface> surface(new Surface(surfaceTexture));
+ if (surface == NULL) {
+ return NULL;
+ }
+
+ jobject surfaceObj = env->NewObject(gSurfaceClassInfo.clazz, gSurfaceClassInfo.ctor);
+ if (surfaceObj == NULL) {
+ if (env->ExceptionCheck()) {
+ ALOGE("Could not create instance of Surface from ISurfaceTexture.");
+ LOGE_EX(env);
+ env->ExceptionClear();
+ }
+ return NULL;
+ }
+
+ setSurface(env, surfaceObj, surface);
+ return surfaceObj;
+}
+
+
// ----------------------------------------------------------------------------
static void nativeCreate(JNIEnv* env, jobject surfaceObj, jobject sessionObj,
@@ -619,24 +656,12 @@
}
static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz,
- jobject tokenObj, jobject surfaceTextureObj) {
+ jobject tokenObj, jobject surfaceObj) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
if (token == NULL) return;
- if (!surfaceTextureObj) {
- SurfaceComposerClient::setDisplaySurface(token, NULL);
- return;
- }
-
- sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, surfaceTextureObj));
- if (st == NULL) {
- jniThrowException(env, "java/lang/IllegalArgumentException",
- "SurfaceTexture has already been released");
- return;
- }
-
- sp<ISurfaceTexture> bq = st->getBufferQueue();
- SurfaceComposerClient::setDisplaySurface(token, bq);
+ sp<ISurfaceTexture> surfaceTexture(getISurfaceTexture(env, surfaceObj));
+ SurfaceComposerClient::setDisplaySurface(token, surfaceTexture);
}
static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz,
@@ -648,23 +673,23 @@
}
static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz,
- jobject tokenObj, jint orientation, jobject rect1Obj, jobject rect2Obj) {
+ jobject tokenObj, jint orientation, jobject layerStackRectObj, jobject displayRectObj) {
sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
if (token == NULL) return;
- Rect rect1;
- rect1.left = env->GetIntField(rect1Obj, gRectClassInfo.left);
- rect1.top = env->GetIntField(rect1Obj, gRectClassInfo.top);
- rect1.right = env->GetIntField(rect1Obj, gRectClassInfo.right);
- rect1.bottom = env->GetIntField(rect1Obj, gRectClassInfo.bottom);
+ Rect layerStackRect;
+ layerStackRect.left = env->GetIntField(layerStackRectObj, gRectClassInfo.left);
+ layerStackRect.top = env->GetIntField(layerStackRectObj, gRectClassInfo.top);
+ layerStackRect.right = env->GetIntField(layerStackRectObj, gRectClassInfo.right);
+ layerStackRect.bottom = env->GetIntField(layerStackRectObj, gRectClassInfo.bottom);
- Rect rect2;
- rect2.left = env->GetIntField(rect2Obj, gRectClassInfo.left);
- rect2.top = env->GetIntField(rect2Obj, gRectClassInfo.top);
- rect2.right = env->GetIntField(rect2Obj, gRectClassInfo.right);
- rect2.bottom = env->GetIntField(rect2Obj, gRectClassInfo.bottom);
+ Rect displayRect;
+ displayRect.left = env->GetIntField(displayRectObj, gRectClassInfo.left);
+ displayRect.top = env->GetIntField(displayRectObj, gRectClassInfo.top);
+ displayRect.right = env->GetIntField(displayRectObj, gRectClassInfo.right);
+ displayRect.bottom = env->GetIntField(displayRectObj, gRectClassInfo.bottom);
- SurfaceComposerClient::setDisplayProjection(token, orientation, rect1, rect2);
+ SurfaceComposerClient::setDisplayProjection(token, orientation, layerStackRect, displayRect);
}
static jboolean nativeGetDisplayInfo(JNIEnv* env, jclass clazz,
@@ -800,7 +825,7 @@
(void*)nativeGetBuiltInDisplay },
{"nativeCreateDisplay", "(Ljava/lang/String;)Landroid/os/IBinder;",
(void*)nativeCreateDisplay },
- {"nativeSetDisplaySurface", "(Landroid/os/IBinder;Landroid/graphics/SurfaceTexture;)V",
+ {"nativeSetDisplaySurface", "(Landroid/os/IBinder;Landroid/view/Surface;)V",
(void*)nativeSetDisplaySurface },
{"nativeSetDisplayLayerStack", "(Landroid/os/IBinder;I)V",
(void*)nativeSetDisplayLayerStack },
@@ -835,6 +860,7 @@
env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;");
gSurfaceClassInfo.mCanvasSaveCount =
env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvasSaveCount", "I");
+ gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>", "()V");
clazz = env->FindClass("android/graphics/Canvas");
gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas", "I");
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4db8cd1..3e27abc 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -486,6 +486,7 @@
<java-symbol type="string" name="display_manager_hdmi_display_name" />
<java-symbol type="string" name="display_manager_overlay_display_name" />
<java-symbol type="string" name="display_manager_overlay_display_title" />
+ <java-symbol type="string" name="display_manager_wifi_display_name" />
<java-symbol type="string" name="double_tap_toast" />
<java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" />
<java-symbol type="string" name="elapsed_time_short_format_mm_ss" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9f254b6..1e7e9fb 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3678,6 +3678,9 @@
<!-- Title text to show within the overlay. [CHAR LIMIT=50] -->
<string name="display_manager_overlay_display_title"><xliff:g id="name">%1$s</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string>
+ <!-- Name of a wifi display. [CHAR LIMIT=50] -->
+ <string name="display_manager_wifi_display_name">Wifi display: <xliff:g id="device">%1$s</xliff:g></string>
+
<!-- Keyguard strings -->
<!-- Label shown on emergency call button in keyguard -->
<string name="kg_emergency_call_label">Emergency call</string>
diff --git a/include/android_runtime/android_view_Surface.h b/include/android_runtime/android_view_Surface.h
index e50186d..df0fe72 100644
--- a/include/android_runtime/android_view_Surface.h
+++ b/include/android_runtime/android_view_Surface.h
@@ -24,6 +24,7 @@
namespace android {
class Surface;
+class ISurfaceTexture;
/* Gets the underlying ANativeWindow for a Surface. */
extern sp<ANativeWindow> android_view_Surface_getNativeWindow(
@@ -35,6 +36,10 @@
/* Gets the underlying Surface from a Surface Java object. */
extern sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj);
+/* Creates a Surface from an ISurfaceTexture. */
+extern jobject android_view_Surface_createFromISurfaceTexture(JNIEnv* env,
+ const sp<ISurfaceTexture>& surfaceTexture);
+
} // namespace android
#endif // _ANDROID_VIEW_SURFACE_H
diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java
new file mode 100644
index 0000000..b463d26
--- /dev/null
+++ b/media/java/android/media/RemoteDisplay.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2012 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 android.media;
+
+import dalvik.system.CloseGuard;
+
+import android.os.Handler;
+import android.view.Surface;
+
+/**
+ * Listens for Wifi remote display connections managed by the media server.
+ *
+ * @hide
+ */
+public final class RemoteDisplay {
+ /* these constants must be kept in sync with IRemoteDisplayClient.h */
+
+ public static final int DISPLAY_FLAG_SECURE = 1 << 0;
+
+ public static final int DISPLAY_ERROR_UNKOWN = 1;
+ public static final int DISPLAY_ERROR_CONNECTION_DROPPED = 2;
+
+ private final CloseGuard mGuard = CloseGuard.get();
+ private final Listener mListener;
+ private final Handler mHandler;
+
+ private int mPtr;
+
+ private native int nativeListen(String iface);
+ private native void nativeDispose(int ptr);
+
+ private RemoteDisplay(Listener listener, Handler handler) {
+ mListener = listener;
+ mHandler = handler;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ try {
+ dispose(true);
+ } finally {
+ super.finalize();
+ }
+ }
+
+ /**
+ * Starts listening for displays to be connected on the specified interface.
+ *
+ * @param iface The interface address and port in the form "x.x.x.x:y".
+ * @param listener The listener to invoke when displays are connected or disconnected.
+ * @param handler The handler on which to invoke the listener.
+ */
+ public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
+ if (iface == null) {
+ throw new IllegalArgumentException("iface must not be null");
+ }
+ if (listener == null) {
+ throw new IllegalArgumentException("listener must not be null");
+ }
+ if (handler == null) {
+ throw new IllegalArgumentException("handler must not be null");
+ }
+
+ RemoteDisplay display = new RemoteDisplay(listener, handler);
+ display.startListening(iface);
+ return display;
+ }
+
+ /**
+ * Disconnects the remote display and stops listening for new connections.
+ */
+ public void dispose() {
+ dispose(false);
+ }
+
+ private void dispose(boolean finalized) {
+ if (mPtr != 0) {
+ if (mGuard != null) {
+ if (finalized) {
+ mGuard.warnIfOpen();
+ } else {
+ mGuard.close();
+ }
+ }
+
+ nativeDispose(mPtr);
+ mPtr = 0;
+ }
+ }
+
+ private void startListening(String iface) {
+ mPtr = nativeListen(iface);
+ if (mPtr == 0) {
+ throw new IllegalStateException("Could not start listening for "
+ + "remote display connection on \"" + iface + "\"");
+ }
+ mGuard.open("dispose");
+ }
+
+ // Called from native.
+ private void notifyDisplayConnected(final Surface surface,
+ final int width, final int height, final int flags) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayConnected(surface, width, height, flags);
+ }
+ });
+ }
+
+ // Called from native.
+ private void notifyDisplayDisconnected() {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayDisconnected();
+ }
+ });
+ }
+
+ // Called from native.
+ private void notifyDisplayError(final int error) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayError(error);
+ }
+ });
+ }
+
+ /**
+ * Listener invoked when the remote display connection changes state.
+ */
+ public interface Listener {
+ void onDisplayConnected(Surface surface, int width, int height, int flags);
+ void onDisplayDisconnected();
+ void onDisplayError(int error);
+ }
+}
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index bdc87f9..995c553 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -17,7 +17,6 @@
package com.android.server.display;
import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
import android.os.IBinder;
import android.view.Surface;
@@ -41,9 +40,9 @@
private Rect mCurrentLayerStackRect;
private Rect mCurrentDisplayRect;
- // The display device does own its surface texture, but it should only set it
+ // The display device owns its surface, but it should only set it
// within a transaction from performTraversalInTransactionLocked.
- private SurfaceTexture mCurrentSurfaceTexture;
+ private Surface mCurrentSurface;
public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
mDisplayAdapter = displayAdapter;
@@ -109,11 +108,10 @@
* Sets the display layer stack while in a transaction.
*/
public final void setLayerStackInTransactionLocked(int layerStack) {
- if (mCurrentLayerStack == layerStack) {
- return;
+ if (mCurrentLayerStack != layerStack) {
+ mCurrentLayerStack = layerStack;
+ Surface.setDisplayLayerStack(mDisplayToken, layerStack);
}
- mCurrentLayerStack = layerStack;
- Surface.setDisplayLayerStack(mDisplayToken, layerStack);
}
/**
@@ -126,28 +124,35 @@
* mapped to. displayRect is specified post-orientation, that is
* it uses the orientation seen by the end-user
*/
- public final void setProjectionInTransactionLocked(int orientation, Rect layerStackRect, Rect displayRect) {
- mCurrentOrientation = orientation;
- if (mCurrentLayerStackRect == null) {
- mCurrentLayerStackRect = new Rect();
+ public final void setProjectionInTransactionLocked(int orientation,
+ Rect layerStackRect, Rect displayRect) {
+ if (mCurrentOrientation != orientation
+ || mCurrentLayerStackRect == null
+ || !mCurrentLayerStackRect.equals(layerStackRect)
+ || mCurrentDisplayRect == null
+ || !mCurrentDisplayRect.equals(displayRect)) {
+ mCurrentOrientation = orientation;
+ if (mCurrentLayerStackRect == null) {
+ mCurrentLayerStackRect = new Rect();
+ }
+ mCurrentLayerStackRect.set(layerStackRect);
+ if (mCurrentDisplayRect == null) {
+ mCurrentDisplayRect = new Rect();
+ }
+ mCurrentDisplayRect.set(displayRect);
+ Surface.setDisplayProjection(mDisplayToken,
+ orientation, layerStackRect, displayRect);
}
- mCurrentLayerStackRect.set(layerStackRect);
- if (mCurrentDisplayRect == null) {
- mCurrentDisplayRect = new Rect();
- }
- mCurrentDisplayRect.set(displayRect);
- Surface.setDisplayProjection(mDisplayToken, orientation, layerStackRect, displayRect);
}
/**
- * Sets the surface texture while in a transaction.
+ * Sets the display surface while in a transaction.
*/
- public final void setSurfaceTextureInTransactionLocked(SurfaceTexture surfaceTexture) {
- if (mCurrentSurfaceTexture == surfaceTexture) {
- return;
+ public final void setSurfaceInTransactionLocked(Surface surface) {
+ if (mCurrentSurface != surface) {
+ mCurrentSurface = surface;
+ Surface.setDisplaySurface(mDisplayToken, surface);
}
- mCurrentSurfaceTexture = surfaceTexture;
- Surface.setDisplaySurface(mDisplayToken, surfaceTexture);
}
/**
@@ -156,10 +161,11 @@
*/
public void dumpLocked(PrintWriter pw) {
pw.println("mAdapter=" + mDisplayAdapter.getName());
+ pw.println("mDisplayToken=" + mDisplayToken);
pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
pw.println("mCurrentOrientation=" + mCurrentOrientation);
- pw.println("mCurrentViewport=" + mCurrentLayerStackRect);
- pw.println("mCurrentFrame=" + mCurrentDisplayRect);
- pw.println("mCurrentSurfaceTexture=" + mCurrentSurfaceTexture);
+ pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect);
+ pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect);
+ pw.println("mCurrentSurface=" + mCurrentSurface);
}
}
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index 6f82119..c90a1c6 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import android.util.DisplayMetrics;
+
import libcore.util.Objects;
/**
@@ -58,13 +60,44 @@
*/
public int height;
+ /**
+ * The refresh rate of the display.
+ */
public float refreshRate;
+
+ /**
+ * The nominal apparent density of the display in DPI used for layout calculations.
+ * This density is sensitive to the viewing distance. A big TV and a tablet may have
+ * the same apparent density even though the pixels on the TV are much bigger than
+ * those on the tablet.
+ */
public int densityDpi;
+
+ /**
+ * The physical density of the display in DPI in the X direction.
+ * This density should specify the physical size of each pixel.
+ */
public float xDpi;
+
+ /**
+ * The physical density of the display in DPI in the X direction.
+ * This density should specify the physical size of each pixel.
+ */
public float yDpi;
+ /**
+ * Display flags.
+ */
public int flags;
+ public void setAssumedDensityForExternalDisplay(int width, int height) {
+ densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
+ // Technically, these values should be smaller than the apparent density
+ // but we don't know the physical size of the display.
+ xDpi = densityDpi;
+ yDpi = densityDpi;
+ }
+
@Override
public boolean equals(Object o) {
return o instanceof DisplayDeviceInfo && equals((DisplayDeviceInfo)o);
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 706007a..ae958df 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -333,6 +333,8 @@
if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
registerDisplayAdapterLocked(new OverlayDisplayAdapter(
mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+ registerDisplayAdapterLocked(new WifiDisplayAdapter(
+ mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
}
}
}
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index 4a8829a..4962753 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -120,19 +120,20 @@
mInfo.width = mPhys.width;
mInfo.height = mPhys.height;
mInfo.refreshRate = mPhys.refreshRate;
- mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
- mInfo.xDpi = mPhys.xDpi;
- mInfo.yDpi = mPhys.yDpi;
if (mBuiltInDisplayId == Surface.BUILT_IN_DISPLAY_ID_MAIN) {
mInfo.name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_built_in_display_name);
mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
| DisplayDeviceInfo.FLAG_SECURE
| DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION;
+ mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
+ mInfo.xDpi = mPhys.xDpi;
+ mInfo.yDpi = mPhys.yDpi;
} else {
mInfo.name = getContext().getResources().getString(
com.android.internal.R.string.display_manager_hdmi_display_name);
mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+ mInfo.setAssumedDensityForExternalDisplay(mPhys.width, mPhys.height);
}
}
return mInfo;
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
index ea7e88de..e2d3059 100644
--- a/services/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -16,9 +16,11 @@
package com.android.server.display;
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
import android.content.Context;
import android.database.ContentObserver;
-import android.graphics.SurfaceTexture;
import android.os.Handler;
import android.os.IBinder;
import android.provider.Settings;
@@ -28,7 +30,6 @@
import android.view.Surface;
import java.io.PrintWriter;
-import java.io.StringWriter;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -71,6 +72,7 @@
@Override
public void dumpLocked(PrintWriter pw) {
super.dumpLocked(pw);
+
pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
pw.println("mOverlays: size=" + mOverlays.size());
for (OverlayDisplayHandle overlay : mOverlays) {
@@ -81,17 +83,19 @@
@Override
public void registerLocked() {
super.registerLocked();
- getContext().getContentResolver().registerContentObserver(
- Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true,
- new ContentObserver(getHandler()) {
- @Override
- public void onChange(boolean selfChange) {
- synchronized (getSyncRoot()) {
- updateOverlayDisplayDevicesLocked();
- }
- }
- });
- updateOverlayDisplayDevicesLocked();
+
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ getContext().getContentResolver().registerContentObserver(
+ Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES),
+ true, new SettingsObserver(getHandler()));
+
+ synchronized (getSyncRoot()) {
+ updateOverlayDisplayDevicesLocked();
+ }
+ }
+ });
}
private void updateOverlayDisplayDevicesLocked() {
@@ -167,6 +171,19 @@
}
}
+ private final class SettingsObserver extends ContentObserver {
+ public SettingsObserver(Handler handler) {
+ super(handler);
+ }
+
+ @Override
+ public void onChange(boolean selfChange) {
+ synchronized (getSyncRoot()) {
+ updateOverlayDisplayDevicesLocked();
+ }
+ }
+ }
+
private final class OverlayDisplayDevice extends DisplayDevice {
private final String mName;
private final int mWidth;
@@ -174,35 +191,29 @@
private final float mRefreshRate;
private final int mDensityDpi;
- private SurfaceTexture mSurfaceTexture;
- private boolean mSurfaceTextureChanged;
-
+ private Surface mSurface;
private DisplayDeviceInfo mInfo;
public OverlayDisplayDevice(IBinder displayToken, String name,
- int width, int height, float refreshRate, int densityDpi) {
+ int width, int height, float refreshRate, int densityDpi,
+ Surface surface) {
super(OverlayDisplayAdapter.this, displayToken);
mName = name;
mWidth = width;
mHeight = height;
mRefreshRate = refreshRate;
mDensityDpi = densityDpi;
+ mSurface = surface;
}
- public void setSurfaceTextureLocked(SurfaceTexture surfaceTexture) {
- if (surfaceTexture != mSurfaceTexture) {
- mSurfaceTexture = surfaceTexture;
- mSurfaceTextureChanged = true;
- sendTraversalRequestLocked();
- }
+ public void clearSurfaceLocked() {
+ mSurface = null;
+ sendTraversalRequestLocked();
}
@Override
public void performTraversalInTransactionLocked() {
- if (mSurfaceTextureChanged) {
- setSurfaceTextureInTransactionLocked(mSurfaceTexture);
- mSurfaceTextureChanged = false;
- }
+ setSurfaceInTransactionLocked(mSurface);
}
@Override
@@ -256,12 +267,11 @@
// Called on the UI thread.
@Override
- public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) {
+ public void onWindowCreated(Surface surface, float refreshRate) {
synchronized (getSyncRoot()) {
IBinder displayToken = Surface.createDisplay(mName);
mDevice = new OverlayDisplayDevice(displayToken, mName,
- mWidth, mHeight, refreshRate, mDensityDpi);
- mDevice.setSurfaceTextureLocked(surfaceTexture);
+ mWidth, mHeight, refreshRate, mDensityDpi, surface);
sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
}
@@ -272,40 +282,24 @@
public void onWindowDestroyed() {
synchronized (getSyncRoot()) {
if (mDevice != null) {
- mDevice.setSurfaceTextureLocked(null);
-
+ mDevice.clearSurfaceLocked();
sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
}
}
}
public void dumpLocked(PrintWriter pw) {
- pw.println(" " + mName + ": ");
+ pw.println(" " + mName + ":");
pw.println(" mWidth=" + mWidth);
pw.println(" mHeight=" + mHeight);
pw.println(" mDensityDpi=" + mDensityDpi);
pw.println(" mGravity=" + mGravity);
// Try to dump the window state.
- // This call may hang if the UI thread is waiting to acquire our lock so
- // we use a short timeout to recover just in case.
if (mWindow != null) {
- final StringWriter sw = new StringWriter();
- final OverlayDisplayWindow window = mWindow;
- if (mUiHandler.runWithScissors(new Runnable() {
- @Override
- public void run() {
- PrintWriter lpw = new PrintWriter(sw);
- window.dump(lpw);
- lpw.close();
- }
- }, 200)) {
- for (String line : sw.toString().split("\n")) {
- pw.println(line);
- }
- } else {
- pw.println(" ... timed out while attempting to dump window state");
- }
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.increaseIndent();
+ DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200);
}
}
diff --git a/services/java/com/android/server/display/OverlayDisplayWindow.java b/services/java/com/android/server/display/OverlayDisplayWindow.java
index 6adfa0f..d08f65f 100644
--- a/services/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/java/com/android/server/display/OverlayDisplayWindow.java
@@ -16,6 +16,8 @@
package com.android.server.display;
+import com.android.internal.util.DumpUtils;
+
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
@@ -27,6 +29,7 @@
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
+import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.WindowManager;
@@ -42,7 +45,7 @@
* No locks are held by this object and locks must not be held while making called into it.
* </p>
*/
-final class OverlayDisplayWindow {
+final class OverlayDisplayWindow implements DumpUtils.Dump {
private static final String TAG = "OverlayDisplayWindow";
private static final boolean DEBUG = false;
@@ -144,18 +147,18 @@
}
public void dump(PrintWriter pw) {
- pw.println(" mWindowVisible=" + mWindowVisible);
- pw.println(" mWindowX=" + mWindowX);
- pw.println(" mWindowY=" + mWindowY);
- pw.println(" mWindowScale=" + mWindowScale);
- pw.println(" mWindowParams=" + mWindowParams);
+ pw.println("mWindowVisible=" + mWindowVisible);
+ pw.println("mWindowX=" + mWindowX);
+ pw.println("mWindowY=" + mWindowY);
+ pw.println("mWindowScale=" + mWindowScale);
+ pw.println("mWindowParams=" + mWindowParams);
if (mTextureView != null) {
- pw.println(" mTextureView.getScaleX()=" + mTextureView.getScaleX());
- pw.println(" mTextureView.getScaleY()=" + mTextureView.getScaleY());
+ pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX());
+ pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY());
}
- pw.println(" mLiveTranslationX=" + mLiveTranslationX);
- pw.println(" mLiveTranslationY=" + mLiveTranslationY);
- pw.println(" mLiveScale=" + mLiveScale);
+ pw.println("mLiveTranslationX=" + mLiveTranslationX);
+ pw.println("mLiveTranslationY=" + mLiveTranslationY);
+ pw.println("mLiveScale=" + mLiveScale);
}
private boolean updateDefaultDisplayInfo() {
@@ -286,22 +289,25 @@
private final SurfaceTextureListener mSurfaceTextureListener =
new SurfaceTextureListener() {
@Override
- public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
- mListener.onWindowCreated(surface, mDefaultDisplayInfo.refreshRate);
+ public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+ int width, int height) {
+ mListener.onWindowCreated(new Surface(surfaceTexture),
+ mDefaultDisplayInfo.refreshRate);
}
@Override
- public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
mListener.onWindowDestroyed();
return true;
}
@Override
- public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
+ int width, int height) {
}
@Override
- public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
}
};
@@ -355,7 +361,7 @@
* Watches for significant changes in the overlay display window lifecycle.
*/
public interface Listener {
- public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate);
+ public void onWindowCreated(Surface surface, float refreshRate);
public void onWindowDestroyed();
}
}
\ No newline at end of file
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
new file mode 100644
index 0000000..38007af
--- /dev/null
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012 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.display;
+
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
+import android.content.Context;
+import android.media.RemoteDisplay;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.Surface;
+
+import java.io.PrintWriter;
+
+/**
+ * Connects to Wifi displays that implement the Miracast protocol.
+ * <p>
+ * The Wifi display protocol relies on Wifi direct for discovering and pairing
+ * with the display. Once connected, the Media Server opens an RTSP socket and accepts
+ * a connection from the display. After session negotiation, the Media Server
+ * streams encoded buffers to the display.
+ * </p><p>
+ * This class is responsible for connecting to Wifi displays and mediating
+ * the interactions between Media Server, Surface Flinger and the Display Manager Service.
+ * </p><p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class WifiDisplayAdapter extends DisplayAdapter {
+ private static final String TAG = "WifiDisplayAdapter";
+
+ private WifiDisplayHandle mDisplayHandle;
+ private WifiDisplayController mDisplayController;
+
+ public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+ Context context, Handler handler, Listener listener) {
+ super(syncRoot, context, handler, listener, TAG);
+ }
+
+ @Override
+ public void dumpLocked(PrintWriter pw) {
+ super.dumpLocked(pw);
+
+ if (mDisplayHandle == null) {
+ pw.println("mDisplayHandle=null");
+ } else {
+ pw.println("mDisplayHandle:");
+ mDisplayHandle.dumpLocked(pw);
+ }
+
+ // Try to dump the controller state.
+ if (mDisplayController == null) {
+ pw.println("mDisplayController=null");
+ } else {
+ pw.println("mDisplayController:");
+ final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+ ipw.increaseIndent();
+ DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
+ }
+ }
+
+ @Override
+ public void registerLocked() {
+ super.registerLocked();
+
+ getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mDisplayController = new WifiDisplayController(
+ getContext(), getHandler(), mWifiDisplayListener);
+ }
+ });
+ }
+
+ private void connectLocked(String deviceName, String iface) {
+ disconnectLocked();
+
+ String name = getContext().getResources().getString(
+ com.android.internal.R.string.display_manager_wifi_display_name,
+ deviceName);
+ mDisplayHandle = new WifiDisplayHandle(name, iface);
+ }
+
+ private void disconnectLocked() {
+ if (mDisplayHandle != null) {
+ mDisplayHandle.disposeLocked();
+ mDisplayHandle = null;
+ }
+ }
+
+ private final WifiDisplayController.Listener mWifiDisplayListener =
+ new WifiDisplayController.Listener() {
+ @Override
+ public void onDisplayConnected(String deviceName, String iface) {
+ synchronized (getSyncRoot()) {
+ connectLocked(deviceName, iface);
+ }
+ }
+
+ @Override
+ public void onDisplayDisconnected() {
+ // Stop listening.
+ synchronized (getSyncRoot()) {
+ disconnectLocked();
+ }
+ }
+ };
+
+ private final class WifiDisplayDevice extends DisplayDevice {
+ private final String mName;
+ private final int mWidth;
+ private final int mHeight;
+ private final float mRefreshRate;
+ private final int mFlags;
+
+ private Surface mSurface;
+ private DisplayDeviceInfo mInfo;
+
+ public WifiDisplayDevice(IBinder displayToken, String name,
+ int width, int height, float refreshRate, int flags,
+ Surface surface) {
+ super(WifiDisplayAdapter.this, displayToken);
+ mName = name;
+ mWidth = width;
+ mHeight = height;
+ mRefreshRate = refreshRate;
+ mFlags = flags;
+ mSurface = surface;
+ }
+
+ public void clearSurfaceLocked() {
+ mSurface = null;
+ sendTraversalRequestLocked();
+ }
+
+ @Override
+ public void performTraversalInTransactionLocked() {
+ setSurfaceInTransactionLocked(mSurface);
+ }
+
+ @Override
+ public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+ if (mInfo == null) {
+ mInfo = new DisplayDeviceInfo();
+ mInfo.name = mName;
+ mInfo.width = mWidth;
+ mInfo.height = mHeight;
+ mInfo.refreshRate = mRefreshRate;
+ mInfo.flags = mFlags;
+ mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
+ }
+ return mInfo;
+ }
+ }
+
+ private final class WifiDisplayHandle implements RemoteDisplay.Listener {
+ private final String mName;
+ private final String mIface;
+ private final RemoteDisplay mRemoteDisplay;
+
+ private WifiDisplayDevice mDevice;
+ private int mLastError;
+
+ public WifiDisplayHandle(String name, String iface) {
+ mName = name;
+ mIface = iface;
+ mRemoteDisplay = RemoteDisplay.listen(iface, this, getHandler());
+
+ Slog.i(TAG, "Listening for Wifi display connections on " + iface
+ + " from " + mName);
+ }
+
+ public void disposeLocked() {
+ Slog.i(TAG, "Stopped listening for Wifi display connections on " + mIface
+ + " from " + mName);
+
+ removeDisplayLocked();
+ mRemoteDisplay.dispose();
+ }
+
+ public void dumpLocked(PrintWriter pw) {
+ pw.println(" " + mName + ": " + (mDevice != null ? "connected" : "disconnected"));
+ pw.println(" mIface=" + mIface);
+ pw.println(" mLastError=" + mLastError);
+ }
+
+ // Called on the handler thread.
+ @Override
+ public void onDisplayConnected(Surface surface, int width, int height, int flags) {
+ synchronized (getSyncRoot()) {
+ mLastError = 0;
+ removeDisplayLocked();
+ addDisplayLocked(surface, width, height, flags);
+
+ Slog.i(TAG, "Wifi display connected: " + mName);
+ }
+ }
+
+ // Called on the handler thread.
+ @Override
+ public void onDisplayDisconnected() {
+ synchronized (getSyncRoot()) {
+ mLastError = 0;
+ removeDisplayLocked();
+
+ Slog.i(TAG, "Wifi display disconnected: " + mName);
+ }
+ }
+
+ // Called on the handler thread.
+ @Override
+ public void onDisplayError(int error) {
+ synchronized (getSyncRoot()) {
+ mLastError = error;
+ removeDisplayLocked();
+
+ Slog.i(TAG, "Wifi display disconnected due to error " + error + ": " + mName);
+ }
+ }
+
+ private void addDisplayLocked(Surface surface, int width, int height, int flags) {
+ int deviceFlags = 0;
+ if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
+ deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
+ }
+
+ float refreshRate = 60.0f; // TODO: get this for real
+
+ IBinder displayToken = Surface.createDisplay(mName);
+ mDevice = new WifiDisplayDevice(displayToken, mName, width, height,
+ refreshRate, deviceFlags, surface);
+ sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
+ }
+
+ private void removeDisplayLocked() {
+ if (mDevice != null) {
+ mDevice.clearSurfaceLocked();
+ sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+ mDevice = null;
+ }
+ }
+ }
+}
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
new file mode 100644
index 0000000..2c32e73
--- /dev/null
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -0,0 +1,554 @@
+/*
+ * Copyright (C) 2012 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.display;
+
+import com.android.internal.util.DumpUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.WifiP2pManager.ActionListener;
+import android.net.wifi.p2p.WifiP2pManager.Channel;
+import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
+import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
+import android.os.Handler;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
+ * on behalf of {@link WifiDisplayAdapter}.
+ * <p>
+ * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
+ * accidentally introducing any deadlocks due to the display manager calling
+ * outside of itself while holding its lock. It's also way easier to write this
+ * asynchronous code if we can assume that it is single-threaded.
+ * </p><p>
+ * The controller must be instantiated on the handler thread.
+ * </p>
+ */
+final class WifiDisplayController implements DumpUtils.Dump {
+ private static final String TAG = "WifiDisplayController";
+ private static final boolean DEBUG = true;
+
+ private static final int DEFAULT_CONTROL_PORT = 7236;
+ private static final int MAX_THROUGHPUT = 50;
+ private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+
+ private final Context mContext;
+ private final Handler mHandler;
+ private final Listener mListener;
+ private final WifiP2pManager mWifiP2pManager;
+ private final Channel mWifiP2pChannel;
+
+ private boolean mWifiP2pEnabled;
+ private boolean mWfdEnabled;
+ private boolean mWfdEnabling;
+ private NetworkInfo mNetworkInfo;
+
+ private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers =
+ new ArrayList<WifiP2pDevice>();
+
+ // The device to which we want to connect, or null if we want to be disconnected.
+ private WifiP2pDevice mDesiredDevice;
+
+ // The device to which we are currently connecting, or null if we have already connected
+ // or are not trying to connect.
+ private WifiP2pDevice mConnectingDevice;
+
+ // The device to which we are currently connected, which means we have an active P2P group.
+ private WifiP2pDevice mConnectedDevice;
+
+ // The group info obtained after connecting.
+ private WifiP2pGroup mConnectedDeviceGroupInfo;
+
+ // The device that we announced to the rest of the system.
+ private WifiP2pDevice mPublishedDevice;
+
+ public WifiDisplayController(Context context, Handler handler, Listener listener) {
+ mContext = context;
+ mHandler = handler;
+ mListener = listener;
+
+ mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
+ mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
+
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+ intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+ context.registerReceiver(mWifiP2pReceiver, intentFilter);
+ }
+
+ public void dump(PrintWriter pw) {
+ pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
+ pw.println("mWfdEnabled=" + mWfdEnabled);
+ pw.println("mWfdEnabling=" + mWfdEnabling);
+ pw.println("mNetworkInfo=" + mNetworkInfo);
+ pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
+ pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
+ pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
+ pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice));
+
+ pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
+ for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
+ pw.println(" " + describeWifiP2pDevice(device));
+ }
+ }
+
+ private void enableWfd() {
+ if (!mWfdEnabled && !mWfdEnabling) {
+ mWfdEnabling = true;
+
+ WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+ wfdInfo.setWfdEnabled(true);
+ wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
+ wfdInfo.setSessionAvailable(true);
+ wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
+ wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
+ mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Successfully set WFD info.");
+ }
+ if (mWfdEnabling) {
+ mWfdEnabled = true;
+ mWfdEnabling = false;
+ discoverPeers();
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+ }
+ mWfdEnabling = false;
+ }
+ });
+ }
+ }
+
+ private void discoverPeers() {
+ mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ if (DEBUG) {
+ Slog.d(TAG, "Discover peers succeeded. Requesting peers now.");
+ }
+
+ requestPeers();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ if (DEBUG) {
+ Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
+ }
+ }
+ });
+ }
+
+ private void requestPeers() {
+ mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
+ @Override
+ public void onPeersAvailable(WifiP2pDeviceList peers) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received list of peers.");
+ }
+
+ mKnownWifiDisplayPeers.clear();
+ for (WifiP2pDevice device : peers.getDeviceList()) {
+ if (DEBUG) {
+ Slog.d(TAG, " " + describeWifiP2pDevice(device));
+ }
+
+ if (isWifiDisplay(device)) {
+ mKnownWifiDisplayPeers.add(device);
+ }
+ }
+
+ // TODO: shouldn't auto-connect like this, let UI do it explicitly
+ if (!mKnownWifiDisplayPeers.isEmpty()) {
+ final WifiP2pDevice device = mKnownWifiDisplayPeers.get(0);
+
+ if (device.status == WifiP2pDevice.AVAILABLE) {
+ connect(device);
+ }
+ }
+
+ // TODO: publish this information to applications
+ }
+ });
+ }
+
+ private void connect(final WifiP2pDevice device) {
+ if (mDesiredDevice != null
+ && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
+ if (DEBUG) {
+ Slog.d(TAG, "connect: nothing to do, already connecting to "
+ + describeWifiP2pDevice(device));
+ }
+ return;
+ }
+
+ if (mConnectedDevice != null
+ && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
+ && mDesiredDevice == null) {
+ if (DEBUG) {
+ Slog.d(TAG, "connect: nothing to do, already connected to "
+ + describeWifiP2pDevice(device) + " and not part way through "
+ + "connecting to a different device.");
+ }
+ }
+
+ mDesiredDevice = device;
+ updateConnection();
+ }
+
+ private void disconnect() {
+ mDesiredDevice = null;
+ updateConnection();
+ }
+
+ /**
+ * This function is called repeatedly after each asynchronous operation
+ * until all preconditions for the connection have been satisfied and the
+ * connection is established (or not).
+ */
+ private void updateConnection() {
+ // Step 1. Before we try to connect to a new device, tell the system we
+ // have disconnected from the old one.
+ if (mPublishedDevice != null && mPublishedDevice != mDesiredDevice) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayDisconnected();
+ }
+ });
+ mPublishedDevice = null;
+
+ // continue to next step
+ }
+
+ // Step 2. Before we try to connect to a new device, disconnect from the old one.
+ if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
+ Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
+
+ final WifiP2pDevice oldDevice = mConnectedDevice;
+ mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
+ next();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.i(TAG, "Failed to disconnect from Wifi display: "
+ + oldDevice.deviceName + ", reason=" + reason);
+ next();
+ }
+
+ private void next() {
+ if (mConnectedDevice == oldDevice) {
+ mConnectedDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 3. Before we try to connect to a new device, stop trying to connect
+ // to the old one.
+ if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
+ Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
+
+ mHandler.removeCallbacks(mConnectionTimeout);
+
+ final WifiP2pDevice oldDevice = mConnectingDevice;
+ mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
+ next();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.i(TAG, "Failed to cancel connection to Wifi display: "
+ + oldDevice.deviceName + ", reason=" + reason);
+ next();
+ }
+
+ private void next() {
+ if (mConnectingDevice == oldDevice) {
+ mConnectingDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 4. If we wanted to disconnect, then mission accomplished.
+ if (mDesiredDevice == null) {
+ return; // done
+ }
+
+ // Step 5. Try to connect.
+ if (mConnectedDevice == null && mConnectingDevice == null) {
+ Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
+
+ mConnectingDevice = mDesiredDevice;
+ WifiP2pConfig config = new WifiP2pConfig();
+ config.deviceAddress = mConnectingDevice.deviceAddress;
+
+ final WifiP2pDevice newDevice = mDesiredDevice;
+ mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
+ @Override
+ public void onSuccess() {
+ // The connection may not yet be established. We still need to wait
+ // for WIFI_P2P_CONNECTION_CHANGED_ACTION. However, we might never
+ // get that broadcast, so we register a timeout.
+ Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
+
+ mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+ + newDevice.deviceName + ", reason=" + reason);
+ if (mConnectingDevice == newDevice) {
+ mConnectingDevice = null;
+ handleConnectionFailure();
+ }
+ }
+ });
+ return; // wait for asynchronous callback
+ }
+
+ // Step 6. Publish the new connection.
+ if (mConnectedDevice != null && mPublishedDevice == null) {
+ Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
+ if (addr == null) {
+ Slog.i(TAG, "Failed to get local interface address for communicating "
+ + "with Wifi display: " + mConnectedDevice.deviceName);
+ handleConnectionFailure();
+ return; // done
+ }
+
+ WifiP2pWfdInfo wfdInfo = mConnectedDevice.wfdInfo;
+ int port = (wfdInfo != null ? wfdInfo.getControlPort() : DEFAULT_CONTROL_PORT);
+ final String name = mConnectedDevice.deviceName;
+ final String iface = addr.getHostAddress() + ":" + port;
+
+ mPublishedDevice = mConnectedDevice;
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onDisplayConnected(name, iface);
+ }
+ });
+ }
+ }
+
+ private void handleStateChanged(boolean enabled) {
+ if (mWifiP2pEnabled != enabled) {
+ mWifiP2pEnabled = enabled;
+ if (enabled) {
+ if (mWfdEnabled) {
+ discoverPeers();
+ } else {
+ enableWfd();
+ }
+ } else {
+ mWfdEnabled = false;
+ disconnect();
+ }
+ }
+ }
+
+ private void handlePeersChanged() {
+ if (mWifiP2pEnabled) {
+ if (mWfdEnabled) {
+ requestPeers();
+ } else {
+ enableWfd();
+ }
+ }
+ }
+
+ private void handleConnectionChanged(NetworkInfo networkInfo) {
+ mNetworkInfo = networkInfo;
+ if (mWifiP2pEnabled && mWfdEnabled && networkInfo.isConnected()) {
+ if (mDesiredDevice != null) {
+ mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
+ @Override
+ public void onGroupInfoAvailable(WifiP2pGroup info) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
+ }
+
+ if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
+ Slog.i(TAG, "Aborting connection to Wifi display because "
+ + "the current P2P group does not contain the device "
+ + "we expected to find: " + mConnectingDevice.deviceName);
+ handleConnectionFailure();
+ return;
+ }
+
+ if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
+ disconnect();
+ return;
+ }
+
+ if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+ Slog.i(TAG, "Connected to Wifi display: " + mConnectingDevice.deviceName);
+
+ mHandler.removeCallbacks(mConnectionTimeout);
+ mConnectedDeviceGroupInfo = info;
+ mConnectedDevice = mConnectingDevice;
+ mConnectingDevice = null;
+ updateConnection();
+ }
+ }
+ });
+ }
+ } else {
+ disconnect();
+ }
+ }
+
+ private final Runnable mConnectionTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+ Slog.i(TAG, "Timed out waiting for Wifi display connection after "
+ + CONNECTION_TIMEOUT_SECONDS + " seconds: "
+ + mConnectingDevice.deviceName);
+ handleConnectionFailure();
+ }
+ }
+ };
+
+ private void handleConnectionFailure() {
+ if (mDesiredDevice != null) {
+ Slog.i(TAG, "Wifi display connection failed!");
+ disconnect();
+ }
+ }
+
+ private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
+ NetworkInterface iface;
+ try {
+ iface = NetworkInterface.getByName(info.getInterface());
+ } catch (SocketException ex) {
+ Slog.w(TAG, "Could not obtain address of network interface "
+ + info.getInterface(), ex);
+ return null;
+ }
+
+ Enumeration<InetAddress> addrs = iface.getInetAddresses();
+ while (addrs.hasMoreElements()) {
+ InetAddress addr = addrs.nextElement();
+ if (addr instanceof Inet4Address) {
+ return (Inet4Address)addr;
+ }
+ }
+
+ Slog.w(TAG, "Could not obtain address of network interface "
+ + info.getInterface() + " because it had no IPv4 addresses.");
+ return null;
+ }
+
+ private static boolean isWifiDisplay(WifiP2pDevice device) {
+ // FIXME: the wfdInfo API doesn't work yet
+ return device.deviceName.equals("DWD-300-22ACC2");
+ //return device.deviceName.startsWith("DWD-")
+ // || device.deviceName.startsWith("DIRECT-")
+ // || device.deviceName.startsWith("CAVM-");
+ //return device.wfdInfo != null && device.wfdInfo.isWfdEnabled();
+ }
+
+ private static String describeWifiP2pDevice(WifiP2pDevice device) {
+ return device != null ? device.toString().replace('\n', ',') : "null";
+ }
+
+ private static String describeWifiP2pGroup(WifiP2pGroup group) {
+ return group != null ? group.toString().replace('\n', ',') : "null";
+ }
+
+ private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
+ boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+ WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
+ WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
+ + enabled);
+ }
+
+ handleStateChanged(enabled);
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
+ }
+
+ handlePeersChanged();
+ } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
+ NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+ WifiP2pManager.EXTRA_NETWORK_INFO);
+ if (DEBUG) {
+ Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
+ + networkInfo);
+ }
+
+ handleConnectionChanged(networkInfo);
+ }
+ }
+ };
+
+ /**
+ * Called on the handler thread when displays are connected or disconnected.
+ */
+ public interface Listener {
+ void onDisplayConnected(String deviceName, String iface);
+ void onDisplayDisconnected();
+ }
+}