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

Commit 3af43c43 authored by Guillaume Jacquart's avatar Guillaume Jacquart
Browse files

Merge branch '2398-s-fakelocation_from_advancedprivacy_contentprovider' into 'v1-s'

feat:2398: fake location from AdvancedPrivacy ContentProvider.

See merge request e/os/android_frameworks_base!214
parents d6b29409 61a025d2
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -5859,6 +5859,11 @@
    <permission android:name="android.permission.READ_GLOBAL_APP_SEARCH_DATA"
                android:protectionLevel="internal|role" />

    <!-- /e/OS addition: read fake location set by the user in AdvancedPrivacy. -->
    <permission android:name="foundation.e.permission.READ_FAKE_LOCATION_SETTINGS"
        android:protectionLevel="signature" />
    <uses-permission android:name="foundation.e.permission.READ_FAKE_LOCATION_SETTINGS" />

    <!-- Attribution for Geofencing service. -->
    <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
    <!-- Attribution for Country Detector. -->
+1 −1
Original line number Diff line number Diff line
@@ -1305,7 +1305,7 @@ public class Location implements Parcelable {
     * @see LocationManager#addTestProvider
     */
    public boolean isMock() {
        return false;
        return (mFieldsMask & HAS_MOCK_PROVIDER_MASK) != 0;
    }

    /**
+139 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 E FOUNDATION
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package com.android.server.location;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.location.Location;
import android.location.LocationResult;
import android.location.util.identity.CallerIdentity;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class FakeLocationResolver {
    public static final String TAG = "FakeLocationResolver";

    private static final String FAKE_LOCATIONS_URI = "content://foundation.e.advancedprivacy.fakelocations";

    private static final String PARAM_UID = "uid";
    private static final String PARAM_LATITUDE = "latitude";
    private static final String PARAM_LONGITUDE = "longitude";

    public static @Nullable LocationResult fakeLocations(
            @NonNull Context context,
            @Nullable LocationResult baseLocations,
            @NonNull CallerIdentity identity) {
        final int uid = identity.getUid();
        final String packageName = identity.getPackageName();

        if (baseLocations == null || packageName == null || uid < 0) {
            Log.w(TAG, "FakeLocationResolver::fakeLocations invalid parameters");
            return baseLocations;
        }
        final FakeLocation latLon = getFakeLocation(context, packageName, uid);
        if (latLon == null) return baseLocations;

        return LocationResult.wrap(overrideLatLon(baseLocations.asList(), latLon));
    }

    public static @Nullable Location fakeLocation(
            @NonNull Context context,
            @Nullable Location baseLocation,
            @NonNull CallerIdentity identity) {
        final int uid = identity.getUid();
        final String packageName = identity.getPackageName();

        if (baseLocation == null || packageName == null || uid < 0) {
            Log.w(TAG, "FakeLocationResolver::fakeLocation invalid parameters");
            return baseLocation;
        }
        final FakeLocation latLon = getFakeLocation(context, packageName, uid);
        if (latLon == null) return baseLocation;

        return overrideLatLon(baseLocation, latLon);
    }

    public static boolean hasFakeLocation(@NonNull Context context, @NonNull CallerIdentity identity) {
        final int uid = identity.getUid();
        final String packageName = identity.getPackageName();

        if (packageName == null || uid < 0) {
            Log.w(TAG, "FakeLocationResolver::hasFakeLocation invalid parameters");
            return false;
        }
        final FakeLocation latLon = getFakeLocation(context, packageName, uid);
        if (latLon == null) return false;

        return true;
    }

    private static class FakeLocation {
        double latitude;
        double longitude;

        public FakeLocation(double latitude, double longitude) {
            this.latitude = latitude;
            this.longitude = longitude;
        }
    }

    private static @Nullable FakeLocation getFakeLocation(@NonNull Context context, @NonNull String packageName, int uid) {
        try {
            final Bundle extra = new Bundle();
            extra.putInt(PARAM_UID, uid);
            final Bundle result = context.getContentResolver().call(
                    Uri.parse(FAKE_LOCATIONS_URI),
                    "",
                    packageName,
                    extra
            );

            if (result != null && result.containsKey(PARAM_LATITUDE) && result.containsKey(PARAM_LONGITUDE)) {
                return new FakeLocation(result.getDouble(PARAM_LATITUDE), result.getDouble(PARAM_LONGITUDE));
            }
        } catch(Exception e) {
            Log.w(TAG, "Can't getFakeLocation", e);
        }
        return null;
    }

    private static @NonNull List<Location> overrideLatLon(@NonNull List<Location> baseLocations, @NonNull FakeLocation latLon) {
        final ArrayList<Location> fakedLocations = new ArrayList<Location>(baseLocations.size());
        for (Location location: baseLocations) {
            Location fakedLocation = overrideLatLon(location, latLon);

            fakedLocations.add(fakedLocation);
        }
        return fakedLocations;
    }

    private static @NonNull Location overrideLatLon(@NonNull Location baseLocation, @NonNull FakeLocation latLon) {
        final Location fakedLocation = new Location(baseLocation);
        fakedLocation.setLatitude(latLon.latitude);
        fakedLocation.setLongitude(latLon.longitude);
        fakedLocation.setAltitude(3.0);
        fakedLocation.setSpeed(0.01f);

        return fakedLocation;
    }
}
+4 −0
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.util.ArraySet;

import com.android.internal.annotations.GuardedBy;
import com.android.server.PendingIntentUtils;
import com.android.server.location.FakeLocationResolver;
import com.android.server.location.LocationPermissions;
import com.android.server.location.injector.Injector;
import com.android.server.location.injector.LocationPermissionsHelper;
@@ -298,6 +299,9 @@ public class GeofenceManager extends

        CallerIdentity identity = CallerIdentity.fromBinder(mContext, packageName,
                attributionTag, AppOpsManager.toReceiverId(pendingIntent));
        if (FakeLocationResolver.hasFakeLocation(mContext, identity)) {
            return;
        }

        final long ident = Binder.clearCallingIdentity();
        try {
+7 −0
Original line number Diff line number Diff line
@@ -91,6 +91,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.Preconditions;
import com.android.server.FgThread;
import com.android.server.LocalServices;
import com.android.server.location.FakeLocationResolver;
import com.android.server.location.LocationPermissions;
import com.android.server.location.LocationPermissions.PermissionLevel;
import com.android.server.location.fudger.LocationFudger;
@@ -974,6 +975,8 @@ public class LocationProviderManager extends
                        deliverLocationResult = locationResult;
                    }

                    deliverLocationResult = FakeLocationResolver.fakeLocations(mContext, deliverLocationResult, getIdentity());

                    listener.deliverOnLocationChanged(deliverLocationResult,
                            mUseWakeLock ? mWakeLockReleaser : null);
                    EVENT_LOG.logProviderDeliveredLocations(mName, locationResult.size(),
@@ -1277,6 +1280,8 @@ public class LocationProviderManager extends
                        deliverLocationResult = locationResult;
                    }

                    deliverLocationResult = FakeLocationResolver.fakeLocations(mContext, deliverLocationResult, getIdentity());

                    // we currently don't hold a wakelock for getCurrentLocation deliveries
                    listener.deliverOnLocationChanged(deliverLocationResult, null);
                    EVENT_LOG.logProviderDeliveredLocations(mName,
@@ -1656,6 +1661,8 @@ public class LocationProviderManager extends
                        Long.MAX_VALUE),
                permissionLevel);

        location = FakeLocationResolver.fakeLocation(mContext, location, identity);

        if (location != null && identity.getPid() == Process.myPid()) {
            // if delivering to the same process, make a copy of the location first (since
            // location is mutable)