Merge "Implement more location checks"
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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>);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,59 +42,234 @@ 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,
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
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);
|
||||
} finally {
|
||||
Trace.endSection();
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mandatory parameter, used for performing permission checks.
|
||||
*/
|
||||
public Builder setCallingUid(int callingUid) {
|
||||
mCallingUid = callingUid;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mandatory parameter, used for performing permission checks.
|
||||
*/
|
||||
public Builder setCallingPid(int callingPid) {
|
||||
mCallingPid = callingPid;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user