Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 2a14323b authored by yongnamcha's avatar yongnamcha
Browse files

Location Query for OTT Emergency Calls (services/Telecomm)

This commit implements Location Query for OTT Emergency Calls
in Telecomm.

Bug: 236748912
Test: atest and cts test
Change-Id: I8d589570e9d4350f908517fe70c77a5663dcfb7a
parent b9694ff9
Loading
Loading
Loading
Loading
+215 −1
Original line number Diff line number Diff line
@@ -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;
@@ -44,11 +49,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;
@@ -63,7 +72,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;

/**
@@ -78,6 +91,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
@@ -1217,6 +1234,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();
@@ -1243,7 +1325,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,
@@ -1303,6 +1386,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.
     */
+31 −0
Original line number Diff line number Diff line
@@ -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;
@@ -1857,6 +1858,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);
    }
+7 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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";
@@ -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:
@@ -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);