diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6be3dbc00a2a24b8a15f38d80863c4d18629955c..efda280494c7298ca47ecdff60f8dd5738406979 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,6 +45,16 @@ android:windowSoftInputMode="adjustResize" tools:replace="android:icon,android:label,android:theme" > + + + + + + Bundle().apply { + putDouble(PARAM_LATITUDE, lat.toDouble()) + putDouble(PARAM_LONGITUDE, lon.toDouble()) + } + } + } + + override fun onCreate(): Boolean { + return true + } + + override fun query( + uri: Uri, + projection: Array?, + selection: String?, + selectionArgs: Array?, + sortOrder: String? + ): Cursor? { + // Use call instead + return null + } + + override fun getType(uri: Uri): String? { + return "text/plain" + } + + override fun insert(p0: Uri, p1: ContentValues?): Uri? { + // ReadOnly content provider + return null + } + + override fun delete(p0: Uri, p1: String?, p2: Array?): Int { + // ReadOnly content provider + return 0 + } + + override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array?): Int { + // ReadOnly content provider + return 0 + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bbf4744ec2a5be5ecd9c46c9289911c87b96f0de..026a7516eaf5f1cc979e345df8159181861fbee9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ ~ along with this program. If not, see . --> - Advanced Privacy + Advanced Privacy_POC diff --git a/fakelocation/fakelocationdemo/build.gradle b/fakelocation/fakelocationdemo/build.gradle index 49b4c3a3bc238b212a78cad7ffd122e53ae9971e..8e109bf5514aee86a6004832c78b9318dde51235 100644 --- a/fakelocation/fakelocationdemo/build.gradle +++ b/fakelocation/fakelocationdemo/build.gradle @@ -49,7 +49,7 @@ android { allWarningsAsErrors = false } buildFeatures { - dataBinding true + viewBinding true } namespace 'foundation.e.privacymodules.fakelocationdemo' } @@ -59,12 +59,13 @@ dependencies { implementation project(':fakelocation') implementation project(':permissionsstandalone') - implementation ( libs.androidx.core.ktx, libs.androidx.appcompat, libs.google.material, ) + + implementation("com.google.android.gms:play-services-location:21.0.0") testImplementation libs.junit } diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt index 78543e61263356509fd83c13793d433bc358f753..62df5518865c78a1a4ff48e3b047ee6dce28670b 100644 --- a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/MainActivity.kt @@ -25,6 +25,7 @@ import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.os.Bundle +import android.os.Looper import android.os.Process.myUid import android.util.Log import android.view.View @@ -32,13 +33,18 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat -import androidx.databinding.DataBindingUtil +import com.google.android.gms.location.LocationRequest +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority import foundation.e.advancedprivacy.domain.entities.AppOpModes import foundation.e.advancedprivacy.domain.entities.ApplicationDescription import foundation.e.advancedprivacy.domain.entities.ProfileType import foundation.e.advancedprivacy.fakelocation.domain.usecases.FakeLocationModule import foundation.e.advancedprivacy.permissions.externalinterfaces.PermissionsPrivacyModuleImpl +import foundation.e.privacymodules.fakelocationdemo.aosp.CallerIdentity +import foundation.e.privacymodules.fakelocationdemo.aosp.LocationResult import foundation.e.privacymodules.fakelocationdemo.databinding.ActivityMainBinding +import foundation.e.privacymodules.fakelocationdemo.microg.ClientIdentity class MainActivity : AppCompatActivity() { companion object { @@ -63,11 +69,12 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + binding = ActivityMainBinding.inflate(layoutInflater) + setContentView(binding.root) actionBar?.setDisplayHomeAsUpEnabled(true) - binding.view = this + setOnClickListeners() } override fun onResume() { @@ -75,15 +82,28 @@ class MainActivity : AppCompatActivity() { updateData("") } + private fun setOnClickListeners() { + binding.permissionsButton.setOnClickListener(::onClickPermission) + + binding.trueLocationButton.setOnClickListener(::onClickReset) + binding.parisButton.setOnClickListener(::onClickParis) + binding.londonButton.setOnClickListener(::onClickLondon) + binding.amsterdamButton.setOnClickListener(::onClickAmsterdam) + binding.toggleListenLocation.setOnClickListener(::onClickToggleListenLocation) + binding.fakeLocationConfigButton.setOnClickListener { onClickFakeLocationConfig() } + binding.toggleListenGmsLocation.setOnClickListener { onClickToggleListenGMSLocation(binding.toggleListenGmsLocation.isChecked) } + } + private fun updateData(mockedLocation: String) { - binding.granted = permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED + binding.permissionsButton.isChecked = + permissionsModule.getAppOpMode(appDesc, AppOpsManager.OPSTR_MOCK_LOCATION) == AppOpModes.ALLOWED - binding.mockedLocation = mockedLocation + binding.mockedLocation.text = mockedLocation } private val listener = object : LocationListener { override fun onLocationChanged(location: Location) { - binding.currentLocation = "lat: ${location.latitude} - lon: ${location.longitude}" + binding.currentLocation.text = "lat: ${location.latitude} - lon: ${location.longitude}" } @Deprecated("Deprecated since API 29, never called.") @@ -92,11 +112,36 @@ class MainActivity : AppCompatActivity() { } override fun onProviderEnabled(provider: String) { - binding.providerInfo = "onProdivderEnabled: $provider" + binding.providerInfo.text = "onProdivderEnabled: $provider" } override fun onProviderDisabled(provider: String) { - binding.providerInfo = "onProdivderDisabled: $provider" + binding.providerInfo.text = "onProdivderDisabled: $provider" + } + } + + private val fusedLocationClient + get() = LocationServices.getFusedLocationProviderClient(this) + + val locationRequest = LocationRequest.Builder( + Priority.PRIORITY_HIGH_ACCURACY, + // 1,5s is a good value for the UX while setting specific location. + 1500L + ).build() + + private var localListener = com.google.android.gms.location.LocationListener { location -> + binding.currentGmsLocation.text = "lat: ${location.latitude} - lon: ${location.longitude}" + } + + fun onClickToggleListenGMSLocation(isChecked: Boolean) { + if (isChecked) { + fusedLocationClient.requestLocationUpdates( + locationRequest, + localListener, + Looper.getMainLooper() + ) + } else { + fusedLocationClient.removeLocationUpdates(localListener) } } @@ -110,7 +155,7 @@ class MainActivity : AppCompatActivity() { } locationManager.removeUpdates(listener) - binding.currentLocation = "no listening" + binding.currentLocation.text = "no listening" } private fun startLocationUpdates() { @@ -124,7 +169,7 @@ class MainActivity : AppCompatActivity() { 1f, listener ) - binding.currentLocation = "listening started" + binding.currentLocation.text = "listening started" } catch (se: SecurityException) { Log.e(TAG, "Missing permission", se) } @@ -214,4 +259,70 @@ class MainActivity : AppCompatActivity() { fun onClickAmsterdam(view: View?) { setFakeLocation(52.3547498, 4.8339211) } + + private fun testFakeLocationResolverMicroG(): List { + return foundation.e.privacymodules.fakelocationdemo.microg.FakeLocationResolver.fakeLocations( + this, + ClientIdentity(packageName, myUid()), + listOf( + Location("gps").apply { + latitude = 1.0 + longitude = 2.0 + } + ) + ) + } + + private fun testFakeLocationsResolverAOSP(): List { + val baseLocation = LocationResult( + arrayListOf( + Location("gps").apply { + latitude = 1.0 + longitude = 2.0 + } + ) + ) + + val identity = CallerIdentity(myUid(), packageName) + + val result = foundation.e.privacymodules.fakelocationdemo.aosp.FakeLocationResolver + .fakeLocations(this, baseLocation, identity) + + return result.asList() + } + + private fun testFakeLocationResolverAOSP(): Location { + val baseLocation = Location("gps").apply { + latitude = 1.0 + longitude = 2.0 + } + + val identity = CallerIdentity(myUid(), packageName) + + val result = foundation.e.privacymodules.fakelocationdemo.aosp.FakeLocationResolver + .fakeLocation(this, baseLocation, identity) + + return result + } + + private fun testHasFakeLocationAOSP(): Boolean { + val identity = CallerIdentity(myUid(), packageName) + + val result = foundation.e.privacymodules.fakelocationdemo.aosp.FakeLocationResolver + .hasFakeLocation(this, identity) + + return result + } + + private fun onClickFakeLocationConfig() { + Log.d("DebugFakeLoc", "onClickFakeLocationConfig") + val test = "microg: ${testFakeLocationResolverMicroG()}" + + "\nAOSP: ${testFakeLocationsResolverAOSP()}" + + "\nAOSP one: ${testFakeLocationResolverAOSP()}" + + "\nAOSP has: ${testHasFakeLocationAOSP()}" + + binding.currentLocation.text = test + + Log.d("DebugFakeLoc", test) + } } diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/CallerIdentity.java b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/CallerIdentity.java new file mode 100644 index 0000000000000000000000000000000000000000..b29fc91045d67b88ae97630a041d411fecce999a --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/CallerIdentity.java @@ -0,0 +1,25 @@ +package foundation.e.privacymodules.fakelocationdemo.aosp; + +//import android.location.util.identity.CallerIdentity; + + +public class CallerIdentity { + + private int mUid; + + private String mPackageName; + + public CallerIdentity(int uid, String packageName) { + mUid = uid; + mPackageName = packageName; + + } + public int getUid() { + return mUid; + } + + public String getPackageName() { + return mPackageName; + } + +} diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/FakeLocationResolver.java b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/FakeLocationResolver.java new file mode 100644 index 0000000000000000000000000000000000000000..b80d9cef0d2ebbf88cac2149c130b8e6b5b7fbb1 --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/FakeLocationResolver.java @@ -0,0 +1,114 @@ +package foundation.e.privacymodules.fakelocationdemo.aosp; + +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 { + 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 LocationResult fakeLocations(Context context, LocationResult baseLocations, CallerIdentity identity) { + int uid = identity.getUid(); + String packageName = identity.getPackageName(); + + if (context == null || packageName == null || uid < 0) { + Log.w("AP-FakeLocation", "FakeLocationResolver::fakeLocations invalid parameters"); + return baseLocations; + } + FakeLocation latLon = getFakeLocation(context, packageName, uid); + if (latLon == null) return baseLocations; + + return LocationResult.wrap(overrideLatLons( baseLocations.asList(), latLon)); + } + + public static Location fakeLocation(Context context, Location baseLocation, CallerIdentity identity) { + int uid = identity.getUid(); + String packageName = identity.getPackageName(); + + if (context == null || packageName == null || uid < 0) { + Log.w("AP-FakeLocation", "FakeLocationResolver::fakeLocation invalid parameters"); + return baseLocation; + } + FakeLocation latLon = getFakeLocation(context, packageName, uid); + if (latLon == null) return baseLocation; + + return overrideLatLons( baseLocation, latLon); + } + + public static boolean hasFakeLocation(Context context, CallerIdentity identity) { + int uid = identity.getUid(); + String packageName = identity.getPackageName(); + + if (context == null || packageName == null || uid < 0) { + Log.w("AP-FakeLocation", "FakeLocationResolver::fakeLocation invalid parameters"); + return false; + } + 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 FakeLocation getFakeLocation(Context context, String packageName, int uid) { + try { + Bundle extra = new Bundle(); + extra.putInt(PARAM_UID, uid); + 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("AP-FakeLocation", "Can't getFakeLocation", e); + } + return null; + } + + private static List overrideLatLons(List baseLocations, FakeLocation latLon) { + ArrayList fakedLocations = new ArrayList(baseLocations.size()); + for (Location location: baseLocations) { + Location fakedLocation = overrideLatLons(location, latLon); + + fakedLocations.add(fakedLocation); + } + return fakedLocations; + } + + private static Location overrideLatLons(Location baseLocation, FakeLocation latLon) { + Location fakedLocation = new Location(baseLocation); + fakedLocation.setLatitude(latLon.latitude); + fakedLocation.setLongitude(latLon.longitude); + fakedLocation.setAltitude(3.0); + fakedLocation.setSpeed(0.01f); + + return fakedLocation; + } +} diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/LocationResult.java b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/LocationResult.java new file mode 100644 index 0000000000000000000000000000000000000000..16c454d2765989847d3fd3812435162e9709fe86 --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/aosp/LocationResult.java @@ -0,0 +1,22 @@ +package foundation.e.privacymodules.fakelocationdemo.aosp; + +import android.location.Location; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class LocationResult { + private ArrayList mLocations; + + public LocationResult(ArrayList locations) { + mLocations = locations; + } + public static LocationResult wrap(List locations) { + return new LocationResult(new ArrayList<>(locations)); + } + + public List asList() { + return Collections.unmodifiableList(mLocations); + } +} diff --git a/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/microg/FakeLocationResolver.kt b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/microg/FakeLocationResolver.kt new file mode 100644 index 0000000000000000000000000000000000000000..37f3352ac8182bc599bf19658950db5771c02d68 --- /dev/null +++ b/fakelocation/fakelocationdemo/src/main/java/foundation/e/privacymodules/fakelocationdemo/microg/FakeLocationResolver.kt @@ -0,0 +1,55 @@ +package foundation.e.privacymodules.fakelocationdemo.microg + +import android.content.Context +import android.location.Location +import android.net.Uri +import android.os.Bundle +import android.util.Log + +data class ClientIdentity(val packageName: String, val uid: Int) +object FakeLocationResolver { + private val FAKE_LOCATIONS_URI = "content://foundation.e.advancedprivacy.fakelocations" + + private val PARAM_UID = "uid" + private val PARAM_LATITUDE = "latitude" + private val PARAM_LONGITUDE = "longitude" + + private data class FakeLocation(var latitude: Double, var longitude: Double) + + fun fakeLocations(context: Context, identity: ClientIdentity, baseLocations: List): List { + Log.w("AP-FakeLocation", "FakeLocationResolver::fakeLocations ${identity.packageName}") + val latLon = + getFakeLocation(context, identity.packageName, identity.uid) ?: return baseLocations + Log.w("AP-FakeLocation", "FakeLocationResolver::fakeLocation ${identity.packageName} has fakelocation: $latLon") + + return baseLocations.map { overrideLatLon(it, latLon) } + } + + private fun getFakeLocation(context: Context, packageName: String, uid: Int): FakeLocation? { + return runCatching { + context.contentResolver.call( + Uri.parse(FAKE_LOCATIONS_URI), + "", + packageName, + Bundle().apply { + putInt(PARAM_UID, uid) + } + )?.let { + FakeLocation( + it.getDouble(PARAM_LATITUDE), + it.getDouble(PARAM_LONGITUDE) + ) + } + }.getOrNull() + } + + private fun overrideLatLon(baseLocation: Location, latLont: FakeLocation): Location { + val location = Location(baseLocation) + location.latitude = latLont.latitude + location.longitude = latLont.longitude + location.altitude = 3.0 + location.speed = 0.01f + + return location + } +} diff --git a/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml index 33fce699a4bc61e33b29f66650f19e19d290445c..cb5cd2c203f9cf36fccb016ef610f5ad769dece0 100644 --- a/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml +++ b/fakelocation/fakelocationdemo/src/main/res/layout/activity_main.xml @@ -15,15 +15,8 @@ ~ You should have received a copy of the GNU General Public License ~ along with this program. If not, see . --> - - - - - - - -