WifiDisplay on Andorid4.2

Android 4.2开始支持secondary display。 提供media router用于query当前secondary display的状态。 提供Presentation, 由用户负责填充,作为secondary display的输入。

在android source code中使用CONFIG_WIFI_DISPLAY来区分相关代码。

  • 从mediaroutner说起
  public MediaRouter(Context context) {        synchronized (Static.class) {
            if (sStatic == null) {
                final Context appContext = context.getApplicationContext();
                sStatic = new Static(appContext);     //创建static
                sStatic.startMonitoringRoutes(appContext);     //调用 startMonitoringRoutes, 
            }
        }
    }

public class MediaRouter 

{
    static class Static implements DisplayManager.DisplayListener {
        void startMonitoringRoutes(Context appContext) {
              appContext.registerReceiver(new WifiDisplayStatusChangedReceiver(),     //将WifiDisplayStatusChangedReceiver注册进去。                    new IntentFilter(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED));
  

     }
     }
}

  static class WifiDisplayStatusChangedReceiver extends BroadcastReceiver {        @Override
        public void onReceive(Context context, Intent intent) {
            if (intent.getAction().equals(DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED)) {
                updateWifiDisplayStatus((WifiDisplayStatus) intent.getParcelableExtra(
                        DisplayManager.EXTRA_WIFI_DISPLAY_STATUS));
            }
        }
    }

  • MediaRouter 将接收DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED消息, 当收到消息时,调用updateWifiDisplayStatus()。根据wifiDisplay 创建display route

 static void updateWifiDisplayStatus(WifiDisplayStatus newStatus) {        final WifiDisplayStatus oldStatus = sStatic.mLastKnownWifiDisplayStatus;

        // TODO Naive implementation. Make this smarter later.
        boolean wantScan = false;
        boolean blockScan = false;
        WifiDisplay[] oldDisplays = oldStatus != null ?
                oldStatus.getRememberedDisplays() : new WifiDisplay[0];
        WifiDisplay[] newDisplays = newStatus.getRememberedDisplays();
        WifiDisplay[] availableDisplays = newStatus.getAvailableDisplays();
        WifiDisplay activeDisplay = newStatus.getActiveDisplay();     //获得新的display

        for (int i = 0; i < newDisplays.length; i++) {
            final WifiDisplay d = newDisplays[i];
            final WifiDisplay oldRemembered = findMatchingDisplay(d, oldDisplays);
            if (oldRemembered == null) {
                addRouteStatic(makeWifiDisplayRoute(d,
                        findMatchingDisplay(d, availableDisplays) != null));     //新建wifi router
                wantScan = true;
            } else {
                final boolean available = findMatchingDisplay(d, availableDisplays) != null;
                final RouteInfo route = findWifiDisplayRoute(d);
                updateWifiDisplayRoute(route, d, available, newStatus);
            }
            if (d.equals(activeDisplay)) {
                final RouteInfo activeRoute = findWifiDisplayRoute(d);
                if (activeRoute != null) {
                    selectRouteStatic(activeRoute.getSupportedTypes(), activeRoute);

                    // Don't scan if we're already connected to a wifi display,
                    // the scanning process can cause a hiccup with some configurations.
                    blockScan = true;
                }
            }
        }
        for (int i = 0; i < oldDisplays.length; i++) {
            final WifiDisplay d = oldDisplays[i];
            final WifiDisplay newDisplay = findMatchingDisplay(d, newDisplays);
            if (newDisplay == null) {
                removeRoute(findWifiDisplayRoute(d));
            }
        }

        if (wantScan && !blockScan) {
            sStatic.mDisplayService.scanWifiDisplays();//重要 !会触发discoverPeers
        }

        sStatic.mLastKnownWifiDisplayStatus = newStatus;
    }


  • 确认wifidisplay是合法的
    private static WifiDisplay findMatchingDisplay(WifiDisplay d, WifiDisplay[] displays) {

        for (int i = 0; i < displays.length; i++) {
            final WifiDisplay other = displays[i];
            if (d.getDeviceAddress().equals(other.getDeviceAddress())) {
                return other;
            }
        }
        return null;
    }

  • 创建router
    static RouteInfo makeWifiDisplayRoute(WifiDisplay display, boolean available) {        final RouteInfo newRoute = new RouteInfo(sStatic.mSystemCategory);
        newRoute.mDeviceAddress = display.getDeviceAddress();     //mac 地址
        newRoute.mSupportedTypes = ROUTE_TYPE_LIVE_AUDIO | ROUTE_TYPE_LIVE_VIDEO;
        newRoute.mVolumeHandling = RouteInfo.PLAYBACK_VOLUME_FIXED;
        newRoute.mPlaybackType = RouteInfo.PLAYBACK_TYPE_REMOTE;


        newRoute.setStatusCode(available ?
                RouteInfo.STATUS_AVAILABLE : RouteInfo.STATUS_CONNECTING);
        newRoute.mEnabled = available;


        newRoute.mName = display.getFriendlyDisplayName();


        newRoute.mPresentationDisplay = choosePresentationDisplayForRoute(newRoute,
                sStatic.getAllPresentationDisplays());
        return newRoute;
    }

Connect Wifi Display!
     static void selectRouteStatic(int types, RouteInfo route) {        final RouteInfo oldRoute = sStatic.mSelectedRoute;
        if (oldRoute == route) return;
        if ((route.getSupportedTypes() & types) == 0) {
            Log.w(TAG, "selectRoute ignored; cannot select route with supported types " +
                    typesToString(route.getSupportedTypes()) + " into route types " +
                    typesToString(types));
            return;
        }


        final RouteInfo btRoute = sStatic.mBluetoothA2dpRoute;
        if (btRoute != null && (types & ROUTE_TYPE_LIVE_AUDIO) != 0 &&
                (route == btRoute || route == sStatic.mDefaultAudioVideo)) {
            try {
                sStatic.mAudioService.setBluetoothA2dpOn(route == btRoute);
            } catch (RemoteException e) {
                Log.e(TAG, "Error changing Bluetooth A2DP state", e);
            }
        }


        final WifiDisplay activeDisplay =
                sStatic.mDisplayService.getWifiDisplayStatus().getActiveDisplay();
        final boolean oldRouteHasAddress = oldRoute != null && oldRoute.mDeviceAddress != null;
        final boolean newRouteHasAddress = route != null && route.mDeviceAddress != null;
        if (activeDisplay != null || oldRouteHasAddress || newRouteHasAddress) {
            if (newRouteHasAddress && !matchesDeviceAddress(activeDisplay, route)) {
                sStatic.mDisplayService.connectWifiDisplay(route.mDeviceAddress);    //Connect
            } else if (activeDisplay != null && !newRouteHasAddress) {
                sStatic.mDisplayService.disconnectWifiDisplay();
            }
        }


        if (oldRoute != null) {
            // TODO filter types properly
            dispatchRouteUnselected(types & oldRoute.getSupportedTypes(), oldRoute);
        }
        sStatic.mSelectedRoute = route;
        if (route != null) {
            // TODO filter types properly
            dispatchRouteSelected(types & route.getSupportedTypes(), route);
        }
    }



In DisplayManager.java


public final class DisplayManager {
   public void connectWifiDisplay(String deviceAddress) {


        mGlobal.connectWifiDisplay(deviceAddress);
    }
}


In DisplayManagerGloabal.java

    public void connectWifiDisplay(String deviceAddress) {        if (deviceAddress == null) {
            throw new IllegalArgumentException("deviceAddress must not be null");
        }


        try {
            mDm.connectWifiDisplay(deviceAddress);
        } catch (RemoteException ex) {
            Log.e(TAG, "Failed to connect to Wifi display " + deviceAddress + ".", ex);
        }
    }

In DisplayManagerService.java

    public void connectWifiDisplay(String address) {        if (address == null) {
            throw new IllegalArgumentException("address must not be null");
        }


        final boolean trusted = canCallerConfigureWifiDisplay();
        final long token = Binder.clearCallingIdentity();
        try {
            synchronized (mSyncRoot) {
                if (mWifiDisplayAdapter != null) {
                    mWifiDisplayAdapter.requestConnectLocked(address, trusted);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(token);
        }
    }


Check App是否支持wifidisplay, 需要在manifest中增加android.Manifest.permission.CONFIGURE_WIFI_DISPLAY
    private boolean canCallerConfigureWifiDisplay() {


        return mContext.checkCallingPermission(android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
                == PackageManager.PERMISSION_GRANTED;
    }


In WifiDisplayAdapter.java

    public void registerLocked() {        super.registerLocked();


        updateRememberedDisplaysLocked();


        getHandler().post(new Runnable() {
            @Override
            public void run() {
                mDisplayController = new WifiDisplayController(
                        getContext(), getHandler(), mWifiDisplayListener);


                getContext().registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
                        new IntentFilter(ACTION_DISCONNECT), null, mHandler);
            }
        });
    }

    public void requestConnectLocked(final String address, final boolean trusted) {        if (DEBUG) {
            Slog.d(TAG, "requestConnectLocked: address=" + address + ", trusted=" + trusted);
        }


        if (!trusted) {
            synchronized (getSyncRoot()) {
                if (!isRememberedDisplayLocked(address)) {
                    Slog.w(TAG, "Ignoring request by an untrusted client to connect to "
                            + "an unknown wifi display: " + address);
                    return;
                }
            }
        }
   getHandler().post(new Runnable() {            @Override
            public void run() {
                if (mDisplayController != null) {
                    mDisplayController.requestConnect(address);
                }
            }
        });
    }

    private WifiDisplayController mDisplayController;


In DisplayController.java
    public void requestConnect(String address) {        for (WifiP2pDevice device : mAvailableWifiDisplayPeers) {
            if (device.deviceAddress.equals(address)) {
                connect(device);
            }
        }
    }



    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.");
            }
            return;
        }


        mDesiredDevice = device;
        mConnectionRetriesLeft = CONNECT_MAX_RETRIES;
        updateConnection();
    }

  • 最后调用到最重要的api:updateConnection,wifi display的握手就是在这里完成的
    /**     * 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 (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
            Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
                    + " from Wifi display: " + mConnectedDevice.deviceName);


            mRemoteDisplay.dispose();
            mRemoteDisplay = null;
            mRemoteDisplayInterface = null;
            mRemoteDisplayConnected = false;
            mHandler.removeCallbacks(mRtspTimeout);


            setRemoteSubmixOn(false);
            unadvertiseDisplay();


            // 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);


            unadvertiseDisplay();


            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);


            unadvertiseDisplay();
            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) {
            unadvertiseDisplay();
            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;
            // Helps with STA & P2P concurrency
            config.groupOwnerIntent = WifiP2pConfig.MIN_GROUP_OWNER_INTENT;


            WifiDisplay display = createWifiDisplay(mConnectingDevice);
            advertiseDisplay(display, null, 0, 0, 0);


            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) {
                    if (mConnectingDevice == newDevice) {
                        Slog.i(TAG, "Failed to initiate connection to Wifi display: "
                                + newDevice.deviceName + ", reason=" + reason);
                        mConnectingDevice = null;
                        handleConnectionFailure(false);
                    }
                }
            });
            return; // wait for asynchronous callback
        }


        // Step 6. Listen for incoming connections.
        if (mConnectedDevice != null && mRemoteDisplay == 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(false);
                return; // done
            }


            setRemoteSubmixOn(true);


            final WifiP2pDevice oldDevice = mConnectedDevice;     //实际的ip和port
            final int port = getPortNumber(mConnectedDevice);
            final String iface = addr.getHostAddress() + ":" + port;
            mRemoteDisplayInterface = iface;


            Slog.i(TAG, "Listening for RTSP connection on " + iface
                    + " from Wifi display: " + mConnectedDevice.deviceName);


            mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {    //重要!
                @Override
                public void onDisplayConnected(Surface surface,
                        int width, int height, int flags) {
                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
                                + mConnectedDevice.deviceName);
                        mRemoteDisplayConnected = true;
                        mHandler.removeCallbacks(mRtspTimeout);


                        final WifiDisplay display = createWifiDisplay(mConnectedDevice);
                        advertiseDisplay(display, surface, width, height, flags);
                    }
                }


                @Override
                public void onDisplayDisconnected() {
                    if (mConnectedDevice == oldDevice) {
                        Slog.i(TAG, "Closed RTSP connection with Wifi display: "
                                + mConnectedDevice.deviceName);
                        mHandler.removeCallbacks(mRtspTimeout);
                        disconnect();
                    }
                }


                @Override
                public void onDisplayError(int error) {
                    if (mConnectedDevice == oldDevice) {
                        Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
                                + error + ": " + mConnectedDevice.deviceName);
                        mHandler.removeCallbacks(mRtspTimeout);
                        handleConnectionFailure(false);
                    }
                }
            }, mHandler);


            mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
        }
    }




  • 会用到RemoteDisplay来接收消息,并callback出来,当连接成功的时候。
public final class RemoteDisplay {

    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;
    }

  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");
    }

    private native int nativeListen(String iface);


static JNINativeMethod gMethods[] = {
    {"nativeListen", "(Ljava/lang/String;)I",

            (void*)nativeListen },

    {"nativeDispose", "(I)V",

            (void*)nativeDispose },

};




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);

}






sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(        const sp<IRemoteDisplayClient>& client, const String8& iface) {

    if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {

        return NULL;

    }



    return new RemoteDisplay(client, iface.string());     //创建了一个

}



RemoteDisplay::RemoteDisplay(        const sp<IRemoteDisplayClient> &client, const char *iface)
    : mLooper(new ALooper),
      mNetSession(new ANetworkSession),
      mSource(new WifiDisplaySource(mNetSession, client)) {     //实现wdf source
    mLooper->setName("wfd_looper");
    mLooper->registerHandler(mSource);

    mNetSession->start();
    mLooper->start();

    mSource->start(iface);
}

RemoteDisplay::~RemoteDisplay() {
}

status_t RemoteDisplay::dispose() {
    mSource->stop();

    mLooper->stop();
    mNetSession->stop();

    return OK;
}



至此,wifi display source握手结束。 这里并没有提到presentation。下次再说。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值