1 // Copyright 2020 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base; 6 7 import android.Manifest; 8 import android.content.Context; 9 import android.content.pm.PackageManager; 10 import android.net.ConnectivityManager; 11 import android.net.Network; 12 import android.net.NetworkCapabilities; 13 import android.os.Build; 14 import android.os.Process; 15 import android.telephony.SignalStrength; 16 import android.telephony.TelephonyManager; 17 18 import androidx.annotation.RequiresApi; 19 20 import org.jni_zero.CalledByNative; 21 import org.jni_zero.JNINamespace; 22 23 import org.chromium.base.compat.ApiHelperForM; 24 import org.chromium.base.compat.ApiHelperForP; 25 26 /** Exposes radio related information about the current device. */ 27 @JNINamespace("base::android") 28 public class RadioUtils { 29 // Cached value indicating if app has ACCESS_NETWORK_STATE permission. 30 private static Boolean sHaveAccessNetworkState; 31 // Cached value indicating if app has ACCESS_WIFI_STATE permission. 32 private static Boolean sHaveAccessWifiState; 33 RadioUtils()34 private RadioUtils() {} 35 36 /** 37 * Return whether the current SDK supports necessary functions and the app 38 * has necessary permissions. 39 * @return True or false. 40 */ 41 @CalledByNative isSupported()42 private static boolean isSupported() { 43 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P 44 && haveAccessNetworkState() 45 && haveAccessWifiState(); 46 } 47 haveAccessNetworkState()48 private static boolean haveAccessNetworkState() { 49 // This could be racy if called on multiple threads, but races will 50 // end in the same result so it's not a problem. 51 if (sHaveAccessNetworkState == null) { 52 sHaveAccessNetworkState = 53 ApiCompatibilityUtils.checkPermission( 54 ContextUtils.getApplicationContext(), 55 Manifest.permission.ACCESS_NETWORK_STATE, 56 Process.myPid(), 57 Process.myUid()) 58 == PackageManager.PERMISSION_GRANTED; 59 } 60 return sHaveAccessNetworkState; 61 } 62 haveAccessWifiState()63 private static boolean haveAccessWifiState() { 64 // This could be racy if called on multiple threads, but races will 65 // end in the same result so it's not a problem. 66 if (sHaveAccessWifiState == null) { 67 sHaveAccessWifiState = 68 ApiCompatibilityUtils.checkPermission( 69 ContextUtils.getApplicationContext(), 70 Manifest.permission.ACCESS_WIFI_STATE, 71 Process.myPid(), 72 Process.myUid()) 73 == PackageManager.PERMISSION_GRANTED; 74 } 75 return sHaveAccessWifiState; 76 } 77 78 /** 79 * Return whether the device is currently connected to a wifi network. 80 * @return True or false. 81 */ 82 @CalledByNative 83 @RequiresApi(Build.VERSION_CODES.P) 84 @SuppressWarnings("AssertionSideEffect") // isSupported() caches via sHaveAccessNetworkState. isWifiConnected()85 private static boolean isWifiConnected() { 86 assert isSupported(); 87 try (TraceEvent te = TraceEvent.scoped("RadioUtils::isWifiConnected")) { 88 ConnectivityManager connectivityManager = 89 (ConnectivityManager) 90 ContextUtils.getApplicationContext() 91 .getSystemService(Context.CONNECTIVITY_SERVICE); 92 Network network = ApiHelperForM.getActiveNetwork(connectivityManager); 93 if (network == null) { 94 return false; 95 } 96 NetworkCapabilities networkCapabilities = 97 connectivityManager.getNetworkCapabilities(network); 98 if (networkCapabilities == null) { 99 return false; 100 } 101 return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); 102 } 103 } 104 105 /** 106 * Return current cell signal level. 107 * @return Signal level from 0 (no signal) to 4 (good signal) or -1 in case of error. 108 */ 109 @CalledByNative 110 @RequiresApi(Build.VERSION_CODES.P) 111 @SuppressWarnings("AssertionSideEffect") // isSupported() caches via sHaveAccessNetworkState. getCellSignalLevel()112 private static int getCellSignalLevel() { 113 assert isSupported(); 114 try (TraceEvent te = TraceEvent.scoped("RadioUtils::getCellSignalLevel")) { 115 TelephonyManager telephonyManager = 116 (TelephonyManager) 117 ContextUtils.getApplicationContext() 118 .getSystemService(Context.TELEPHONY_SERVICE); 119 int level = -1; 120 try { 121 SignalStrength signalStrength = ApiHelperForP.getSignalStrength(telephonyManager); 122 if (signalStrength != null) { 123 level = signalStrength.getLevel(); 124 } 125 } catch (java.lang.SecurityException e) { 126 // Sometimes SignalStrength.getLevel() requires extra permissions 127 // that Chrome doesn't have. See crbug.com/1150536. 128 } 129 return level; 130 } 131 } 132 133 /** 134 * Return current cell data activity. 135 * @return 0 - none, 1 - in, 2 - out, 3 - in/out, 4 - dormant, or -1 in case of error. 136 */ 137 @CalledByNative 138 @RequiresApi(Build.VERSION_CODES.P) 139 @SuppressWarnings("AssertionSideEffect") // isSupported() caches via sHaveAccessNetworkState. getCellDataActivity()140 private static int getCellDataActivity() { 141 assert isSupported(); 142 try (TraceEvent te = TraceEvent.scoped("RadioUtils::getCellDataActivity")) { 143 TelephonyManager telephonyManager = 144 (TelephonyManager) 145 ContextUtils.getApplicationContext() 146 .getSystemService(Context.TELEPHONY_SERVICE); 147 try { 148 return telephonyManager.getDataActivity(); 149 } catch (java.lang.SecurityException e) { 150 // Just in case getDataActivity() requires extra permissions. 151 return -1; 152 } 153 } 154 } 155 } 156