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 17 package androidx.mediarouter.media; 18 19 import android.content.Context; 20 import android.hardware.display.DisplayManager; 21 import android.os.Build; 22 import android.os.Handler; 23 import android.util.Log; 24 import android.view.Display; 25 26 import androidx.annotation.RequiresApi; 27 28 import java.lang.reflect.Field; 29 import java.lang.reflect.InvocationTargetException; 30 import java.lang.reflect.Method; 31 32 @RequiresApi(17) 33 final class MediaRouterJellybeanMr1 { 34 private static final String TAG = "MediaRouterJellybeanMr1"; 35 36 public static Object createCallback(Callback callback) { 37 return new CallbackProxy<Callback>(callback); 38 } 39 40 public static final class RouteInfo { 41 public static boolean isEnabled(Object routeObj) { 42 return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled(); 43 } 44 45 public static Display getPresentationDisplay(Object routeObj) { 46 // android.media.MediaRouter.RouteInfo.getPresentationDisplay() was 47 // added in API 17. However, some factory releases of JB MR1 missed it. 48 try { 49 return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay(); 50 } catch (NoSuchMethodError ex) { 51 Log.w(TAG, "Cannot get presentation display for the route.", ex); 52 } 53 return null; 54 } 55 56 private RouteInfo() { 57 } 58 } 59 60 public static interface Callback extends MediaRouterJellybean.Callback { 61 public void onRoutePresentationDisplayChanged(Object routeObj); 62 } 63 64 /** 65 * Workaround the fact that the version of MediaRouter.addCallback() that accepts a 66 * flag to perform an active scan does not exist in JB MR1 so we need to force 67 * wifi display scans directly through the DisplayManager. 68 * Do not use on JB MR2 and above. 69 */ 70 public static final class ActiveScanWorkaround implements Runnable { 71 // Time between wifi display scans when actively scanning in milliseconds. 72 private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000; 73 74 private final DisplayManager mDisplayManager; 75 private final Handler mHandler; 76 private Method mScanWifiDisplaysMethod; 77 78 private boolean mActivelyScanningWifiDisplays; 79 80 public ActiveScanWorkaround(Context context, Handler handler) { 81 if (Build.VERSION.SDK_INT != 17) { 82 throw new UnsupportedOperationException(); 83 } 84 85 mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); 86 mHandler = handler; 87 try { 88 mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays"); 89 } catch (NoSuchMethodException ex) { 90 } 91 } 92 93 public void setActiveScanRouteTypes(int routeTypes) { 94 // On JB MR1, there is no API to scan wifi display routes. 95 // Instead we must make a direct call into the DisplayManager to scan 96 // wifi displays on this version but only when live video routes are requested. 97 // See also the JellybeanMr2Impl implementation of this method. 98 // This was fixed in JB MR2 by adding a new overload of addCallback() to 99 // enable active scanning on request. 100 if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) { 101 if (!mActivelyScanningWifiDisplays) { 102 if (mScanWifiDisplaysMethod != null) { 103 mActivelyScanningWifiDisplays = true; 104 mHandler.post(this); 105 } else { 106 Log.w(TAG, "Cannot scan for wifi displays because the " 107 + "DisplayManager.scanWifiDisplays() method is " 108 + "not available on this device."); 109 } 110 } 111 } else { 112 if (mActivelyScanningWifiDisplays) { 113 mActivelyScanningWifiDisplays = false; 114 mHandler.removeCallbacks(this); 115 } 116 } 117 } 118 119 @Override 120 public void run() { 121 if (mActivelyScanningWifiDisplays) { 122 try { 123 mScanWifiDisplaysMethod.invoke(mDisplayManager); 124 } catch (IllegalAccessException ex) { 125 Log.w(TAG, "Cannot scan for wifi displays.", ex); 126 } catch (InvocationTargetException ex) { 127 Log.w(TAG, "Cannot scan for wifi displays.", ex); 128 } 129 mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL); 130 } 131 } 132 } 133 134 /** 135 * Workaround the fact that the isConnecting() method does not exist in JB MR1. 136 * Do not use on JB MR2 and above. 137 */ 138 public static final class IsConnectingWorkaround { 139 private Method mGetStatusCodeMethod; 140 private int mStatusConnecting; 141 142 public IsConnectingWorkaround() { 143 if (Build.VERSION.SDK_INT != 17) { 144 throw new UnsupportedOperationException(); 145 } 146 147 try { 148 Field statusConnectingField = 149 android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING"); 150 mStatusConnecting = statusConnectingField.getInt(null); 151 mGetStatusCodeMethod = 152 android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode"); 153 } catch (NoSuchFieldException ex) { 154 } catch (NoSuchMethodException ex) { 155 } catch (IllegalAccessException ex) { 156 } 157 } 158 159 public boolean isConnecting(Object routeObj) { 160 android.media.MediaRouter.RouteInfo route = 161 (android.media.MediaRouter.RouteInfo)routeObj; 162 163 if (mGetStatusCodeMethod != null) { 164 try { 165 int statusCode = (Integer)mGetStatusCodeMethod.invoke(route); 166 return statusCode == mStatusConnecting; 167 } catch (IllegalAccessException ex) { 168 } catch (InvocationTargetException ex) { 169 } 170 } 171 172 // Assume not connecting. 173 return false; 174 } 175 } 176 177 static class CallbackProxy<T extends Callback> 178 extends MediaRouterJellybean.CallbackProxy<T> { 179 public CallbackProxy(T callback) { 180 super(callback); 181 } 182 183 @Override 184 public void onRoutePresentationDisplayChanged(android.media.MediaRouter router, 185 android.media.MediaRouter.RouteInfo route) { 186 mCallback.onRoutePresentationDisplayChanged(route); 187 } 188 } 189 190 private MediaRouterJellybeanMr1() { 191 } 192 } 193