diff options
| author | Jeff Brown <jeffbrown@google.com> | 2012-09-07 16:00:12 -0700 |
|---|---|---|
| committer | Android (Google) Code Review <android-gerrit@google.com> | 2012-09-07 16:00:13 -0700 |
| commit | 7017e48380ab0c1be033594bb2a9331898ad5be8 (patch) | |
| tree | 1e24e747937706deb96ac1770f71a9c4cb6ebc65 | |
| parent | cd620591b764cd999f18878985444fba01d5b710 (diff) | |
| parent | cbad976b2a36a0895ca94510d5208a86f66cf596 (diff) | |
| download | frameworks_base-7017e48380ab0c1be033594bb2a9331898ad5be8.zip frameworks_base-7017e48380ab0c1be033594bb2a9331898ad5be8.tar.gz frameworks_base-7017e48380ab0c1be033594bb2a9331898ad5be8.tar.bz2 | |
Merge "Add support for Wifi display." into jb-mr1-dev
19 files changed, 1441 insertions, 138 deletions
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 @@ public class Surface implements Parcelable { 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 @@ public class Surface implements Parcelable { } /** @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 @@ public class IndentingPrintWriter extends PrintWriter { 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 class IndentingPrintWriter extends PrintWriter { 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 @@ public class IndentingPrintWriter extends PrintWriter { } @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 @@ LOCAL_SRC_FILES:= \ 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_backup_BackupHelperDispatcher(JNIEnv *env); 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 @@ static const RegJNIRec gRegJNI[] = { 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 @@ static struct { jfieldID mGenerationId; jfieldID mCanvas; jfieldID mCanvasSaveCount; + jmethodID ctor; } gSurfaceClassInfo; static struct { @@ -231,6 +232,42 @@ static void setSurface(JNIEnv* env, jobject surfaceObj, const sp<Surface>& surfa } } +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 jobject nativeCreateDisplay(JNIEnv* env, jclass clazz, jstring nameObj) { } 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 nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz, } 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 @@ static JNINativeMethod gSurfaceMethods[] = { (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 @@ int register_android_view_Surface(JNIEnv* env) 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 @@ extern bool android_view_Surface_isInstanceOf(JNIEnv* env, jobject obj); /* 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 @@ abstract class DisplayDevice { 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 @@ abstract class DisplayDevice { * 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 @@ abstract class DisplayDevice { * 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 @@ abstract class DisplayDevice { */ 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 @@ final class DisplayDeviceInfo { */ 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 @@ public final class DisplayManagerService extends IDisplayManager.Stub { 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { 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 ea7e88d..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.Gravity; 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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { @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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { @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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { } } + 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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { 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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { // 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 @@ final class OverlayDisplayAdapter extends DisplayAdapter { 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.Gravity; 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 @@ import java.io.PrintWriter; * 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 @@ final class OverlayDisplayWindow { } 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 @@ final class OverlayDisplayWindow { 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 @@ final class OverlayDisplayWindow { * 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(); + } +} |
