diff options
| -rwxr-xr-x | api/current.txt | 10 | ||||
| -rw-r--r-- | api/removed.txt | 2 | ||||
| -rw-r--r-- | api/system-current.txt | 2 | ||||
| -rw-r--r-- | services/core/java/com/android/server/TelephonyRegistry.java | 113 | ||||
| -rw-r--r-- | telephony/java/android/telephony/LocationAccessPolicy.java | 294 | ||||
| -rw-r--r-- | telephony/java/android/telephony/NetworkRegistrationState.java | 20 | ||||
| -rw-r--r-- | telephony/java/android/telephony/ServiceState.java | 26 | ||||
| -rw-r--r-- | telephony/java/android/telephony/TelephonyManager.java | 45 | ||||
| -rw-r--r-- | telephony/java/android/telephony/TelephonyScanManager.java | 13 | ||||
| -rw-r--r-- | telephony/java/com/android/internal/telephony/ITelephony.aidl | 5 | 
10 files changed, 423 insertions, 107 deletions
diff --git a/api/current.txt b/api/current.txt index 95350955f163..e8512486c7fc 100755 --- a/api/current.txt +++ b/api/current.txt @@ -43018,12 +43018,12 @@ package android.telephony {      method public boolean canChangeDtmfToneLength();      method @Nullable public android.telephony.TelephonyManager createForPhoneAccountHandle(android.telecom.PhoneAccountHandle);      method public android.telephony.TelephonyManager createForSubscriptionId(int); -    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo(); +    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public java.util.List<android.telephony.CellInfo> getAllCellInfo();      method public int getCallState();      method public int getCardIdForDefaultEuicc();      method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @WorkerThread public android.os.PersistableBundle getCarrierConfig();      method public int getCarrierIdFromSimMccMnc(); -    method @Deprecated @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.CellLocation getCellLocation(); +    method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public android.telephony.CellLocation getCellLocation();      method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList();      method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) @Nullable public java.util.Map<java.lang.Integer,java.util.List<android.telephony.emergency.EmergencyNumber>> getCurrentEmergencyNumberList(int);      method public int getDataActivity(); @@ -43053,7 +43053,7 @@ package android.telephony {      method public int getPhoneCount();      method public int getPhoneType();      method @RequiresPermission(anyOf={"android.permission.READ_PRIVILEGED_PHONE_STATE", android.Manifest.permission.READ_PHONE_STATE}) public int getPreferredOpportunisticDataSubscription(); -    method @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public android.telephony.ServiceState getServiceState(); +    method @RequiresPermission(allOf={android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.ACCESS_COARSE_LOCATION}) public android.telephony.ServiceState getServiceState();      method @Nullable public android.telephony.SignalStrength getSignalStrength();      method public int getSimCarrierId();      method @Nullable public CharSequence getSimCarrierIdName(); @@ -43095,8 +43095,8 @@ package android.telephony {      method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);      method public boolean isWorldPhone();      method public void listen(android.telephony.PhoneStateListener, int); -    method @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); -    method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback); +    method @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION) public void requestCellInfoUpdate(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); +    method @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, java.util.concurrent.Executor, android.telephony.TelephonyScanManager.NetworkScanCallback);      method public void sendDialerSpecialCode(String);      method public String sendEnvelopeWithStatus(String);      method @RequiresPermission(android.Manifest.permission.CALL_PHONE) public void sendUssdRequest(String, android.telephony.TelephonyManager.UssdResponseCallback, android.os.Handler); diff --git a/api/removed.txt b/api/removed.txt index 05d52d4e40c0..72202ad9712a 100644 --- a/api/removed.txt +++ b/api/removed.txt @@ -540,7 +540,7 @@ package android.telephony {    public class TelephonyManager {      method @Deprecated @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) public java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo(); -    method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback); +    method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION}) public android.telephony.NetworkScan requestNetworkScan(android.telephony.NetworkScanRequest, android.telephony.TelephonyScanManager.NetworkScanCallback);    }  } diff --git a/api/system-current.txt b/api/system-current.txt index fc24e7798b9a..33cc4162ca54 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6377,7 +6377,7 @@ package android.telephony {      method @Deprecated @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public boolean isVisualVoicemailEnabled(android.telecom.PhoneAccountHandle);      method public boolean needsOtaServiceProvisioning();      method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean rebootRadio(); -    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_COARSE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback); +    method @RequiresPermission(allOf={android.Manifest.permission.ACCESS_FINE_LOCATION, android.Manifest.permission.MODIFY_PHONE_STATE}) public void requestCellInfoUpdate(@NonNull android.os.WorkSource, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.TelephonyManager.CellInfoCallback);      method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public void requestNumberVerification(@NonNull android.telephony.PhoneNumberRange, long, @NonNull java.util.concurrent.Executor, @NonNull android.telephony.NumberVerificationCallback);      method @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public boolean resetRadioConfig();      method @Deprecated @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) public int setAllowedCarriers(int, java.util.List<android.service.carrier.CarrierIdentifier>); diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java index 43af36f86f3d..3b5c9f53d9a1 100644 --- a/services/core/java/com/android/server/TelephonyRegistry.java +++ b/services/core/java/com/android/server/TelephonyRegistry.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager;  import android.net.LinkProperties;  import android.net.NetworkCapabilities;  import android.os.Binder; +import android.os.Build;  import android.os.Bundle;  import android.os.Handler;  import android.os.IBinder; @@ -246,7 +247,10 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {      private PreciseDataConnectionState mPreciseDataConnectionState =                  new PreciseDataConnectionState(); -    static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = +    // Nothing here yet, but putting it here in case we want to add more in the future. +    static final int ENFORCE_COARSE_LOCATION_PERMISSION_MASK = 0; + +    static final int ENFORCE_FINE_LOCATION_PERMISSION_MASK =              PhoneStateListener.LISTEN_CELL_LOCATION                      | PhoneStateListener.LISTEN_CELL_INFO; @@ -637,8 +641,14 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                      if ((events & PhoneStateListener.LISTEN_SERVICE_STATE) != 0) {                          try {                              if (VDBG) log("listen: call onSSC state=" + mServiceState[phoneId]); -                            r.callback.onServiceStateChanged( -                                    new ServiceState(mServiceState[phoneId])); +                            ServiceState rawSs = new ServiceState(mServiceState[phoneId]); +                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { +                                r.callback.onServiceStateChanged(rawSs); +                            } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { +                                r.callback.onServiceStateChanged(rawSs.sanitizeLocationInfo(false)); +                            } else { +                                r.callback.onServiceStateChanged(rawSs.sanitizeLocationInfo(true)); +                            }                          } catch (RemoteException ex) {                              remove(r.binder);                          } @@ -673,7 +683,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                          try {                              if (DBG_LOC) log("listen: mCellLocation = "                                      + mCellLocation[phoneId]); -                            if (checkLocationAccess(r)) { +                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {                                  r.callback.onCellLocationChanged(                                          new Bundle(mCellLocation[phoneId]));                              } @@ -722,7 +732,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                          try {                              if (DBG_LOC) log("listen: mCellInfo[" + phoneId + "] = "                                      + mCellInfo.get(phoneId)); -                            if (checkLocationAccess(r)) { +                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {                                  r.callback.onCellInfoChanged(mCellInfo.get(phoneId));                              }                          } catch (RemoteException ex) { @@ -1009,13 +1019,22 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                      }                      if (r.matchPhoneStateListenerEvent(PhoneStateListener.LISTEN_SERVICE_STATE) &&                              idMatch(r.subId, subId, phoneId)) { +                          try { +                            ServiceState stateToSend; +                            if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) { +                                stateToSend = new ServiceState(state); +                            } else if (checkCoarseLocationAccess(r, Build.VERSION_CODES.Q)) { +                                stateToSend = state.sanitizeLocationInfo(false); +                            } else { +                                stateToSend = state.sanitizeLocationInfo(true); +                            }                              if (DBG) {                                  log("notifyServiceStateForSubscriber: callback.onSSC r=" + r                                          + " subId=" + subId + " phoneId=" + phoneId                                          + " state=" + state);                              } -                            r.callback.onServiceStateChanged(new ServiceState(state)); +                            r.callback.onServiceStateChanged(stateToSend);                          } catch (RemoteException ex) {                              mRemoveList.add(r.binder);                          } @@ -1198,7 +1217,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                  for (Record r : mRecords) {                      if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_INFO) &&                              idMatch(r.subId, subId, phoneId) && -                            checkLocationAccess(r)) { +                            checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {                          try {                              if (DBG_LOC) {                                  log("notifyCellInfo: mCellInfo=" + cellInfo + " r=" + r); @@ -1500,7 +1519,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                  for (Record r : mRecords) {                      if (validateEventsAndUserLocked(r, PhoneStateListener.LISTEN_CELL_LOCATION) &&                              idMatch(r.subId, subId, phoneId) && -                            checkLocationAccess(r)) { +                            checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {                          try {                              if (DBG_LOC) {                                  log("notifyCellLocation: cellLocation=" + cellLocation @@ -2109,12 +2128,35 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {      private boolean checkListenerPermission(              int events, int subId, String callingPackage, String message) { +        LocationAccessPolicy.LocationPermissionQuery.Builder locationQueryBuilder = +                new LocationAccessPolicy.LocationPermissionQuery.Builder() +                .setCallingPackage(callingPackage) +                .setMethod(message + " events: " + events) +                .setCallingPid(Binder.getCallingPid()) +                .setCallingUid(Binder.getCallingUid()); + +        boolean shouldCheckLocationPermissions = false;          if ((events & ENFORCE_COARSE_LOCATION_PERMISSION_MASK) != 0) { -            mContext.enforceCallingOrSelfPermission( -                    android.Manifest.permission.ACCESS_COARSE_LOCATION, null); -            if (mAppOps.noteOp(AppOpsManager.OP_COARSE_LOCATION, Binder.getCallingUid(), -                    callingPackage) != AppOpsManager.MODE_ALLOWED) { -                return false; +            locationQueryBuilder.setMinSdkVersionForCoarse(0); +            shouldCheckLocationPermissions = true; +        } + +        if ((events & ENFORCE_FINE_LOCATION_PERMISSION_MASK) != 0) { +            // Everything that requires fine location started in Q. So far... +            locationQueryBuilder.setMinSdkVersionForFine(Build.VERSION_CODES.Q); +            shouldCheckLocationPermissions = true; +        } + +        if (shouldCheckLocationPermissions) { +            LocationAccessPolicy.LocationPermissionResult result = +                    LocationAccessPolicy.checkLocationPermission( +                            mContext, locationQueryBuilder.build()); +            switch (result) { +                case DENIED_HARD: +                    throw new SecurityException("Unable to listen for events " + events + " due to " +                            + "insufficient location permissions."); +                case DENIED_SOFT: +                    return false;              }          } @@ -2229,15 +2271,38 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {          }      } -    private boolean checkLocationAccess(Record r) { -        long token = Binder.clearCallingIdentity(); -        try { -            return LocationAccessPolicy.canAccessCellLocation(mContext, -                    r.callingPackage, r.callerUid, r.callerPid, -                    /*throwOnDeniedPermission*/ false); -        } finally { -            Binder.restoreCallingIdentity(token); -        } +    private boolean checkFineLocationAccess(Record r, int minSdk) { +        LocationAccessPolicy.LocationPermissionQuery query = +                new LocationAccessPolicy.LocationPermissionQuery.Builder() +                        .setCallingPackage(r.callingPackage) +                        .setCallingPid(r.callerPid) +                        .setCallingUid(r.callerUid) +                        .setMethod("TelephonyRegistry push") +                        .setMinSdkVersionForFine(minSdk) +                        .build(); + +        return Binder.withCleanCallingIdentity(() -> { +            LocationAccessPolicy.LocationPermissionResult locationResult = +                    LocationAccessPolicy.checkLocationPermission(mContext, query); +            return locationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED; +        }); +    } + +    private boolean checkCoarseLocationAccess(Record r, int minSdk) { +        LocationAccessPolicy.LocationPermissionQuery query = +                new LocationAccessPolicy.LocationPermissionQuery.Builder() +                        .setCallingPackage(r.callingPackage) +                        .setCallingPid(r.callerPid) +                        .setCallingUid(r.callerUid) +                        .setMethod("TelephonyRegistry push") +                        .setMinSdkVersionForCoarse(minSdk) +                        .build(); + +        return Binder.withCleanCallingIdentity(() -> { +            LocationAccessPolicy.LocationPermissionResult locationResult = +                    LocationAccessPolicy.checkLocationPermission(mContext, query); +            return locationResult == LocationAccessPolicy.LocationPermissionResult.ALLOWED; +        });      }      private void checkPossibleMissNotify(Record r, int phoneId) { @@ -2287,7 +2352,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {                      log("checkPossibleMissNotify: onCellInfoChanged[" + phoneId + "] = "                              + mCellInfo.get(phoneId));                  } -                if (checkLocationAccess(r)) { +                if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {                      r.callback.onCellInfoChanged(mCellInfo.get(phoneId));                  }              } catch (RemoteException ex) { @@ -2337,7 +2402,7 @@ public class TelephonyRegistry extends ITelephonyRegistry.Stub {              try {                  if (DBG_LOC) log("checkPossibleMissNotify: onCellLocationChanged mCellLocation = "                          + mCellLocation[phoneId]); -                if (checkLocationAccess(r)) { +                if (checkFineLocationAccess(r, Build.VERSION_CODES.Q)) {                      r.callback.onCellLocationChanged(new Bundle(mCellLocation[phoneId]));                  }              } catch (RemoteException ex) { diff --git a/telephony/java/android/telephony/LocationAccessPolicy.java b/telephony/java/android/telephony/LocationAccessPolicy.java index 53d69f447a56..24db438580c9 100644 --- a/telephony/java/android/telephony/LocationAccessPolicy.java +++ b/telephony/java/android/telephony/LocationAccessPolicy.java @@ -26,11 +26,12 @@ import android.content.pm.PackageManager;  import android.content.pm.UserInfo;  import android.location.LocationManager;  import android.os.Binder; +import android.os.Build;  import android.os.Process; -import android.os.Trace;  import android.os.UserHandle;  import android.os.UserManager;  import android.util.Log; +import android.widget.Toast;  import java.util.List; @@ -41,61 +42,236 @@ import java.util.List;  public final class LocationAccessPolicy {      private static final String TAG = "LocationAccessPolicy";      private static final boolean DBG = false; +    public static final int MAX_SDK_FOR_ANY_ENFORCEMENT = Build.VERSION_CODES.P; -    /** -     * API to determine if the caller has permissions to get cell location. -     * -     * @param pkgName Package name of the application requesting access -     * @param uid The uid of the package -     * @param pid The pid of the package -     * @param throwOnDeniedPermission Whether to throw if the location permission is denied. -     * @return boolean true or false if permissions is granted -     */ -    public static boolean canAccessCellLocation(@NonNull Context context, @NonNull String pkgName, -            int uid, int pid, boolean throwOnDeniedPermission) throws SecurityException { -        Trace.beginSection("TelephonyLocationCheck"); -        try { -            // Always allow the phone process and system server to access location. This avoid -            // breaking legacy code that rely on public-facing APIs to access cell location, and -            // it doesn't create an info leak risk because the cell location is stored in the phone -            // process anyway, and the system server already has location access. -            if (uid == Process.PHONE_UID || uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) { -                return true; +    public enum LocationPermissionResult { +        ALLOWED, +        /** +         * Indicates that the denial is due to a transient device state +         * (e.g. app-ops, location master switch) +         */ +        DENIED_SOFT, +        /** +         * Indicates that the denial is due to a misconfigured app (e.g. missing entry in manifest) +         */ +        DENIED_HARD, +    } + +    public static class LocationPermissionQuery { +        public final String callingPackage; +        public final int callingUid; +        public final int callingPid; +        public final int minSdkVersionForCoarse; +        public final int minSdkVersionForFine; +        public final String method; + +        private LocationPermissionQuery(String callingPackage, int callingUid, int callingPid, +                int minSdkVersionForCoarse, int minSdkVersionForFine, String method) { +            this.callingPackage = callingPackage; +            this.callingUid = callingUid; +            this.callingPid = callingPid; +            this.minSdkVersionForCoarse = minSdkVersionForCoarse; +            this.minSdkVersionForFine = minSdkVersionForFine; +            this.method = method; +        } + +        public static class Builder { +            private String mCallingPackage; +            private int mCallingUid; +            private int mCallingPid; +            private int mMinSdkVersionForCoarse = Integer.MAX_VALUE; +            private int mMinSdkVersionForFine = Integer.MAX_VALUE; +            private String mMethod; + +            /** +             * Mandatory parameter, used for performing permission checks. +             */ +            public Builder setCallingPackage(String callingPackage) { +                mCallingPackage = callingPackage; +                return this;              } -            // We always require the location permission and also require the -            // location mode to be on for non-legacy apps. Legacy apps are -            // required to be in the foreground to at least mitigate the case -            // where a legacy app the user is not using tracks their location. -            // Granting ACCESS_FINE_LOCATION to an app automatically grants it -            // ACCESS_COARSE_LOCATION. -            if (throwOnDeniedPermission) { -                context.enforcePermission(Manifest.permission.ACCESS_COARSE_LOCATION, -                        pid, uid, "canAccessCellLocation"); -            } else if (context.checkPermission(Manifest.permission.ACCESS_COARSE_LOCATION, -                    pid, uid) == PackageManager.PERMISSION_DENIED) { -                if (DBG) Log.w(TAG, "Permission checked failed (" + pid + "," + uid + ")"); -                return false; +            /** +             * Mandatory parameter, used for performing permission checks. +             */ +            public Builder setCallingUid(int callingUid) { +                mCallingUid = callingUid; +                return this;              } -            final int opCode = AppOpsManager.permissionToOpCode( -                    Manifest.permission.ACCESS_COARSE_LOCATION); -            if (opCode != AppOpsManager.OP_NONE && context.getSystemService(AppOpsManager.class) -                    .noteOpNoThrow(opCode, uid, pkgName) != AppOpsManager.MODE_ALLOWED) { -                if (DBG) Log.w(TAG, "AppOp check failed (" + uid + "," + pkgName + ")"); -                return false; + +            /** +             * Mandatory parameter, used for performing permission checks. +             */ +            public Builder setCallingPid(int callingPid) { +                mCallingPid = callingPid; +                return this;              } -            if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { -                if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); -                return false; + +            /** +             * Apps that target at least this sdk version will be checked for coarse location +             * permission. Defaults to INT_MAX (which means don't check) +             */ +            public Builder setMinSdkVersionForCoarse( +                    int minSdkVersionForCoarse) { +                mMinSdkVersionForCoarse = minSdkVersionForCoarse; +                return this;              } -            // If the user or profile is current, permission is granted. -            // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. -            return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context); -        } finally { -            Trace.endSection(); + +            /** +             * Apps that target at least this sdk version will be checked for fine location +             * permission. Defaults to INT_MAX (which means don't check) +             */ +            public Builder setMinSdkVersionForFine( +                    int minSdkVersionForFine) { +                mMinSdkVersionForFine = minSdkVersionForFine; +                return this; +            } + +            /** +             * Optional, for logging purposes only. +             */ +            public Builder setMethod(String method) { +                mMethod = method; +                return this; +            } + +            public LocationPermissionQuery build() { +                return new LocationPermissionQuery(mCallingPackage, mCallingUid, +                        mCallingPid, mMinSdkVersionForCoarse, mMinSdkVersionForFine, mMethod); +            } +        } +    } + +    private static void logError(Context context, String errorMsg) { +        Log.e(TAG, errorMsg); +        try { +            if (Build.IS_DEBUGGABLE) { +                Toast.makeText(context, errorMsg, Toast.LENGTH_SHORT).show(); +            } +        } catch (Throwable t) { +            // whatever, not important +        } +    } + +    private static LocationPermissionResult appOpsModeToPermissionResult(int appOpsMode) { +        switch (appOpsMode) { +            case AppOpsManager.MODE_ALLOWED: +                return LocationPermissionResult.ALLOWED; +            case AppOpsManager.MODE_ERRORED: +                return LocationPermissionResult.DENIED_HARD; +            default: +                return LocationPermissionResult.DENIED_SOFT;          }      } +    private static LocationPermissionResult checkAppLocationPermissionHelper(Context context, +            LocationPermissionQuery query, String permissionToCheck) { +        String locationTypeForLog = +                Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) +                        ? "fine" : "coarse"; + +        // Do the app-ops and the manifest check without any of the allow-overrides first. +        boolean hasManifestPermission = checkManifestPermission(context, query.callingPid, +                query.callingUid, permissionToCheck); + +        int appOpMode = context.getSystemService(AppOpsManager.class) +                .noteOpNoThrow(AppOpsManager.permissionToOpCode(permissionToCheck), +                        query.callingUid, query.callingPackage); + +        if (hasManifestPermission && appOpMode == AppOpsManager.MODE_ALLOWED) { +            // If the app did everything right, return without logging. +            return LocationPermissionResult.ALLOWED; +        } + +        // If the app has the manifest permission but not the app-op permission, it means that +        // it's aware of the requirement and the user denied permission explicitly. If we see +        // this, don't let any of the overrides happen. +        if (hasManifestPermission) { +            Log.i(TAG, query.callingPackage + " is aware of " + locationTypeForLog + " but the" +                    + " app-ops permission is specifically denied."); +            return appOpsModeToPermissionResult(appOpMode); +        } + +        int minSdkVersion = Manifest.permission.ACCESS_FINE_LOCATION.equals(permissionToCheck) +                ? query.minSdkVersionForFine : query.minSdkVersionForCoarse; + +        // If the app fails for some reason, see if it should be allowed to proceed. +        if (minSdkVersion > MAX_SDK_FOR_ANY_ENFORCEMENT) { +            String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog +                    + " because we're not enforcing API " + query.minSdkVersionForFine + " yet." +                    + " Please fix this app because it will break in the future. Called from " +                    + query.method; +            logError(context, errorMsg); +            return null; +        } else if (!isAppAtLeastSdkVersion(context, query.callingPackage, minSdkVersion)) { +            String errorMsg = "Allowing " + query.callingPackage + " " + locationTypeForLog +                    + " because it doesn't target API " + query.minSdkVersionForFine + " yet." +                    + " Please fix this app. Called from " + query.method; +            logError(context, errorMsg); +            return null; +        } else { +            // If we're not allowing it due to the above two conditions, this means that the app +            // did not declare the permission in their manifest. +            return LocationPermissionResult.DENIED_HARD; +        } +    } + +    public static LocationPermissionResult checkLocationPermission( +            Context context, LocationPermissionQuery query) { +        // Always allow the phone process and system server to access location. This avoid +        // breaking legacy code that rely on public-facing APIs to access cell location, and +        // it doesn't create an info leak risk because the cell location is stored in the phone +        // process anyway, and the system server already has location access. +        if (query.callingUid == Process.PHONE_UID || query.callingUid == Process.SYSTEM_UID +                || query.callingUid == Process.ROOT_UID) { +            return LocationPermissionResult.ALLOWED; +        } + +        // Check the system-wide requirements. If the location master switch is off or +        // the app's profile isn't in foreground, return a soft denial. +        if (!checkSystemLocationAccess(context, query.callingUid, query.callingPid)) { +            return LocationPermissionResult.DENIED_SOFT; +        } + +        // Do the check for fine, then for coarse. +        if (query.minSdkVersionForFine < Integer.MAX_VALUE) { +            LocationPermissionResult resultForFine = checkAppLocationPermissionHelper( +                    context, query, Manifest.permission.ACCESS_FINE_LOCATION); +            if (resultForFine != null) { +                return resultForFine; +            } +        } + +        if (query.minSdkVersionForCoarse < Integer.MAX_VALUE) { +            LocationPermissionResult resultForCoarse = checkAppLocationPermissionHelper( +                    context, query, Manifest.permission.ACCESS_COARSE_LOCATION); +            if (resultForCoarse != null) { +                return resultForCoarse; +            } +        } + +        // At this point, we're out of location checks to do. If the app bypassed all the previous +        // ones due to the SDK grandfathering schemes, allow it access. +        return LocationPermissionResult.ALLOWED; +    } + + +    private static boolean checkManifestPermission(Context context, int pid, int uid, +            String permissionToCheck) { +        return context.checkPermission(permissionToCheck, pid, uid) +                == PackageManager.PERMISSION_GRANTED; +    } + +    private static boolean checkSystemLocationAccess(@NonNull Context context, int uid, int pid) { +        if (!isLocationModeEnabled(context, UserHandle.getUserId(uid))) { +            if (DBG) Log.w(TAG, "Location disabled, failed, (" + uid + ")"); +            return false; +        } +        // If the user or profile is current, permission is granted. +        // Otherwise, uid must have INTERACT_ACROSS_USERS_FULL permission. +        return isCurrentProfile(context, uid) || checkInteractAcrossUsersFull(context, uid, pid); +    } +      private static boolean isLocationModeEnabled(@NonNull Context context, @UserIdInt int userId) {          LocationManager locationManager = context.getSystemService(LocationManager.class);          if (locationManager == null) { @@ -105,10 +281,10 @@ public final class LocationAccessPolicy {          return locationManager.isLocationEnabledForUser(UserHandle.of(userId));      } -    private static boolean checkInteractAcrossUsersFull(@NonNull Context context) { -        return context.checkCallingOrSelfPermission( -                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) -                == PackageManager.PERMISSION_GRANTED; +    private static boolean checkInteractAcrossUsersFull( +            @NonNull Context context, int pid, int uid) { +        return checkManifestPermission(context, pid, uid, +                Manifest.permission.INTERACT_ACROSS_USERS_FULL);      }      private static boolean isCurrentProfile(@NonNull Context context, int uid) { @@ -132,4 +308,18 @@ public final class LocationAccessPolicy {              Binder.restoreCallingIdentity(token);          }      } -} + +    private static boolean isAppAtLeastSdkVersion(Context context, String pkgName, int sdkVersion) { +        try { +            if (context.getPackageManager().getApplicationInfo(pkgName, 0).targetSdkVersion +                    >= sdkVersion) { +                return true; +            } +        } catch (PackageManager.NameNotFoundException e) { +            // In case of exception, assume known app (more strict checking) +            // Note: This case will never happen since checkPackage is +            // called to verify validity before checking app's version. +        } +        return false; +    } +}
\ No newline at end of file diff --git a/telephony/java/android/telephony/NetworkRegistrationState.java b/telephony/java/android/telephony/NetworkRegistrationState.java index c37b492a9cf1..6e6d59e62148 100644 --- a/telephony/java/android/telephony/NetworkRegistrationState.java +++ b/telephony/java/android/telephony/NetworkRegistrationState.java @@ -152,7 +152,7 @@ public class NetworkRegistrationState implements Parcelable {      private final int[] mAvailableServices;      @Nullable -    private final CellIdentity mCellIdentity; +    private CellIdentity mCellIdentity;      @Nullable      private VoiceSpecificRegistrationStates mVoiceSpecificStates; @@ -521,4 +521,22 @@ public class NetworkRegistrationState implements Parcelable {              return new NetworkRegistrationState[size];          }      }; + +    /** +     * @hide +     */ +    public NetworkRegistrationState sanitizeLocationInfo() { +        NetworkRegistrationState result = copy(); +        result.mCellIdentity = null; +        return result; +    } + +    private NetworkRegistrationState copy() { +        Parcel p = Parcel.obtain(); +        this.writeToParcel(p, 0); +        p.setDataPosition(0); +        NetworkRegistrationState result = new NetworkRegistrationState(p); +        p.recycle(); +        return result; +    }  } diff --git a/telephony/java/android/telephony/ServiceState.java b/telephony/java/android/telephony/ServiceState.java index 402763e52cfa..a1aee6d8217f 100644 --- a/telephony/java/android/telephony/ServiceState.java +++ b/telephony/java/android/telephony/ServiceState.java @@ -36,6 +36,7 @@ import java.util.ArrayList;  import java.util.Arrays;  import java.util.List;  import java.util.Objects; +import java.util.stream.Collectors;  /**   * Contains phone state and service related information. @@ -1885,4 +1886,29 @@ public class ServiceState implements Parcelable {                  ? range1                  : range2;      } + +    /** +     * Returns a copy of self with location-identifying information removed. +     * Always clears the NetworkRegistrationState's CellIdentity fields, but if removeCoarseLocation +     * is true, clears other info as well. +     * @hide +     */ +    public ServiceState sanitizeLocationInfo(boolean removeCoarseLocation) { +        ServiceState state = new ServiceState(this); +        if (state.mNetworkRegistrationStates != null) { +            state.mNetworkRegistrationStates = state.mNetworkRegistrationStates.stream() +                    .map(NetworkRegistrationState::sanitizeLocationInfo) +                    .collect(Collectors.toList()); +        } +        if (!removeCoarseLocation) return state; + +        state.mDataOperatorAlphaLong = null; +        state.mDataOperatorAlphaShort = null; +        state.mDataOperatorNumeric = null; +        state.mVoiceOperatorAlphaLong = null; +        state.mVoiceOperatorAlphaShort = null; +        state.mVoiceOperatorNumeric = null; + +        return state; +    }  } diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index 756d9eb3f1c2..d2345cd79dce 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -1655,10 +1655,7 @@ public class TelephonyManager {       * @deprecated use {@link #getAllCellInfo} instead, which returns a superset of this API.       */      @Deprecated -    @RequiresPermission(anyOf = { -            android.Manifest.permission.ACCESS_COARSE_LOCATION, -            android.Manifest.permission.ACCESS_FINE_LOCATION -    }) +    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)      public CellLocation getCellLocation() {          try {              ITelephony telephony = getITelephony(); @@ -4918,7 +4915,7 @@ public class TelephonyManager {       * @return List of {@link android.telephony.CellInfo}; null if cell       * information is unavailable.       */ -    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) +    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)      public List<CellInfo> getAllCellInfo() {          try {              ITelephony telephony = getITelephony(); @@ -4995,7 +4992,7 @@ public class TelephonyManager {       * @param executor the executor on which callback will be invoked.       * @param callback a callback to receive CellInfo.       */ -    @RequiresPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION) +    @RequiresPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)      public void requestCellInfoUpdate(              @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) {          try { @@ -5034,7 +5031,7 @@ public class TelephonyManager {       * @hide       */      @SystemApi -    @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_COARSE_LOCATION, +    @RequiresPermission(allOf = {android.Manifest.permission.ACCESS_FINE_LOCATION,              android.Manifest.permission.MODIFY_PHONE_STATE})      public void requestCellInfoUpdate(@NonNull WorkSource workSource,              @NonNull @CallbackExecutor Executor executor, @NonNull CellInfoCallback callback) { @@ -6624,9 +6621,10 @@ public class TelephonyManager {       *       * <p> Note that this scan can take a long time (sometimes minutes) to happen.       * -     * <p>Requires Permission: +     * <p>Requires Permissions:       * {@link android.Manifest.permission#MODIFY_PHONE_STATE} or that the calling app has carrier       * privileges (see {@link #hasCarrierPrivileges}) +     * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.       *       * @return {@link CellNetworkScanResult} with the status       * {@link CellNetworkScanResult#STATUS_SUCCESS} and a list of @@ -6635,12 +6633,15 @@ public class TelephonyManager {       *       * @hide       */ -    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) +    @RequiresPermission(allOf = { +            android.Manifest.permission.MODIFY_PHONE_STATE, +            Manifest.permission.ACCESS_COARSE_LOCATION +    })      public CellNetworkScanResult getAvailableNetworks() {          try {              ITelephony telephony = getITelephony();              if (telephony != null) { -                return telephony.getCellNetworkScanResults(getSubId()); +                return telephony.getCellNetworkScanResults(getSubId(), getOpPackageName());              }          } catch (RemoteException ex) {              Rlog.e(TAG, "getAvailableNetworks RemoteException", ex); @@ -6659,7 +6660,8 @@ public class TelephonyManager {       *       * <p>Requires Permission:       * {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE} or that the calling -     * app has carrier privileges (see {@link #hasCarrierPrivileges}). +     * app has carrier privileges (see {@link #hasCarrierPrivileges}) +     * and {@link android.Manifest.permission#ACCESS_FINE_LOCATION}.       *       * @param request Contains all the RAT with bands/channels that need to be scanned.       * @param executor The executor through which the callback should be invoked. Since the scan @@ -6670,7 +6672,10 @@ public class TelephonyManager {       * @return A NetworkScan obj which contains a callback which can be used to stop the scan.       */      @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges -    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) +    @RequiresPermission(allOf = { +            android.Manifest.permission.MODIFY_PHONE_STATE, +            Manifest.permission.ACCESS_FINE_LOCATION +    })      public NetworkScan requestNetworkScan(              NetworkScanRequest request, Executor executor,              TelephonyScanManager.NetworkScanCallback callback) { @@ -6679,7 +6684,8 @@ public class TelephonyManager {                  mTelephonyScanManager = new TelephonyScanManager();              }          } -        return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback); +        return mTelephonyScanManager.requestNetworkScan(getSubId(), request, executor, callback, +                getOpPackageName());      }      /** @@ -6689,7 +6695,10 @@ public class TelephonyManager {       * @removed       */      @Deprecated -    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE) +    @RequiresPermission(allOf = { +            android.Manifest.permission.MODIFY_PHONE_STATE, +            Manifest.permission.ACCESS_FINE_LOCATION +    })      public NetworkScan requestNetworkScan(          NetworkScanRequest request, TelephonyScanManager.NetworkScanCallback callback) {          return requestNetworkScan(request, AsyncTask.SERIAL_EXECUTOR, callback); @@ -8689,10 +8698,14 @@ public class TelephonyManager {       * given subId. Otherwise, applies to {@link SubscriptionManager#getDefaultSubscriptionId()}       *       * <p>Requires Permission: {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE} -     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}). +     * or that the calling app has carrier privileges (see {@link #hasCarrierPrivileges}) +     * and {@link android.Manifest.permission#ACCESS_COARSE_LOCATION}.       */      @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges -    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) +    @RequiresPermission(allOf = { +            Manifest.permission.READ_PHONE_STATE, +            Manifest.permission.ACCESS_COARSE_LOCATION +    })      public ServiceState getServiceState() {          return getServiceStateForSubscriber(getSubId());      } diff --git a/telephony/java/android/telephony/TelephonyScanManager.java b/telephony/java/android/telephony/TelephonyScanManager.java index 96ff33255b53..91f74b867fa0 100644 --- a/telephony/java/android/telephony/TelephonyScanManager.java +++ b/telephony/java/android/telephony/TelephonyScanManager.java @@ -29,14 +29,14 @@ import android.os.Messenger;  import android.os.Parcelable;  import android.os.RemoteException;  import android.os.ServiceManager; -import android.util.Log;  import android.util.SparseArray; + +import com.android.internal.telephony.ITelephony; +  import java.util.Arrays;  import java.util.List;  import java.util.concurrent.Executor; -import com.android.internal.telephony.ITelephony; -  /**   * Manages the radio access network scan requests and callbacks.   */ @@ -183,6 +183,7 @@ public final class TelephonyScanManager {       *       * <p>       * Requires Permission: +     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} and       *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}       * Or the calling app has carrier privileges. @see #hasCarrierPrivileges       * @@ -192,11 +193,13 @@ public final class TelephonyScanManager {       * @hide       */      public NetworkScan requestNetworkScan(int subId, -            NetworkScanRequest request, Executor executor, NetworkScanCallback callback) { +            NetworkScanRequest request, Executor executor, NetworkScanCallback callback, +            String callingPackage) {          try {              ITelephony telephony = getITelephony();              if (telephony != null) { -                int scanId = telephony.requestNetworkScan(subId, request, mMessenger, new Binder()); +                int scanId = telephony.requestNetworkScan( +                        subId, request, mMessenger, new Binder(), callingPackage);                  saveScanInfo(scanId, request, executor, callback);                  return new NetworkScan(scanId, subId);              } diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl index 8e54c434b497..e6a55585506c 100644 --- a/telephony/java/com/android/internal/telephony/ITelephony.aidl +++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl @@ -765,7 +765,7 @@ interface ITelephony {       * @param subId the id of the subscription.       * @return CellNetworkScanResult containing status of scan and networks.       */ -    CellNetworkScanResult getCellNetworkScanResults(int subId); +    CellNetworkScanResult getCellNetworkScanResults(int subId, String callingPackage);      /**       * Perform a radio network scan and return the id of this scan. @@ -774,10 +774,11 @@ interface ITelephony {       * @param request Defines all the configs for network scan.       * @param messenger Callback messages will be sent using this messenger.       * @param binder the binder object instantiated in TelephonyManager. +     * @param callingPackage the calling package       * @return An id for this scan.       */      int requestNetworkScan(int subId, in NetworkScanRequest request, in Messenger messenger, -            in IBinder binder); +            in IBinder binder, in String callingPackage);      /**       * Stop an existing radio network scan.  |