Loading src/com/android/server/telecom/ConnectionServiceWrapper.java +215 −1 Original line number Diff line number Diff line Loading @@ -23,11 +23,16 @@ import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; import android.location.LocationRequest; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; Loading @@ -43,11 +48,15 @@ import android.telecom.Logging.Session; import android.telecom.ParcelableConference; import android.telecom.ParcelableConnection; import android.telecom.PhoneAccountHandle; import android.telecom.QueryLocationException; import android.telecom.StatusHints; import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.telephony.CellIdentity; import android.telephony.TelephonyManager; import android.util.Pair; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.IConnectionService; Loading @@ -62,7 +71,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.Objects; /** Loading @@ -77,6 +90,10 @@ public class ConnectionServiceWrapper extends ServiceBinder implements private static final String TELECOM_ABBREVIATION = "cast"; private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null; private @Nullable CancellationSignal mOngoingQueryLocationRequest = null; private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor(); private final class Adapter extends IConnectionServiceAdapter.Stub { @Override Loading Loading @@ -1216,6 +1233,71 @@ public class ConnectionServiceWrapper extends ServiceBinder implements Log.endSession(); } } @Override public void queryLocation(String callId, long timeoutMillis, String provider, ResultReceiver callback, Session.Info sessionInfo) { Log.startSession(sessionInfo, "CSW.qL", mPackageAbbreviation); TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); if (telecomManager == null || !telecomManager.getSimCallManager().getComponentName() .equals(getComponentName())) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult(QueryLocationException.ERROR_NOT_PERMITTED)); Log.endSession(); return; } String opPackageName = mContext.getOpPackageName(); int packageUid = -1; try { packageUid = mContext.getPackageManager().getPackageUid(opPackageName, PackageManager.PackageInfoFlags.of(0)); } catch (PackageManager.NameNotFoundException e) { // packageUid is -1 } try { mAppOpsManager.noteProxyOp( AppOpsManager.OPSTR_FINE_LOCATION, opPackageName, packageUid, null, null); } catch (SecurityException e) { Log.e(ConnectionServiceWrapper.this, e, ""); } if (!callingUidMatchesPackageManagerRecords(getComponentName().getPackageName())) { throw new SecurityException(String.format("queryCurrentLocation: " + "uid mismatch found : callingPackageName=[%s], callingUid=[%d]", getComponentName().getPackageName(), Binder.getCallingUid())); } Call call = mCallIdMapper.getCall(callId); if (call == null || !call.isEmergencyCall()) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult(QueryLocationException .ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS)); Log.endSession(); return; } long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { logIncoming("queryLocation %s %d", callId, timeoutMillis); ConnectionServiceWrapper.this.queryCurrentLocation(timeoutMillis, provider, callback); } } catch (Throwable t) { Log.e(ConnectionServiceWrapper.this, t, ""); throw t; } finally { Binder.restoreCallingIdentity(token); Log.endSession(); } } } private final Adapter mAdapter = new Adapter(); Loading @@ -1242,7 +1324,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements * @param context The context. * @param userHandle The {@link UserHandle} to use when binding. */ ConnectionServiceWrapper( @VisibleForTesting public ConnectionServiceWrapper( ComponentName componentName, ConnectionServiceRepository connectionServiceRepository, PhoneAccountRegistrar phoneAccountRegistrar, Loading Loading @@ -1302,6 +1385,137 @@ public class ConnectionServiceWrapper extends ServiceBinder implements return null; } @VisibleForTesting @SuppressWarnings("FutureReturnValueIgnored") public void queryCurrentLocation(long timeoutMillis, String provider, ResultReceiver callback) { if (mQueryLocationFuture != null && !mQueryLocationFuture.isDone()) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult( QueryLocationException.ERROR_PREVIOUS_REQUEST_EXISTS)); return; } LocationManager locationManager = (LocationManager) mContext.createAttributionContext( ConnectionServiceWrapper.class.getSimpleName()).getSystemService( Context.LOCATION_SERVICE); if (locationManager == null) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult(QueryLocationException.ERROR_SERVICE_UNAVAILABLE)); } mQueryLocationFuture = new CompletableFuture<Pair<Integer, Location>>() .completeOnTimeout( Pair.create(QueryLocationException.ERROR_REQUEST_TIME_OUT, null), timeoutMillis, TimeUnit.MILLISECONDS); mOngoingQueryLocationRequest = new CancellationSignal(); locationManager.getCurrentLocation( provider, new LocationRequest.Builder(0) .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY) .setLocationSettingsIgnored(true) .build(), mOngoingQueryLocationRequest, mQueryLocationExecutor, (location) -> mQueryLocationFuture.complete(Pair.create(null, location))); mQueryLocationFuture.whenComplete((result, e) -> { if (e != null) { callback.send(0, getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED)); } if (result.second != null) { callback.send(1, getQueryLocationResult(result.second)); } else { callback.send(0, getQueryLocationErrorResult(result.first)); } if (mOngoingQueryLocationRequest != null) { mOngoingQueryLocationRequest.cancel(); mOngoingQueryLocationRequest = null; } if (mQueryLocationFuture != null) { mQueryLocationFuture = null; } }); } private Bundle getQueryLocationResult(Location location) { Bundle extras = new Bundle(); extras.putParcelable(Connection.EXTRA_KEY_QUERY_LOCATION, location); return extras; } private Bundle getQueryLocationErrorResult(int result) { String message; switch (result) { case QueryLocationException.ERROR_REQUEST_TIME_OUT: message = "The operation was not completed on time"; break; case QueryLocationException.ERROR_PREVIOUS_REQUEST_EXISTS: message = "The operation was rejected due to a previous request exists"; break; case QueryLocationException.ERROR_NOT_PERMITTED: message = "The operation is not permitted"; break; case QueryLocationException.ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS: message = "Non-emergency call connection are not allowed"; break; case QueryLocationException.ERROR_SERVICE_UNAVAILABLE: message = "The operation has failed due to service is not available"; break; default: message = "The operation has failed due to an unknown or unspecified error"; } QueryLocationException exception = new QueryLocationException(message, result); Bundle extras = new Bundle(); extras.putParcelable(QueryLocationException.QUERY_LOCATION_ERROR, exception); return extras; } /** * helper method that compares the binder_uid to what the packageManager_uid reports for the * passed in packageName. * * returns true if the binder_uid matches the packageManager_uid records */ private boolean callingUidMatchesPackageManagerRecords(String packageName) { int packageUid = -1; int callingUid = Binder.getCallingUid(); PackageManager pm; try{ pm = mContext.createContextAsUser( UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); } catch (Exception e){ Log.i(this, "callingUidMatchesPackageManagerRecords:" + " createContextAsUser hit exception=[%s]", e.toString()); return false; } if (pm != null) { try { packageUid = pm.getPackageUid(packageName, PackageManager.PackageInfoFlags.of(0)); } catch (PackageManager.NameNotFoundException e) { // packageUid is -1. } } if (packageUid != callingUid) { Log.i(this, "callingUidMatchesPackageManagerRecords: uid mismatch found for " + "packageName=[%s]. packageManager reports packageUid=[%d] but " + "binder reports callingUid=[%d]", packageName, packageUid, callingUid); } return packageUid == callingUid; } /** * Creates a conference for a new outgoing call or attach to an existing incoming call. */ Loading tests/src/com/android/server/telecom/tests/CallsManagerTest.java +31 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.os.Handler; import android.os.Looper; import android.os.OutcomeReceiver; import android.os.Process; import android.os.ResultReceiver; import android.os.SystemClock; import android.os.UserHandle; import android.telecom.CallerInfo; Loading Loading @@ -1981,6 +1982,36 @@ public class CallsManagerTest extends TelecomTestCase { } } /** * When queryCurrentLocation is called, check whether the result is received through the * ResultReceiver. * @throws Exception if {@link CompletableFuture#get()} fails. */ @Test public void testQueryCurrentLocationCheckOnReceiveResult() throws Exception { ConnectionServiceWrapper service = new ConnectionServiceWrapper( new ComponentName(mContext.getPackageName(), mContext.getPackageName().getClass().getName()), null, mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null); CompletableFuture<String> resultFuture = new CompletableFuture<>(); try { service.queryCurrentLocation(500L, "Test_provider", new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle result) { super.onReceiveResult(resultCode, result); resultFuture.complete("onReceiveResult"); } }); } catch (Exception e) { resultFuture.complete("Exception : " + e); } String result = resultFuture.get(1000L, TimeUnit.MILLISECONDS); assertTrue(result.contains("onReceiveResult")); } private Call addSpyCall() { return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE); } Loading tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +7 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.SensorPrivacyManager; import android.location.CountryDetector; import android.location.LocationManager; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Bundle; Loading Loading @@ -125,6 +126,9 @@ public class ComponentContextFixture implements TestFixture<Context> { return this; } @Override public Context createAttributionContext(String attributionTag) { return this; } @Override public String getPackageName() { return "com.android.server.telecom.tests"; Loading Loading @@ -199,6 +203,8 @@ public class ComponentContextFixture implements TestFixture<Context> { return mAudioManager; case Context.TELEPHONY_SERVICE: return mTelephonyManager; case Context.LOCATION_SERVICE: return mLocationManager; case Context.APP_OPS_SERVICE: return mAppOpsManager; case Context.NOTIFICATION_SERVICE: Loading Loading @@ -551,6 +557,7 @@ public class ComponentContextFixture implements TestFixture<Context> { private final Executor mMainExecutor = mock(Executor.class); private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext)); private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class); private final LocationManager mLocationManager = mock(LocationManager.class); private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class); private final NotificationManager mNotificationManager = mock(NotificationManager.class); private final UserManager mUserManager = mock(UserManager.class); Loading Loading
src/com/android/server/telecom/ConnectionServiceWrapper.java +215 −1 Original line number Diff line number Diff line Loading @@ -23,11 +23,16 @@ import android.app.AppOpsManager; import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.location.Location; import android.location.LocationManager; import android.location.LocationRequest; import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.CancellationSignal; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.ResultReceiver; import android.os.UserHandle; Loading @@ -43,11 +48,15 @@ import android.telecom.Logging.Session; import android.telecom.ParcelableConference; import android.telecom.ParcelableConnection; import android.telecom.PhoneAccountHandle; import android.telecom.QueryLocationException; import android.telecom.StatusHints; import android.telecom.TelecomManager; import android.telecom.VideoProfile; import android.telephony.CellIdentity; import android.telephony.TelephonyManager; import android.util.Pair; import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.telecom.IConnectionService; Loading @@ -62,7 +71,11 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.Objects; /** Loading @@ -77,6 +90,10 @@ public class ConnectionServiceWrapper extends ServiceBinder implements private static final String TELECOM_ABBREVIATION = "cast"; private CompletableFuture<Pair<Integer, Location>> mQueryLocationFuture = null; private @Nullable CancellationSignal mOngoingQueryLocationRequest = null; private final ExecutorService mQueryLocationExecutor = Executors.newSingleThreadExecutor(); private final class Adapter extends IConnectionServiceAdapter.Stub { @Override Loading Loading @@ -1216,6 +1233,71 @@ public class ConnectionServiceWrapper extends ServiceBinder implements Log.endSession(); } } @Override public void queryLocation(String callId, long timeoutMillis, String provider, ResultReceiver callback, Session.Info sessionInfo) { Log.startSession(sessionInfo, "CSW.qL", mPackageAbbreviation); TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); if (telecomManager == null || !telecomManager.getSimCallManager().getComponentName() .equals(getComponentName())) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult(QueryLocationException.ERROR_NOT_PERMITTED)); Log.endSession(); return; } String opPackageName = mContext.getOpPackageName(); int packageUid = -1; try { packageUid = mContext.getPackageManager().getPackageUid(opPackageName, PackageManager.PackageInfoFlags.of(0)); } catch (PackageManager.NameNotFoundException e) { // packageUid is -1 } try { mAppOpsManager.noteProxyOp( AppOpsManager.OPSTR_FINE_LOCATION, opPackageName, packageUid, null, null); } catch (SecurityException e) { Log.e(ConnectionServiceWrapper.this, e, ""); } if (!callingUidMatchesPackageManagerRecords(getComponentName().getPackageName())) { throw new SecurityException(String.format("queryCurrentLocation: " + "uid mismatch found : callingPackageName=[%s], callingUid=[%d]", getComponentName().getPackageName(), Binder.getCallingUid())); } Call call = mCallIdMapper.getCall(callId); if (call == null || !call.isEmergencyCall()) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult(QueryLocationException .ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS)); Log.endSession(); return; } long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { logIncoming("queryLocation %s %d", callId, timeoutMillis); ConnectionServiceWrapper.this.queryCurrentLocation(timeoutMillis, provider, callback); } } catch (Throwable t) { Log.e(ConnectionServiceWrapper.this, t, ""); throw t; } finally { Binder.restoreCallingIdentity(token); Log.endSession(); } } } private final Adapter mAdapter = new Adapter(); Loading @@ -1242,7 +1324,8 @@ public class ConnectionServiceWrapper extends ServiceBinder implements * @param context The context. * @param userHandle The {@link UserHandle} to use when binding. */ ConnectionServiceWrapper( @VisibleForTesting public ConnectionServiceWrapper( ComponentName componentName, ConnectionServiceRepository connectionServiceRepository, PhoneAccountRegistrar phoneAccountRegistrar, Loading Loading @@ -1302,6 +1385,137 @@ public class ConnectionServiceWrapper extends ServiceBinder implements return null; } @VisibleForTesting @SuppressWarnings("FutureReturnValueIgnored") public void queryCurrentLocation(long timeoutMillis, String provider, ResultReceiver callback) { if (mQueryLocationFuture != null && !mQueryLocationFuture.isDone()) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult( QueryLocationException.ERROR_PREVIOUS_REQUEST_EXISTS)); return; } LocationManager locationManager = (LocationManager) mContext.createAttributionContext( ConnectionServiceWrapper.class.getSimpleName()).getSystemService( Context.LOCATION_SERVICE); if (locationManager == null) { callback.send(0 /* isSuccess */, getQueryLocationErrorResult(QueryLocationException.ERROR_SERVICE_UNAVAILABLE)); } mQueryLocationFuture = new CompletableFuture<Pair<Integer, Location>>() .completeOnTimeout( Pair.create(QueryLocationException.ERROR_REQUEST_TIME_OUT, null), timeoutMillis, TimeUnit.MILLISECONDS); mOngoingQueryLocationRequest = new CancellationSignal(); locationManager.getCurrentLocation( provider, new LocationRequest.Builder(0) .setQuality(LocationRequest.QUALITY_HIGH_ACCURACY) .setLocationSettingsIgnored(true) .build(), mOngoingQueryLocationRequest, mQueryLocationExecutor, (location) -> mQueryLocationFuture.complete(Pair.create(null, location))); mQueryLocationFuture.whenComplete((result, e) -> { if (e != null) { callback.send(0, getQueryLocationErrorResult(QueryLocationException.ERROR_UNSPECIFIED)); } if (result.second != null) { callback.send(1, getQueryLocationResult(result.second)); } else { callback.send(0, getQueryLocationErrorResult(result.first)); } if (mOngoingQueryLocationRequest != null) { mOngoingQueryLocationRequest.cancel(); mOngoingQueryLocationRequest = null; } if (mQueryLocationFuture != null) { mQueryLocationFuture = null; } }); } private Bundle getQueryLocationResult(Location location) { Bundle extras = new Bundle(); extras.putParcelable(Connection.EXTRA_KEY_QUERY_LOCATION, location); return extras; } private Bundle getQueryLocationErrorResult(int result) { String message; switch (result) { case QueryLocationException.ERROR_REQUEST_TIME_OUT: message = "The operation was not completed on time"; break; case QueryLocationException.ERROR_PREVIOUS_REQUEST_EXISTS: message = "The operation was rejected due to a previous request exists"; break; case QueryLocationException.ERROR_NOT_PERMITTED: message = "The operation is not permitted"; break; case QueryLocationException.ERROR_NOT_ALLOWED_FOR_NON_EMERGENCY_CONNECTIONS: message = "Non-emergency call connection are not allowed"; break; case QueryLocationException.ERROR_SERVICE_UNAVAILABLE: message = "The operation has failed due to service is not available"; break; default: message = "The operation has failed due to an unknown or unspecified error"; } QueryLocationException exception = new QueryLocationException(message, result); Bundle extras = new Bundle(); extras.putParcelable(QueryLocationException.QUERY_LOCATION_ERROR, exception); return extras; } /** * helper method that compares the binder_uid to what the packageManager_uid reports for the * passed in packageName. * * returns true if the binder_uid matches the packageManager_uid records */ private boolean callingUidMatchesPackageManagerRecords(String packageName) { int packageUid = -1; int callingUid = Binder.getCallingUid(); PackageManager pm; try{ pm = mContext.createContextAsUser( UserHandle.getUserHandleForUid(callingUid), 0).getPackageManager(); } catch (Exception e){ Log.i(this, "callingUidMatchesPackageManagerRecords:" + " createContextAsUser hit exception=[%s]", e.toString()); return false; } if (pm != null) { try { packageUid = pm.getPackageUid(packageName, PackageManager.PackageInfoFlags.of(0)); } catch (PackageManager.NameNotFoundException e) { // packageUid is -1. } } if (packageUid != callingUid) { Log.i(this, "callingUidMatchesPackageManagerRecords: uid mismatch found for " + "packageName=[%s]. packageManager reports packageUid=[%d] but " + "binder reports callingUid=[%d]", packageName, packageUid, callingUid); } return packageUid == callingUid; } /** * Creates a conference for a new outgoing call or attach to an existing incoming call. */ Loading
tests/src/com/android/server/telecom/tests/CallsManagerTest.java +31 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.os.Handler; import android.os.Looper; import android.os.OutcomeReceiver; import android.os.Process; import android.os.ResultReceiver; import android.os.SystemClock; import android.os.UserHandle; import android.telecom.CallerInfo; Loading Loading @@ -1981,6 +1982,36 @@ public class CallsManagerTest extends TelecomTestCase { } } /** * When queryCurrentLocation is called, check whether the result is received through the * ResultReceiver. * @throws Exception if {@link CompletableFuture#get()} fails. */ @Test public void testQueryCurrentLocationCheckOnReceiveResult() throws Exception { ConnectionServiceWrapper service = new ConnectionServiceWrapper( new ComponentName(mContext.getPackageName(), mContext.getPackageName().getClass().getName()), null, mPhoneAccountRegistrar, mCallsManager, mContext, mLock, null); CompletableFuture<String> resultFuture = new CompletableFuture<>(); try { service.queryCurrentLocation(500L, "Test_provider", new ResultReceiver(new Handler(Looper.getMainLooper())) { @Override protected void onReceiveResult(int resultCode, Bundle result) { super.onReceiveResult(resultCode, result); resultFuture.complete("onReceiveResult"); } }); } catch (Exception e) { resultFuture.complete("Exception : " + e); } String result = resultFuture.get(1000L, TimeUnit.MILLISECONDS); assertTrue(result.contains("onReceiveResult")); } private Call addSpyCall() { return addSpyCall(SIM_2_HANDLE, CallState.ACTIVE); } Loading
tests/src/com/android/server/telecom/tests/ComponentContextFixture.java +7 −0 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.SensorPrivacyManager; import android.location.CountryDetector; import android.location.LocationManager; import android.media.AudioDeviceInfo; import android.media.AudioManager; import android.os.Bundle; Loading Loading @@ -125,6 +126,9 @@ public class ComponentContextFixture implements TestFixture<Context> { return this; } @Override public Context createAttributionContext(String attributionTag) { return this; } @Override public String getPackageName() { return "com.android.server.telecom.tests"; Loading Loading @@ -199,6 +203,8 @@ public class ComponentContextFixture implements TestFixture<Context> { return mAudioManager; case Context.TELEPHONY_SERVICE: return mTelephonyManager; case Context.LOCATION_SERVICE: return mLocationManager; case Context.APP_OPS_SERVICE: return mAppOpsManager; case Context.NOTIFICATION_SERVICE: Loading Loading @@ -551,6 +557,7 @@ public class ComponentContextFixture implements TestFixture<Context> { private final Executor mMainExecutor = mock(Executor.class); private final AudioManager mAudioManager = spy(new FakeAudioManager(mContext)); private final TelephonyManager mTelephonyManager = mock(TelephonyManager.class); private final LocationManager mLocationManager = mock(LocationManager.class); private final AppOpsManager mAppOpsManager = mock(AppOpsManager.class); private final NotificationManager mNotificationManager = mock(NotificationManager.class); private final UserManager mUserManager = mock(UserManager.class); Loading