| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.base; |
| |
| import android.Manifest; |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.net.ConnectivityManager; |
| import android.net.Network; |
| import android.net.NetworkCapabilities; |
| import android.os.Build; |
| import android.os.Process; |
| import android.telephony.SignalStrength; |
| import android.telephony.TelephonyManager; |
| |
| import androidx.annotation.RequiresApi; |
| |
| import org.jni_zero.CalledByNative; |
| import org.jni_zero.JNINamespace; |
| |
| import org.chromium.base.compat.ApiHelperForM; |
| import org.chromium.base.compat.ApiHelperForP; |
| |
| /** Exposes radio related information about the current device. */ |
| @JNINamespace("base::android") |
| public class RadioUtils { |
| // Cached value indicating if app has ACCESS_NETWORK_STATE permission. |
| private static Boolean sHaveAccessNetworkState; |
| // Cached value indicating if app has ACCESS_WIFI_STATE permission. |
| private static Boolean sHaveAccessWifiState; |
| |
| private RadioUtils() {} |
| |
| /** |
| * Return whether the current SDK supports necessary functions and the app |
| * has necessary permissions. |
| * @return True or false. |
| */ |
| @CalledByNative |
| private static boolean isSupported() { |
| return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P |
| && haveAccessNetworkState() |
| && haveAccessWifiState(); |
| } |
| |
| private static boolean haveAccessNetworkState() { |
| // This could be racy if called on multiple threads, but races will |
| // end in the same result so it's not a problem. |
| if (sHaveAccessNetworkState == null) { |
| sHaveAccessNetworkState = |
| ApiCompatibilityUtils.checkPermission( |
| ContextUtils.getApplicationContext(), |
| Manifest.permission.ACCESS_NETWORK_STATE, |
| Process.myPid(), |
| Process.myUid()) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| return sHaveAccessNetworkState; |
| } |
| |
| private static boolean haveAccessWifiState() { |
| // This could be racy if called on multiple threads, but races will |
| // end in the same result so it's not a problem. |
| if (sHaveAccessWifiState == null) { |
| sHaveAccessWifiState = |
| ApiCompatibilityUtils.checkPermission( |
| ContextUtils.getApplicationContext(), |
| Manifest.permission.ACCESS_WIFI_STATE, |
| Process.myPid(), |
| Process.myUid()) |
| == PackageManager.PERMISSION_GRANTED; |
| } |
| return sHaveAccessWifiState; |
| } |
| |
| /** |
| * Return whether the device is currently connected to a wifi network. |
| * @return True or false. |
| */ |
| @CalledByNative |
| @RequiresApi(Build.VERSION_CODES.P) |
| @SuppressWarnings("AssertionSideEffect") // isSupported() caches via sHaveAccessNetworkState. |
| private static boolean isWifiConnected() { |
| assert isSupported(); |
| try (TraceEvent te = TraceEvent.scoped("RadioUtils::isWifiConnected")) { |
| ConnectivityManager connectivityManager = |
| (ConnectivityManager) |
| ContextUtils.getApplicationContext() |
| .getSystemService(Context.CONNECTIVITY_SERVICE); |
| Network network = ApiHelperForM.getActiveNetwork(connectivityManager); |
| if (network == null) { |
| return false; |
| } |
| NetworkCapabilities networkCapabilities = |
| connectivityManager.getNetworkCapabilities(network); |
| if (networkCapabilities == null) { |
| return false; |
| } |
| return networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI); |
| } |
| } |
| |
| /** |
| * Return current cell signal level. |
| * @return Signal level from 0 (no signal) to 4 (good signal) or -1 in case of error. |
| */ |
| @CalledByNative |
| @RequiresApi(Build.VERSION_CODES.P) |
| @SuppressWarnings("AssertionSideEffect") // isSupported() caches via sHaveAccessNetworkState. |
| private static int getCellSignalLevel() { |
| assert isSupported(); |
| try (TraceEvent te = TraceEvent.scoped("RadioUtils::getCellSignalLevel")) { |
| TelephonyManager telephonyManager = |
| (TelephonyManager) |
| ContextUtils.getApplicationContext() |
| .getSystemService(Context.TELEPHONY_SERVICE); |
| int level = -1; |
| try { |
| SignalStrength signalStrength = ApiHelperForP.getSignalStrength(telephonyManager); |
| if (signalStrength != null) { |
| level = signalStrength.getLevel(); |
| } |
| } catch (java.lang.SecurityException e) { |
| // Sometimes SignalStrength.getLevel() requires extra permissions |
| // that Chrome doesn't have. See crbug.com/1150536. |
| } |
| return level; |
| } |
| } |
| |
| /** |
| * Return current cell data activity. |
| * @return 0 - none, 1 - in, 2 - out, 3 - in/out, 4 - dormant, or -1 in case of error. |
| */ |
| @CalledByNative |
| @RequiresApi(Build.VERSION_CODES.P) |
| @SuppressWarnings("AssertionSideEffect") // isSupported() caches via sHaveAccessNetworkState. |
| private static int getCellDataActivity() { |
| assert isSupported(); |
| try (TraceEvent te = TraceEvent.scoped("RadioUtils::getCellDataActivity")) { |
| TelephonyManager telephonyManager = |
| (TelephonyManager) |
| ContextUtils.getApplicationContext() |
| .getSystemService(Context.TELEPHONY_SERVICE); |
| try { |
| return telephonyManager.getDataActivity(); |
| } catch (java.lang.SecurityException e) { |
| // Just in case getDataActivity() requires extra permissions. |
| return -1; |
| } |
| } |
| } |
| } |