Merge "Add support for Wifi display." into jb-mr1-dev
This commit is contained in:
@@ -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 */
|
||||
|
||||
57
core/java/com/android/internal/util/DumpUtils.java
Normal file
57
core/java/com/android/internal/util/DumpUtils.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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),
|
||||
|
||||
182
core/jni/android_media_RemoteDisplay.cpp
Normal file
182
core/jni/android_media_RemoteDisplay.cpp
Normal file
@@ -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;
|
||||
}
|
||||
|
||||
};
|
||||
@@ -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");
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
153
media/java/android/media/RemoteDisplay.java
Normal file
153
media/java/android/media/RemoteDisplay.java
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
259
services/java/com/android/server/display/WifiDisplayAdapter.java
Normal file
259
services/java/com/android/server/display/WifiDisplayAdapter.java
Normal file
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user