1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.media.routing;
18
19import android.content.Context;
20import android.hardware.display.DisplayManager;
21import android.os.Build;
22import android.os.Handler;
23import android.util.Log;
24import android.view.Display;
25
26import java.lang.reflect.Field;
27import java.lang.reflect.InvocationTargetException;
28import java.lang.reflect.Method;
29
30class MediaRouterJellybeanMr1 extends MediaRouterJellybean {
31    private static final String TAG = "MediaRouterJellybeanMr1";
32
33    public static Object createCallback(Callback callback) {
34        return new CallbackProxy<Callback>(callback);
35    }
36
37    public static final class RouteInfo {
38        public static boolean isEnabled(Object routeObj) {
39            return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled();
40        }
41
42        public static Display getPresentationDisplay(Object routeObj) {
43            return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
44        }
45    }
46
47    public static interface Callback extends MediaRouterJellybean.Callback {
48        public void onRoutePresentationDisplayChanged(Object routeObj);
49    }
50
51    /**
52     * Workaround the fact that the version of MediaRouter.addCallback() that accepts a
53     * flag to perform an active scan does not exist in JB MR1 so we need to force
54     * wifi display scans directly through the DisplayManager.
55     * Do not use on JB MR2 and above.
56     */
57    public static final class ActiveScanWorkaround implements Runnable {
58        // Time between wifi display scans when actively scanning in milliseconds.
59        private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
60
61        private final DisplayManager mDisplayManager;
62        private final Handler mHandler;
63        private Method mScanWifiDisplaysMethod;
64
65        private boolean mActivelyScanningWifiDisplays;
66
67        public ActiveScanWorkaround(Context context, Handler handler) {
68            if (Build.VERSION.SDK_INT != 17) {
69                throw new UnsupportedOperationException();
70            }
71
72            mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
73            mHandler = handler;
74            try {
75                mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays");
76            } catch (NoSuchMethodException ex) {
77            }
78        }
79
80        public void setActiveScanRouteTypes(int routeTypes) {
81            // On JB MR1, there is no API to scan wifi display routes.
82            // Instead we must make a direct call into the DisplayManager to scan
83            // wifi displays on this version but only when live video routes are requested.
84            // See also the JellybeanMr2Impl implementation of this method.
85            // This was fixed in JB MR2 by adding a new overload of addCallback() to
86            // enable active scanning on request.
87            if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
88                if (!mActivelyScanningWifiDisplays) {
89                    if (mScanWifiDisplaysMethod != null) {
90                        mActivelyScanningWifiDisplays = true;
91                        mHandler.post(this);
92                    } else {
93                        Log.w(TAG, "Cannot scan for wifi displays because the "
94                                + "DisplayManager.scanWifiDisplays() method is "
95                                + "not available on this device.");
96                    }
97                }
98            } else {
99                if (mActivelyScanningWifiDisplays) {
100                    mActivelyScanningWifiDisplays = false;
101                    mHandler.removeCallbacks(this);
102                }
103            }
104        }
105
106        @Override
107        public void run() {
108            if (mActivelyScanningWifiDisplays) {
109                try {
110                    mScanWifiDisplaysMethod.invoke(mDisplayManager);
111                } catch (IllegalAccessException ex) {
112                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
113                } catch (InvocationTargetException ex) {
114                    Log.w(TAG, "Cannot scan for wifi displays.", ex);
115                }
116                mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
117            }
118        }
119    }
120
121    /**
122     * Workaround the fact that the isConnecting() method does not exist in JB MR1.
123     * Do not use on JB MR2 and above.
124     */
125    public static final class IsConnectingWorkaround {
126        private Method mGetStatusCodeMethod;
127        private int mStatusConnecting;
128
129        public IsConnectingWorkaround() {
130            if (Build.VERSION.SDK_INT != 17) {
131                throw new UnsupportedOperationException();
132            }
133
134            try {
135                Field statusConnectingField =
136                        android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING");
137                mStatusConnecting = statusConnectingField.getInt(null);
138                mGetStatusCodeMethod =
139                        android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode");
140            } catch (NoSuchFieldException ex) {
141            } catch (NoSuchMethodException ex) {
142            } catch (IllegalAccessException ex) {
143            }
144        }
145
146        public boolean isConnecting(Object routeObj) {
147            android.media.MediaRouter.RouteInfo route =
148                    (android.media.MediaRouter.RouteInfo)routeObj;
149
150            if (mGetStatusCodeMethod != null) {
151                try {
152                    int statusCode = (Integer)mGetStatusCodeMethod.invoke(route);
153                    return statusCode == mStatusConnecting;
154                } catch (IllegalAccessException ex) {
155                } catch (InvocationTargetException ex) {
156                }
157            }
158
159            // Assume not connecting.
160            return false;
161        }
162    }
163
164    static class CallbackProxy<T extends Callback>
165            extends MediaRouterJellybean.CallbackProxy<T> {
166        public CallbackProxy(T callback) {
167            super(callback);
168        }
169
170        @Override
171        public void onRoutePresentationDisplayChanged(android.media.MediaRouter router,
172                android.media.MediaRouter.RouteInfo route) {
173            mCallback.onRoutePresentationDisplayChanged(route);
174        }
175    }
176}
177