├── .github └── workflows │ ├── build.yml │ └── reuse.yml ├── .gitignore ├── LICENSES ├── Apache-2.0.txt ├── CC-BY-SA-4.0.txt └── CC0-1.0.txt ├── README.md ├── api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── org │ │ └── microg │ │ └── nlp │ │ └── api │ │ ├── GeocoderBackend.aidl │ │ ├── LocationBackend.aidl │ │ └── LocationCallback.aidl │ ├── java │ ├── android │ │ └── location │ │ │ └── Address.aidl │ └── org │ │ └── microg │ │ └── nlp │ │ └── api │ │ ├── AbstractBackendHelper.java │ │ ├── AbstractBackendService.java │ │ ├── BluetoothBackendHelper.java │ │ ├── CellBackendHelper.java │ │ ├── Constants.java │ │ ├── GeocoderBackendService.java │ │ ├── HelperLocationBackendService.java │ │ ├── LocationBackendService.java │ │ ├── LocationHelper.java │ │ ├── MPermissionHelperActivity.java │ │ ├── Utils.java │ │ ├── VersionUtils.java │ │ └── WiFiBackendHelper.java │ └── res │ └── values │ ├── themes.xml │ └── version.xml ├── artwork ├── ic_nlp_marker.svg └── ic_nlp_settings.svg ├── build.gradle ├── client ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── org │ │ └── microg │ │ └── nlp │ │ └── service │ │ ├── AddressCallback.aidl │ │ ├── LocationCallback.aidl │ │ └── UnifiedLocationService.aidl │ ├── java │ └── android │ │ └── location │ │ └── Address.aidl │ └── kotlin │ └── org │ └── microg │ └── nlp │ └── client │ ├── BaseClient.kt │ ├── GeocodeClient.kt │ ├── IntentResolver.kt │ ├── LocationClient.kt │ └── UnifiedLocationClient.kt ├── compat ├── build.gradle └── src │ ├── current │ └── java │ │ ├── android │ │ └── location │ │ │ ├── GeocoderParams.java │ │ │ ├── Geofence.java │ │ │ ├── Location.java │ │ │ ├── LocationManager.java │ │ │ └── LocationRequest.java │ │ └── com │ │ └── android │ │ ├── internal │ │ └── location │ │ │ ├── ProviderProperties.java │ │ │ └── ProviderRequest.java │ │ └── location │ │ └── provider │ │ ├── GeocodeProvider.java │ │ ├── LocationProviderBase.java │ │ ├── LocationRequestUnbundled.java │ │ ├── ProviderPropertiesUnbundled.java │ │ └── ProviderRequestUnbundled.java │ └── v9 │ └── java │ └── com │ └── android │ └── location │ └── provider │ └── LocationProvider.java ├── docs └── backend-sample │ ├── build.gradle │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── org │ │ └── microg │ │ └── nlp │ │ └── api │ │ └── sample │ │ ├── SampleBackendService.java │ │ ├── SecondSampleService.java │ │ ├── SecondSettings.java │ │ └── ThirdSampleService.java │ └── res │ └── values │ └── strings.xml ├── geocode-v1 ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── org │ └── microg │ └── nlp │ └── geocode │ └── v1 │ ├── GeocodeProvider.kt │ └── GeocodeService.kt ├── gradle.properties ├── gradle ├── publish.gradle └── wrapper │ ├── gradle-wrapper.jar │ ├── gradle-wrapper.jar.license │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── location-v2 ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── org │ └── microg │ └── nlp │ └── location │ └── v2 │ ├── LocationProvider.kt │ └── LocationService.kt ├── location-v3 ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── org │ └── microg │ └── nlp │ └── location │ └── v3 │ └── LocationService.kt ├── patches └── android_frameworks_base-N.patch ├── service-api ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── aidl │ └── org │ │ └── microg │ │ └── nlp │ │ └── service │ │ └── api │ │ ├── GeocodeRequest.aidl │ │ ├── IAddressesCallback.aidl │ │ ├── IGeocodeService.aidl │ │ ├── ILocationListener.aidl │ │ ├── ILocationService.aidl │ │ ├── IStatusCallback.aidl │ │ ├── IStringsCallback.aidl │ │ ├── LocationRequest.aidl │ │ └── ReverseGeocodeRequest.aidl │ └── java │ └── org │ └── microg │ └── nlp │ └── service │ └── api │ ├── Constants.java │ ├── GeocodeRequest.java │ ├── LatLon.java │ ├── LatLonBounds.java │ ├── LocationRequest.java │ └── ReverseGeocodeRequest.java ├── service ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ └── kotlin │ └── org │ └── microg │ └── nlp │ └── service │ ├── AbstractBackendHelper.kt │ ├── AsyncGeocoderBackend.kt │ ├── AsyncLocationBackend.kt │ ├── GeocodeFuser.kt │ ├── GeocodeService.kt │ ├── LocationFuser.kt │ ├── LocationService.kt │ ├── PackageChangedReceiver.kt │ ├── Preferences.kt │ ├── UnifiedLocationServiceEntryPoint.kt │ ├── UnifiedLocationServiceInstance.kt │ └── UnifiedLocationServiceRoot.kt ├── settings.gradle └── ui ├── build.gradle └── src └── main ├── AndroidManifest.xml ├── kotlin └── org │ └── microg │ └── nlp │ └── ui │ ├── ActivityResultProcessor.kt │ ├── BackendConfiguration.kt │ ├── BackendDetailsFragment.kt │ ├── BackendListFragment.kt │ ├── BackendSettingsActivity.kt │ ├── binding │ └── BindingAdapter.kt │ └── model │ ├── BackendDetailsCallback.kt │ ├── BackendInfo.kt │ └── BackendListEntryCallback.kt └── res ├── layout ├── backend_details.xml ├── backend_list.xml ├── backend_list_entry.xml └── backend_settings_activity.xml ├── navigation └── nav_unlp.xml ├── values-be └── strings.xml ├── values-de └── strings.xml ├── values-eo └── strings.xml ├── values-es └── strings.xml ├── values-fr └── strings.xml ├── values-in └── strings.xml ├── values-it └── strings.xml ├── values-pl └── strings.xml ├── values-ro └── strings.xml ├── values-ru └── strings.xml ├── values-sr └── strings.xml ├── values-uk └── strings.xml ├── values-zh-rTW └── strings.xml └── values └── strings.xml /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021, microG Project Team 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | name: Build 5 | on: [pull_request, push] 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | with: 12 | fetch-depth: 0 13 | - run: ./gradlew --no-daemon build 14 | env: 15 | TERM: dumb 16 | JAVA_OPTS: -Xmx2048m 17 | -------------------------------------------------------------------------------- /.github/workflows/reuse.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021, microG Project Team 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | name: REUSE Compliance Check 5 | on: [pull_request, push] 6 | jobs: 7 | test: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | - uses: fsfe/reuse-action@v1 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2013, microG Project Team 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | *.iml 5 | gen/ 6 | bin/ 7 | build/ 8 | user.gradle 9 | .gradle/ 10 | local.properties 11 | .idea/ 12 | BuildConfig.java 13 | -------------------------------------------------------------------------------- /LICENSES/CC0-1.0.txt: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES 4 | NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE 5 | AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION 6 | ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE 7 | OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS 8 | LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION 9 | OR WORKS PROVIDED HEREUNDER. 10 | 11 | Statement of Purpose 12 | 13 | The laws of most jurisdictions throughout the world automatically confer exclusive 14 | Copyright and Related Rights (defined below) upon the creator and subsequent 15 | owner(s) (each and all, an "owner") of an original work of authorship and/or 16 | a database (each, a "Work"). 17 | 18 | Certain owners wish to permanently relinquish those rights to a Work for the 19 | purpose of contributing to a commons of creative, cultural and scientific 20 | works ("Commons") that the public can reliably and without fear of later claims 21 | of infringement build upon, modify, incorporate in other works, reuse and 22 | redistribute as freely as possible in any form whatsoever and for any purposes, 23 | including without limitation commercial purposes. These owners may contribute 24 | to the Commons to promote the ideal of a free culture and the further production 25 | of creative, cultural and scientific works, or to gain reputation or greater 26 | distribution for their Work in part through the use and efforts of others. 27 | 28 | For these and/or other purposes and motivations, and without any expectation 29 | of additional consideration or compensation, the person associating CC0 with 30 | a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 31 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 32 | and publicly distribute the Work under its terms, with knowledge of his or 33 | her Copyright and Related Rights in the Work and the meaning and intended 34 | legal effect of CC0 on those rights. 35 | 36 | 1. Copyright and Related Rights. A Work made available under CC0 may be protected 37 | by copyright and related or neighboring rights ("Copyright and Related Rights"). 38 | Copyright and Related Rights include, but are not limited to, the following: 39 | 40 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 41 | and translate a Work; 42 | 43 | ii. moral rights retained by the original author(s) and/or performer(s); 44 | 45 | iii. publicity and privacy rights pertaining to a person's image or likeness 46 | depicted in a Work; 47 | 48 | iv. rights protecting against unfair competition in regards to a Work, subject 49 | to the limitations in paragraph 4(a), below; 50 | 51 | v. rights protecting the extraction, dissemination, use and reuse of data 52 | in a Work; 53 | 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal protection 56 | of databases, and under any national implementation thereof, including any 57 | amended or successor version of such directive); and 58 | 59 | vii. other similar, equivalent or corresponding rights throughout the world 60 | based on applicable law or treaty, and any national implementations thereof. 61 | 62 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 63 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 64 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 65 | and Related Rights and associated claims and causes of action, whether now 66 | known or unknown (including existing as well as future claims and causes of 67 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 68 | duration provided by applicable law or treaty (including future time extensions), 69 | (iii) in any current or future medium and for any number of copies, and (iv) 70 | for any purpose whatsoever, including without limitation commercial, advertising 71 | or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the 72 | benefit of each member of the public at large and to the detriment of Affirmer's 73 | heirs and successors, fully intending that such Waiver shall not be subject 74 | to revocation, rescission, cancellation, termination, or any other legal or 75 | equitable action to disrupt the quiet enjoyment of the Work by the public 76 | as contemplated by Affirmer's express Statement of Purpose. 77 | 78 | 3. Public License Fallback. Should any part of the Waiver for any reason be 79 | judged legally invalid or ineffective under applicable law, then the Waiver 80 | shall be preserved to the maximum extent permitted taking into account Affirmer's 81 | express Statement of Purpose. In addition, to the extent the Waiver is so 82 | judged Affirmer hereby grants to each affected person a royalty-free, non 83 | transferable, non sublicensable, non exclusive, irrevocable and unconditional 84 | license to exercise Affirmer's Copyright and Related Rights in the Work (i) 85 | in all territories worldwide, (ii) for the maximum duration provided by applicable 86 | law or treaty (including future time extensions), (iii) in any current or 87 | future medium and for any number of copies, and (iv) for any purpose whatsoever, 88 | including without limitation commercial, advertising or promotional purposes 89 | (the "License"). The License shall be deemed effective as of the date CC0 90 | was applied by Affirmer to the Work. Should any part of the License for any 91 | reason be judged legally invalid or ineffective under applicable law, such 92 | partial invalidity or ineffectiveness shall not invalidate the remainder of 93 | the License, and in such case Affirmer hereby affirms that he or she will 94 | not (i) exercise any of his or her remaining Copyright and Related Rights 95 | in the Work or (ii) assert any associated claims and causes of action with 96 | respect to the Work, in either case contrary to Affirmer's express Statement 97 | of Purpose. 98 | 99 | 4. Limitations and Disclaimers. 100 | 101 | a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, 102 | licensed or otherwise affected by this document. 103 | 104 | b. Affirmer offers the Work as-is and makes no representations or warranties 105 | of any kind concerning the Work, express, implied, statutory or otherwise, 106 | including without limitation warranties of title, merchantability, fitness 107 | for a particular purpose, non infringement, or the absence of latent or other 108 | defects, accuracy, or the present or absence of errors, whether or not discoverable, 109 | all to the greatest extent permissible under applicable law. 110 | 111 | c. Affirmer disclaims responsibility for clearing rights of other persons 112 | that may apply to the Work or any use thereof, including without limitation 113 | any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims 114 | responsibility for obtaining any necessary consents, permissions or other 115 | rights required for any use of the Work. 116 | 117 | d. Affirmer understands and acknowledges that Creative Commons is not a party 118 | to this document and has no duty or obligation with respect to this CC0 or 119 | use of the Work. 120 | -------------------------------------------------------------------------------- /api/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | apply plugin: 'maven-publish' 8 | apply plugin: 'signing' 9 | 10 | android { 11 | compileSdkVersion androidCompileSdk 12 | buildToolsVersion "$androidBuildVersionTools" 13 | 14 | defaultConfig { 15 | versionName version 16 | minSdkVersion androidMinSdk 17 | targetSdkVersion androidTargetSdk 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility = 1.8 22 | targetCompatibility = 1.8 23 | } 24 | } 25 | 26 | apply from: '../gradle/publish.gradle' 27 | 28 | description = 'API interfaces and helpers to create backends for UnifiedNlp' 29 | -------------------------------------------------------------------------------- /api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 14 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /api/src/main/aidl/org/microg/nlp/api/GeocoderBackend.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Intent; 9 | import android.location.Location; 10 | import android.location.Address; 11 | 12 | interface GeocoderBackend { 13 | void open(); 14 | List
getFromLocation(double latitude, double longitude, int maxResults, String locale); 15 | List
getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, 16 | double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, 17 | String locale); 18 | void close(); 19 | Intent getInitIntent(); 20 | Intent getSettingsIntent(); 21 | Intent getAboutIntent(); 22 | List
getFromLocationWithOptions(double latitude, double longitude, int maxResults, 23 | String locale, in Bundle options); 24 | List
getFromLocationNameWithOptions(String locationName, int maxResults, 25 | double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, 26 | double upperRightLongitude, String locale, in Bundle options); 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/aidl/org/microg/nlp/api/LocationBackend.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import org.microg.nlp.api.LocationCallback; 9 | import android.content.Intent; 10 | import android.location.Location; 11 | 12 | interface LocationBackend { 13 | void open(LocationCallback callback); 14 | Location update(); 15 | void close(); 16 | Intent getInitIntent(); 17 | Intent getSettingsIntent(); 18 | Intent getAboutIntent(); 19 | Location updateWithOptions(in Bundle options); 20 | } 21 | -------------------------------------------------------------------------------- /api/src/main/aidl/org/microg/nlp/api/LocationCallback.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.location.Location; 9 | 10 | interface LocationCallback { 11 | void report(in Location location); 12 | } 13 | -------------------------------------------------------------------------------- /api/src/main/java/android/location/Address.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2008, The Android Open Source Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package android.location; 7 | parcelable Address; 8 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/AbstractBackendHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Context; 9 | import android.util.Log; 10 | 11 | @SuppressWarnings("WeakerAccess") 12 | public class AbstractBackendHelper { 13 | public static final String TAG = "BackendHelper"; 14 | protected final Context context; 15 | protected State state = State.DISABLED; 16 | protected boolean currentDataUsed = true; 17 | protected long lastUpdate = 0; 18 | 19 | public AbstractBackendHelper(Context context) { 20 | if (context == null) 21 | throw new IllegalArgumentException("context must not be null"); 22 | this.context = context; 23 | } 24 | 25 | /** 26 | * Call this in {@link org.microg.nlp.api.LocationBackendService#onOpen()}. 27 | */ 28 | public synchronized void onOpen() { 29 | if (state == State.WAITING || state == State.SCANNING) { 30 | Log.w(TAG, "Do not call onOpen if not closed before"); 31 | } 32 | currentDataUsed = true; 33 | state = State.WAITING; 34 | } 35 | 36 | /** 37 | * Call this in {@link org.microg.nlp.api.LocationBackendService#onClose()}. 38 | */ 39 | public synchronized void onClose() { 40 | if (state == State.DISABLED || state == State.DISABLING) { 41 | Log.w(TAG, "Do not call onClose if not opened before"); 42 | return; 43 | } 44 | if (state == State.WAITING) { 45 | state = State.DISABLED; 46 | } else { 47 | state = State.DISABLING; 48 | } 49 | } 50 | 51 | /** 52 | * Call this in {@link org.microg.nlp.api.LocationBackendService#update()}. 53 | */ 54 | public synchronized void onUpdate() { 55 | } 56 | 57 | public String[] getRequiredPermissions() { 58 | return new String[0]; 59 | } 60 | 61 | protected enum State {DISABLED, WAITING, SCANNING, DISABLING} 62 | } 63 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/AbstractBackendService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.app.Service; 9 | import android.content.Intent; 10 | import android.os.IBinder; 11 | import android.util.Log; 12 | 13 | @SuppressWarnings({"WeakerAccess", "unused"}) 14 | public abstract class AbstractBackendService extends Service { 15 | 16 | public static final String TAG = "BackendService"; 17 | 18 | @Override 19 | public IBinder onBind(Intent intent) { 20 | return getBackend(); 21 | } 22 | 23 | /** 24 | * Called after a connection was setup 25 | */ 26 | protected void onOpen() { 27 | 28 | } 29 | 30 | /** 31 | * Called before connection closure 32 | */ 33 | protected void onClose() { 34 | 35 | } 36 | 37 | protected Intent getInitIntent() { 38 | return null; 39 | } 40 | 41 | @SuppressWarnings("SameReturnValue") 42 | protected Intent getSettingsIntent() { 43 | return null; 44 | } 45 | 46 | @SuppressWarnings("SameReturnValue") 47 | protected Intent getAboutIntent() { 48 | return null; 49 | } 50 | 51 | @Override 52 | public boolean onUnbind(Intent intent) { 53 | try { 54 | disconnect(); 55 | } catch (Exception e) { 56 | Log.w(TAG, e); 57 | } 58 | return super.onUnbind(intent); 59 | } 60 | 61 | public abstract void disconnect(); 62 | 63 | protected abstract IBinder getBackend(); 64 | 65 | protected String getServiceApiVersion() { 66 | return Utils.getServiceApiVersion(this); 67 | } 68 | 69 | protected String getSelfApiVersion() { 70 | return Utils.getSelfApiVersion(this); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/BluetoothBackendHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.bluetooth.BluetoothAdapter; 9 | import android.bluetooth.BluetoothDevice; 10 | import android.content.BroadcastReceiver; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.content.IntentFilter; 14 | 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | import static android.Manifest.permission.ACCESS_COARSE_LOCATION; 19 | import static android.Manifest.permission.BLUETOOTH; 20 | import static android.Manifest.permission.BLUETOOTH_ADMIN; 21 | 22 | /** 23 | * Utility class to support backend using Device for geolocation. 24 | */ 25 | @SuppressWarnings({"MissingPermission", "WeakerAccess", "unused"}) 26 | public class BluetoothBackendHelper extends AbstractBackendHelper { 27 | private final static IntentFilter bluetoothBroadcastFilter = 28 | new IntentFilter(); 29 | 30 | private final Listener listener; 31 | private final BluetoothAdapter bluetoothAdapter; 32 | private final Set devices = new HashSet<>(); 33 | private final BroadcastReceiver bluetoothBroadcastReceiver = new BroadcastReceiver() { 34 | @Override 35 | public void onReceive(Context context, Intent intent) { 36 | String action = intent.getAction(); 37 | if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(action)) { 38 | devices.clear(); 39 | } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(action)) { 40 | onBluetoothChanged(); 41 | } else if (BluetoothDevice.ACTION_FOUND.equals(action)) { 42 | BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE); 43 | if (device != null) { 44 | int rssi = intent.getShortExtra(BluetoothDevice.EXTRA_RSSI, Short.MIN_VALUE); 45 | Device deviceDiscovered = new Device(device.getAddress(), device.getName(), rssi); 46 | devices.add(deviceDiscovered); 47 | } 48 | } 49 | } 50 | }; 51 | 52 | public BluetoothBackendHelper(Context context, Listener listener){ 53 | super(context); 54 | if (listener == null) 55 | throw new IllegalArgumentException("listener must not be null"); 56 | this.listener = listener; 57 | this.bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); 58 | bluetoothBroadcastFilter.addAction(BluetoothDevice.ACTION_FOUND); 59 | bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 60 | bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 61 | } 62 | 63 | public synchronized void onOpen() { 64 | super.onOpen(); 65 | bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_STARTED); 66 | bluetoothBroadcastFilter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); 67 | bluetoothBroadcastFilter.addAction(BluetoothDevice.ACTION_FOUND); 68 | context.registerReceiver(bluetoothBroadcastReceiver, bluetoothBroadcastFilter); 69 | } 70 | 71 | public synchronized void onClose() { 72 | super.onClose(); 73 | context.unregisterReceiver(bluetoothBroadcastReceiver); 74 | } 75 | 76 | public synchronized void onUpdate() { 77 | if (!currentDataUsed) { 78 | listener.onDevicesChanged(getDevices()); 79 | } else { 80 | scanBluetooth(); 81 | } 82 | } 83 | 84 | @Override 85 | public String[] getRequiredPermissions() { 86 | return new String[]{BLUETOOTH, BLUETOOTH_ADMIN, ACCESS_COARSE_LOCATION}; 87 | } 88 | 89 | private void onBluetoothChanged() { 90 | if (loadBluetooths()) { 91 | listener.onDevicesChanged(getDevices()); 92 | } 93 | } 94 | 95 | private synchronized boolean scanBluetooth() { 96 | if (state == State.DISABLED) 97 | return false; 98 | if (bluetoothAdapter.isEnabled()) { 99 | state = State.SCANNING; 100 | bluetoothAdapter.startDiscovery(); 101 | return true; 102 | } 103 | return false; 104 | } 105 | 106 | private synchronized boolean loadBluetooths() { 107 | currentDataUsed = false; 108 | if (state == State.DISABLING) 109 | state = State.DISABLED; 110 | switch (state) { 111 | default: 112 | case DISABLED: 113 | return false; 114 | case SCANNING: 115 | state = State.WAITING; 116 | return true; 117 | } 118 | } 119 | 120 | public synchronized Set getDevices() { 121 | currentDataUsed = true; 122 | return new HashSet<>(devices); 123 | } 124 | 125 | public interface Listener { 126 | void onDevicesChanged(Set device); 127 | } 128 | 129 | public static class Device { 130 | private final String bssid; 131 | private final String name; 132 | private final int rssi; 133 | 134 | public String getBssid() { return bssid; } 135 | 136 | public String getName() {return name; } 137 | 138 | public int getRssi() { return rssi; } 139 | 140 | public Device(String bssid, String name, int rssi) { 141 | this.bssid = Utils.wellFormedMac(bssid); 142 | this.name = name; 143 | this.rssi = rssi; 144 | } 145 | 146 | @Override 147 | public String toString() { 148 | return "Device{" + 149 | "name=" + name + 150 | ", bssid=" + bssid + 151 | ", rssi=" + rssi + 152 | "}"; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | public class Constants { 9 | public static final String ACTION_LOCATION_BACKEND = "org.microg.nlp.LOCATION_BACKEND"; 10 | public static final String ACTION_GEOCODER_BACKEND = "org.microg.nlp.GEOCODER_BACKEND"; 11 | public static final String ACTION_RELOAD_SETTINGS = "org.microg.nlp.RELOAD_SETTINGS"; 12 | public static final String ACTION_FORCE_LOCATION = "org.microg.nlp.FORCE_LOCATION"; 13 | public static final String PERMISSION_FORCE_LOCATION = "org.microg.permission.FORCE_COARSE_LOCATION"; 14 | public static final String INTENT_EXTRA_LOCATION = "location"; 15 | @Deprecated 16 | public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "SERVICE_BACKEND_PROVIDER"; 17 | @Deprecated 18 | public static final String LOCATION_EXTRA_BACKEND_COMPONENT = "SERVICE_BACKEND_COMPONENT"; 19 | @Deprecated 20 | public static final String LOCATION_EXTRA_OTHER_BACKENDS = "OTHER_BACKEND_RESULTS"; 21 | public static final String METADATA_BACKEND_SETTINGS_ACTIVITY = "org.microg.nlp.BACKEND_SETTINGS_ACTIVITY"; 22 | public static final String METADATA_BACKEND_ABOUT_ACTIVITY = "org.microg.nlp.BACKEND_ABOUT_ACTIVITY"; 23 | public static final String METADATA_BACKEND_INIT_ACTIVITY = "org.microg.nlp.BACKEND_INIT_ACTIVITY"; 24 | public static final String METADATA_BACKEND_SUMMARY = "org.microg.nlp.BACKEND_SUMMARY"; 25 | public static final String METADATA_API_VERSION = "org.microg.nlp.API_VERSION"; 26 | public static final String API_VERSION = "3"; 27 | } 28 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/GeocoderBackendService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Intent; 9 | import android.location.Address; 10 | import android.os.Bundle; 11 | import android.os.IBinder; 12 | 13 | import java.util.List; 14 | 15 | @SuppressWarnings({"WeakerAccess", "unused"}) 16 | public abstract class GeocoderBackendService extends AbstractBackendService { 17 | 18 | private final Backend backend = new Backend(); 19 | private boolean connected = false; 20 | 21 | @Override 22 | protected IBinder getBackend() { 23 | return backend; 24 | } 25 | 26 | @Override 27 | public void disconnect() { 28 | if (connected) { 29 | onClose(); 30 | connected = false; 31 | } 32 | } 33 | 34 | /** 35 | * @param locale The locale, formatted as a String with underscore (eg. en_US) the resulting 36 | * address should be localized in 37 | * @see android.location.Geocoder#getFromLocation(double, double, int) 38 | */ 39 | protected abstract List
getFromLocation(double latitude, double longitude, int maxResults, String locale); 40 | 41 | protected List
getFromLocation(double latitude, double longitude, int maxResults, String locale, Bundle options) { 42 | return getFromLocation(latitude, longitude, maxResults, locale); 43 | } 44 | 45 | /** 46 | * @param locale The locale, formatted as a String with underscore (eg. en_US) the resulting 47 | * address should be localized in 48 | * @see android.location.Geocoder#getFromLocationName(String, int, double, double, double, double) 49 | */ 50 | protected abstract List
getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale); 51 | 52 | 53 | protected List
getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale, Bundle options) { 54 | return getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale); 55 | } 56 | 57 | private class Backend extends GeocoderBackend.Stub { 58 | 59 | @Override 60 | public void open() { 61 | onOpen(); 62 | connected = true; 63 | } 64 | 65 | @Override 66 | public List
getFromLocation(double latitude, double longitude, int maxResults, String locale) { 67 | return GeocoderBackendService.this 68 | .getFromLocation(latitude, longitude, maxResults, locale); 69 | } 70 | 71 | @Override 72 | public List
getFromLocationWithOptions(double latitude, double longitude, int maxResults, String locale, Bundle options) { 73 | return GeocoderBackendService.this.getFromLocation(latitude, longitude, maxResults, locale, options); 74 | } 75 | 76 | 77 | @Override 78 | public List
getFromLocationName(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale) { 79 | return GeocoderBackendService.this 80 | .getFromLocationName(locationName, maxResults, lowerLeftLatitude, 81 | lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale); 82 | } 83 | 84 | @Override 85 | public List
getFromLocationNameWithOptions(String locationName, int maxResults, double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, double upperRightLongitude, String locale, Bundle options) { 86 | return GeocoderBackendService.this.getFromLocationName(locationName, maxResults, lowerLeftLatitude, lowerLeftLongitude, upperRightLatitude, upperRightLongitude, locale, options); 87 | } 88 | 89 | @Override 90 | public void close() { 91 | disconnect(); 92 | } 93 | 94 | @Override 95 | public Intent getInitIntent() { 96 | return GeocoderBackendService.this.getInitIntent(); 97 | } 98 | 99 | @Override 100 | public Intent getSettingsIntent() { 101 | return GeocoderBackendService.this.getSettingsIntent(); 102 | } 103 | 104 | @Override 105 | public Intent getAboutIntent() { 106 | return GeocoderBackendService.this.getAboutIntent(); 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/HelperLocationBackendService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Intent; 9 | import android.content.pm.PackageManager; 10 | import android.location.Location; 11 | import android.os.Build; 12 | 13 | import java.util.Arrays; 14 | import java.util.HashSet; 15 | import java.util.Iterator; 16 | import java.util.LinkedList; 17 | import java.util.List; 18 | import java.util.Set; 19 | 20 | import static android.Manifest.permission.ACCESS_BACKGROUND_LOCATION; 21 | import static android.Manifest.permission.ACCESS_COARSE_LOCATION; 22 | import static android.Manifest.permission.ACCESS_FINE_LOCATION; 23 | 24 | @SuppressWarnings("unused") 25 | public abstract class HelperLocationBackendService extends LocationBackendService { 26 | 27 | private boolean opened; 28 | private final Set helpers = new HashSet<>(); 29 | 30 | public synchronized void addHelper(AbstractBackendHelper helper) { 31 | helpers.add(helper); 32 | if (opened) { 33 | helper.onOpen(); 34 | } 35 | } 36 | 37 | public synchronized void removeHelpers() { 38 | if (opened) { 39 | for (AbstractBackendHelper helper : helpers) { 40 | helper.onClose(); 41 | } 42 | } 43 | helpers.clear(); 44 | } 45 | 46 | @Override 47 | protected synchronized void onOpen() { 48 | for (AbstractBackendHelper helper : helpers) { 49 | helper.onOpen(); 50 | } 51 | opened = true; 52 | } 53 | 54 | @Override 55 | protected synchronized void onClose() { 56 | for (AbstractBackendHelper helper : helpers) { 57 | helper.onClose(); 58 | } 59 | opened = false; 60 | } 61 | 62 | @Override 63 | protected synchronized Location update() { 64 | for (AbstractBackendHelper helper : helpers) { 65 | helper.onUpdate(); 66 | } 67 | return null; 68 | } 69 | 70 | @Override 71 | protected Intent getInitIntent() { 72 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 73 | // Consider permissions 74 | List perms = new LinkedList<>(); 75 | for (AbstractBackendHelper helper : helpers) { 76 | perms.addAll(Arrays.asList(helper.getRequiredPermissions())); 77 | } 78 | // Request background location permission if needed as we are likely to run in background 79 | if (Build.VERSION.SDK_INT >= 29 && (perms.contains(ACCESS_COARSE_LOCATION) || perms.contains(ACCESS_FINE_LOCATION))) { 80 | perms.add(ACCESS_BACKGROUND_LOCATION); 81 | } 82 | for (Iterator iterator = perms.iterator(); iterator.hasNext(); ) { 83 | String perm = iterator.next(); 84 | if (checkSelfPermission(perm) == PackageManager.PERMISSION_GRANTED) { 85 | iterator.remove(); 86 | } 87 | } 88 | if (perms.isEmpty()) return null; 89 | Intent intent = new Intent(this, MPermissionHelperActivity.class); 90 | intent.putExtra(MPermissionHelperActivity.EXTRA_PERMISSIONS, perms.toArray(new String[0])); 91 | return intent; 92 | } 93 | return super.getInitIntent(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/LocationBackendService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Intent; 9 | import android.location.Location; 10 | import android.os.Bundle; 11 | import android.os.IBinder; 12 | import android.os.RemoteException; 13 | 14 | @SuppressWarnings({"WeakerAccess", "unused"}) 15 | public abstract class LocationBackendService extends AbstractBackendService { 16 | 17 | private LocationCallback callback; 18 | private Location waiting; 19 | 20 | /** 21 | * Called, whenever an app requires a location update. This can be a single or a repeated request. 22 | *

23 | * You may return null if your backend has no newer location available then the last one. 24 | * Do not send the same {@link android.location.Location} twice, if it's not based on updated/refreshed data. 25 | *

26 | * You can completely ignore this method (means returning null) if you use {@link #report(android.location.Location)}. 27 | * 28 | * @return a new {@link android.location.Location} instance or null if not available. 29 | */ 30 | @SuppressWarnings("SameReturnValue") 31 | protected Location update() { 32 | return null; 33 | } 34 | 35 | protected Location update(Bundle options) { 36 | return update(); 37 | } 38 | 39 | /** 40 | * Directly report a {@link android.location.Location} to the requesting apps. Use this if your updates are based 41 | * on environment changes (eg. cell id change). 42 | * 43 | * @param location the new {@link android.location.Location} instance to be send 44 | */ 45 | public void report(Location location) { 46 | if (callback != null) { 47 | try { 48 | callback.report(location); 49 | } catch (android.os.DeadObjectException e) { 50 | waiting = location; 51 | callback = null; 52 | } catch (RemoteException e) { 53 | waiting = location; 54 | } 55 | } else { 56 | waiting = location; 57 | } 58 | } 59 | 60 | /** 61 | * @return true if we're an actively connected backend, false if not 62 | */ 63 | public boolean isConnected() { 64 | return callback != null; 65 | } 66 | 67 | @Override 68 | protected IBinder getBackend() { 69 | return new Backend(); 70 | } 71 | 72 | @Override 73 | public void disconnect() { 74 | if (callback != null) { 75 | onClose(); 76 | callback = null; 77 | } 78 | } 79 | 80 | private class Backend extends LocationBackend.Stub { 81 | @Override 82 | public void open(LocationCallback callback) throws RemoteException { 83 | LocationBackendService.this.callback = callback; 84 | if (waiting != null) { 85 | callback.report(waiting); 86 | waiting = null; 87 | } 88 | onOpen(); 89 | } 90 | 91 | @Override 92 | public Location update() { 93 | return LocationBackendService.this.update(); 94 | } 95 | 96 | @Override 97 | public Location updateWithOptions(Bundle options) { 98 | return LocationBackendService.this.update(options); 99 | } 100 | 101 | @Override 102 | public void close() { 103 | disconnect(); 104 | } 105 | 106 | @Override 107 | public Intent getInitIntent() { 108 | return LocationBackendService.this.getInitIntent(); 109 | } 110 | 111 | @Override 112 | public Intent getSettingsIntent() { 113 | return LocationBackendService.this.getSettingsIntent(); 114 | } 115 | 116 | @Override 117 | public Intent getAboutIntent() { 118 | return LocationBackendService.this.getAboutIntent(); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/LocationHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.location.Location; 9 | import android.os.Bundle; 10 | 11 | import java.util.Collection; 12 | 13 | @SuppressWarnings({"WeakerAccess", "unused"}) 14 | public final class LocationHelper { 15 | public static final String EXTRA_AVERAGED_OF = "AVERAGED_OF"; 16 | public static final String EXTRA_TOTAL_WEIGHT = "org.microg.nlp.TOTAL_WEIGHT"; 17 | public static final String EXTRA_TOTAL_ALTITUDE_WEIGHT = "org.microg.nlp.TOTAL_ALTITUDE_WEIGHT"; 18 | public static final String EXTRA_WEIGHT = "org.microg.nlp.WEIGHT"; 19 | 20 | private LocationHelper() { 21 | } 22 | 23 | public static Location create(String source) { 24 | Location l = new Location(source); 25 | l.setTime(System.currentTimeMillis()); 26 | return l; 27 | } 28 | 29 | public static Location create(String source, double latitude, double longitude, float accuracy) { 30 | Location location = create(source); 31 | location.setLatitude(latitude); 32 | location.setLongitude(longitude); 33 | location.setAccuracy(accuracy); 34 | return location; 35 | } 36 | 37 | public static Location create(String source, double latitude, double longitude, float altitude, Bundle extras) { 38 | Location location = create(source, latitude, longitude, altitude); 39 | location.setExtras(extras); 40 | return location; 41 | } 42 | 43 | public static Location create(String source, double latitude, double longitude, double altitude, float accuracy) { 44 | Location location = create(source, latitude, longitude, accuracy); 45 | location.setAltitude(altitude); 46 | return location; 47 | } 48 | 49 | public static Location create(String source, double latitude, double longitude, double altitude, float accuracy, Bundle extras) { 50 | Location location = create(source, latitude, longitude, altitude, accuracy); 51 | location.setExtras(extras); 52 | return location; 53 | } 54 | 55 | public static Location create(String source, long time) { 56 | Location location = create(source); 57 | location.setTime(time); 58 | return location; 59 | } 60 | 61 | public static Location create(String source, long time, Bundle extras) { 62 | Location location = create(source, time); 63 | location.setExtras(extras); 64 | return location; 65 | } 66 | 67 | public static Location average(String source, Collection locations) { 68 | return weightedAverage(source, locations, LocationBalance.BALANCED, new Bundle()); 69 | } 70 | 71 | public static Location weightedAverage(String source, Collection locations, LocationBalance balance, Bundle extras) { 72 | if (locations == null || locations.isEmpty()) { 73 | return null; 74 | } 75 | double total = 0; 76 | double lat = 0; 77 | double lon = 0; 78 | float acc = 0; 79 | double altTotal = 0; 80 | double alt = 0; 81 | for (Location value : locations) { 82 | if (value != null) { 83 | double weight = balance.getWeight(value); 84 | total += weight; 85 | lat += value.getLatitude() * weight; 86 | lon += value.getLongitude() * weight; 87 | acc += value.getAccuracy() * weight; 88 | if (value.hasAltitude()) { 89 | alt += value.getAltitude(); 90 | altTotal += weight; 91 | } 92 | } 93 | } 94 | if (extras == null) extras = new Bundle(); 95 | extras.putInt(EXTRA_AVERAGED_OF, locations.size()); 96 | extras.putDouble(EXTRA_TOTAL_WEIGHT, total); 97 | if (altTotal > 0) { 98 | extras.putDouble(EXTRA_TOTAL_ALTITUDE_WEIGHT, altTotal); 99 | return create(source, lat / total, lon / total, alt / altTotal, (float) (acc / total), extras); 100 | } else { 101 | return create(source, lat / total, lon / total, (float) (acc / total), extras); 102 | } 103 | } 104 | 105 | public interface LocationBalance { 106 | LocationBalance BALANCED = location -> 1; 107 | LocationBalance FROM_EXTRA = location -> location.getExtras() == null ? 1 : location.getExtras().getDouble(EXTRA_WEIGHT, 1); 108 | 109 | double getWeight(Location location); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/MPermissionHelperActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.annotation.TargetApi; 9 | import android.app.Activity; 10 | import android.content.pm.PackageManager; 11 | import android.os.Build; 12 | import android.os.Bundle; 13 | 14 | @TargetApi(Build.VERSION_CODES.M) 15 | public class MPermissionHelperActivity extends Activity { 16 | public static final String EXTRA_PERMISSIONS = "org.microg.nlp.api.mperms"; 17 | private static final int REQUEST_CODE_PERMS = 1; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | String[] mperms = getIntent().getStringArrayExtra(EXTRA_PERMISSIONS); 23 | if (mperms == null || mperms.length == 0) { 24 | setResult(RESULT_OK); 25 | finish(); 26 | } else { 27 | requestPermissions(mperms, REQUEST_CODE_PERMS); 28 | } 29 | } 30 | 31 | @Override 32 | public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { 33 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 34 | boolean ok = true; 35 | for (int result : grantResults) { 36 | if (result != PackageManager.PERMISSION_GRANTED) ok = false; 37 | } 38 | setResult(ok ? RESULT_OK : RESULT_CANCELED); 39 | finish(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Context; 9 | import android.content.pm.ApplicationInfo; 10 | import android.content.pm.PackageManager; 11 | import android.util.Log; 12 | 13 | @SuppressWarnings("WeakerAccess") 14 | public class Utils { 15 | 16 | /** 17 | * Bring a mac address to the form 01:23:45:AB:CD:EF 18 | * 19 | * @param mac address to be well-formed 20 | * @return well-formed mac address 21 | */ 22 | public static String wellFormedMac(String mac) { 23 | int HEX_RADIX = 16; 24 | int[] bytes = new int[6]; 25 | String[] splitAtColon = mac.split(":"); 26 | if (splitAtColon.length == 6) { 27 | for (int i = 0; i < 6; ++i) { 28 | bytes[i] = Integer.parseInt(splitAtColon[i], HEX_RADIX); 29 | } 30 | } else { 31 | String[] splitAtLine = mac.split("-"); 32 | if (splitAtLine.length == 6) { 33 | for (int i = 0; i < 6; ++i) { 34 | bytes[i] = Integer.parseInt(splitAtLine[i], HEX_RADIX); 35 | } 36 | } else if (mac.length() == 12) { 37 | for (int i = 0; i < 6; ++i) { 38 | bytes[i] = Integer.parseInt(mac.substring(i * 2, (i + 1) * 2), HEX_RADIX); 39 | } 40 | } else if (mac.length() == 17) { 41 | for (int i = 0; i < 6; ++i) { 42 | bytes[i] = Integer.parseInt(mac.substring(i * 3, (i * 3) + 2), HEX_RADIX); 43 | } 44 | } else { 45 | throw new IllegalArgumentException("Can't read this string as mac address"); 46 | 47 | } 48 | } 49 | StringBuilder sb = new StringBuilder(); 50 | for (int i = 0; i < 6; ++i) { 51 | String hex = Integer.toHexString(bytes[i]); 52 | if (hex.length() == 1) { 53 | hex = "0" + hex; 54 | } 55 | if (sb.length() != 0) 56 | sb.append(":"); 57 | sb.append(hex); 58 | } 59 | return sb.toString(); 60 | } 61 | 62 | public static String getPackageApiVersion(Context context, String packageName) { 63 | PackageManager pm = context.getPackageManager(); 64 | ApplicationInfo applicationInfo; 65 | try { 66 | applicationInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA); 67 | } catch (PackageManager.NameNotFoundException e) { 68 | return null; 69 | } 70 | return applicationInfo.metaData == null ? null 71 | : applicationInfo.metaData.getString(Constants.METADATA_API_VERSION); 72 | } 73 | 74 | public static String getServiceApiVersion(Context context) { 75 | String apiVersion = getPackageApiVersion(context, "com.google.android.gms"); 76 | if (apiVersion == null) 77 | apiVersion = getPackageApiVersion(context, "com.google.android.location"); 78 | if (apiVersion == null) apiVersion = getPackageApiVersion(context, "org.microg.nlp"); 79 | return apiVersion; 80 | } 81 | 82 | public static String getSelfApiVersion(Context context) { 83 | String apiVersion = getPackageApiVersion(context, context.getPackageName()); 84 | if (!Constants.API_VERSION.equals(apiVersion)) { 85 | Log.w("VersionUtil", "You did not specify the currently used api version in your manifest.\n" + 86 | "When using gradle + aar, this should be done automatically, if not, add the\n" + 87 | "following to your tag\n" + 88 | ""); 90 | apiVersion = Constants.API_VERSION; 91 | } 92 | return apiVersion; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /api/src/main/java/org/microg/nlp/api/VersionUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api; 7 | 8 | import android.content.Context; 9 | 10 | @SuppressWarnings("unused") 11 | @Deprecated 12 | public class VersionUtils { 13 | 14 | public static String getPackageApiVersion(Context context, String packageName) { 15 | return Utils.getPackageApiVersion(context, packageName); 16 | } 17 | 18 | public static String getServiceApiVersion(Context context) { 19 | return Utils.getServiceApiVersion(context); 20 | } 21 | 22 | public static String getSelfApiVersion(Context context) { 23 | return Utils.getSelfApiVersion(context); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /api/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 15 | -------------------------------------------------------------------------------- /api/src/main/res/values/version.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 3 9 | -------------------------------------------------------------------------------- /artwork/ic_nlp_marker.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /artwork/ic_nlp_settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | buildscript { 7 | ext.kotlinVersion = '1.6.10' 8 | ext.coroutineVersion = '1.5.2' 9 | 10 | ext.appcompatVersion = '1.4.0' 11 | ext.fragmentVersion = '1.4.0' 12 | ext.lifecycleVersion = '2.4.0' 13 | ext.navigationVersion = '2.3.5' 14 | ext.preferenceVersion = '1.1.1' 15 | ext.recyclerviewVersion = '1.2.0' 16 | 17 | ext.androidBuildGradleVersion = '7.0.4' 18 | 19 | ext.androidBuildVersionTools = '30.0.2' 20 | 21 | ext.androidMinSdk = 9 22 | ext.androidTargetSdk = 29 23 | ext.androidCompileSdk = 31 24 | 25 | repositories { 26 | jcenter() 27 | google() 28 | } 29 | 30 | dependencies { 31 | classpath "com.android.tools.build:gradle:$androidBuildGradleVersion" 32 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" 33 | } 34 | } 35 | 36 | String gitDescribeVersion() { 37 | def stdout = new ByteArrayOutputStream() 38 | if (rootProject.file("gradlew").exists()) 39 | exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout } 40 | else // automatic build system, don't tag dirty 41 | exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout } 42 | return stdout.toString().trim().substring(1) 43 | } 44 | 45 | int gitCountCommits() { 46 | def stdout = new ByteArrayOutputStream() 47 | exec { 48 | commandLine 'git', 'rev-list', '--count', "HEAD" 49 | standardOutput = stdout 50 | } 51 | return Integer.parseInt(stdout.toString().trim()) 52 | } 53 | 54 | allprojects { 55 | apply plugin: 'idea' 56 | 57 | group = 'org.microg.nlp' 58 | version = gitDescribeVersion() 59 | ext.appVersionCode = gitCountCommits() 60 | 61 | gradle.projectsEvaluated { 62 | tasks.withType(JavaCompile) { 63 | options.compilerArgs << "-Xlint:deprecation" 64 | } 65 | } 66 | } 67 | 68 | subprojects { 69 | repositories { 70 | jcenter() 71 | google() 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /client/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | apply plugin: 'kotlin-android' 8 | apply plugin: 'maven-publish' 9 | apply plugin: 'signing' 10 | 11 | android { 12 | compileSdkVersion androidCompileSdk 13 | buildToolsVersion "$androidBuildVersionTools" 14 | 15 | defaultConfig { 16 | versionName version 17 | minSdkVersion androidMinSdk 18 | targetSdkVersion androidTargetSdk 19 | } 20 | 21 | sourceSets { 22 | main.java.srcDirs += 'src/main/kotlin' 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility = 1.8 27 | targetCompatibility = 1.8 28 | } 29 | } 30 | 31 | apply from: '../gradle/publish.gradle' 32 | 33 | description = 'UnifiedNlp client library' 34 | 35 | dependencies { 36 | api project(":service-api") 37 | 38 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 39 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" 40 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" 41 | } 42 | -------------------------------------------------------------------------------- /client/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /client/src/main/aidl/org/microg/nlp/service/AddressCallback.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service; 7 | 8 | import android.location.Location; 9 | 10 | interface AddressCallback { 11 | void onResult(in List

location); 12 | } 13 | -------------------------------------------------------------------------------- /client/src/main/aidl/org/microg/nlp/service/LocationCallback.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service; 7 | 8 | import android.location.Location; 9 | 10 | interface LocationCallback { 11 | void onLocationUpdate(in Location location); 12 | } 13 | -------------------------------------------------------------------------------- /client/src/main/aidl/org/microg/nlp/service/UnifiedLocationService.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service; 7 | 8 | import android.location.Location; 9 | import org.microg.nlp.service.AddressCallback; 10 | import org.microg.nlp.service.LocationCallback; 11 | 12 | interface UnifiedLocationService { 13 | void registerLocationCallback(LocationCallback callback, in Bundle options); 14 | void setUpdateInterval(long interval, in Bundle options); 15 | void requestSingleUpdate(in Bundle options); 16 | 17 | void getFromLocationWithOptions(double latitude, double longitude, int maxResults, 18 | String locale, in Bundle options, AddressCallback callback); 19 | void getFromLocationNameWithOptions(String locationName, int maxResults, 20 | double lowerLeftLatitude, double lowerLeftLongitude, double upperRightLatitude, 21 | double upperRightLongitude, String locale, in Bundle options, AddressCallback callback); 22 | 23 | void reloadPreferences(); 24 | String[] getLocationBackends(); 25 | void setLocationBackends(in String[] backends); 26 | String[] getGeocoderBackends(); 27 | void setGeocoderBackends(in String[] backends); 28 | 29 | Location getLastLocation(); 30 | Location getLastLocationForBackend(String packageName, String className, String signatureDigest); 31 | } 32 | -------------------------------------------------------------------------------- /client/src/main/java/android/location/Address.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2008, The Android Open Source Project 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package android.location; 7 | parcelable Address; 8 | -------------------------------------------------------------------------------- /client/src/main/kotlin/org/microg/nlp/client/BaseClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.client 7 | 8 | import android.content.ComponentName 9 | import android.content.Context 10 | import android.content.Context.BIND_ABOVE_CLIENT 11 | import android.content.Context.BIND_AUTO_CREATE 12 | import android.content.Intent 13 | import android.content.ServiceConnection 14 | import android.location.Location 15 | import android.os.Bundle 16 | import android.os.IBinder 17 | import android.util.Log 18 | import androidx.lifecycle.Lifecycle 19 | import androidx.lifecycle.LifecycleOwner 20 | import kotlinx.coroutines.sync.Mutex 21 | import kotlinx.coroutines.sync.withLock 22 | import org.microg.nlp.service.api.Constants 23 | import org.microg.nlp.service.api.ILocationListener 24 | import org.microg.nlp.service.api.IStatusCallback 25 | import org.microg.nlp.service.api.IStringsCallback 26 | import java.util.concurrent.LinkedBlockingQueue 27 | import java.util.concurrent.ThreadPoolExecutor 28 | import java.util.concurrent.TimeUnit 29 | import java.util.concurrent.atomic.AtomicInteger 30 | import kotlin.coroutines.Continuation 31 | import kotlin.coroutines.resume 32 | import kotlin.coroutines.resumeWithException 33 | import kotlin.coroutines.suspendCoroutine 34 | 35 | private const val TAG = "BaseClient" 36 | 37 | abstract class BaseClient(val context: Context, private val lifecycle: Lifecycle, val asInterface: (IBinder) -> I) : LifecycleOwner { 38 | private val callbackThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue()) 39 | private var persistedConnectionCounter = AtomicInteger(0) 40 | private val serviceConnectionMutex = Mutex() 41 | private var persistedServiceConnection: ContinuedServiceConnection? = null 42 | val defaultOptions = Bundle() 43 | 44 | abstract val action: String 45 | 46 | val intent: Intent? 47 | get() = resolveIntent(context, action) 48 | 49 | val isAvailable: Boolean 50 | get() = intent != null 51 | 52 | var packageName: String 53 | get() = defaultOptions.getString("packageName") ?: context.packageName 54 | set(value) = defaultOptions.putString("packageName", value) 55 | 56 | init { 57 | packageName = context.packageName 58 | } 59 | 60 | val isConnectedUnsafe: Boolean 61 | get() = persistedServiceConnection != null && persistedConnectionCounter.get() > 0 62 | 63 | suspend fun isConnected(): Boolean = serviceConnectionMutex.withLock { 64 | return persistedServiceConnection != null && persistedConnectionCounter.get() > 0 65 | } 66 | 67 | suspend fun connect() { 68 | serviceConnectionMutex.withLock { 69 | if (persistedServiceConnection == null) { 70 | val intent = intent ?: throw IllegalStateException("$action service is not available") 71 | persistedServiceConnection = suspendCoroutine { continuation -> 72 | context.bindService(intent, ContinuedServiceConnection(continuation), BIND_AUTO_CREATE or BIND_ABOVE_CLIENT) 73 | } 74 | } 75 | persistedConnectionCounter.incrementAndGet() 76 | } 77 | } 78 | 79 | suspend fun disconnect() { 80 | serviceConnectionMutex.withLock { 81 | if (persistedConnectionCounter.decrementAndGet() <= 0) { 82 | persistedServiceConnection?.let { context.unbindService(it) } 83 | persistedServiceConnection = null 84 | persistedConnectionCounter.set(0) 85 | } 86 | } 87 | } 88 | 89 | fun withConnectedServiceSync(v: (I) -> T): T { 90 | try { 91 | if (persistedConnectionCounter.incrementAndGet() <= 1) { 92 | throw IllegalStateException("Service not connected") 93 | } 94 | val service = persistedServiceConnection?.service ?: throw RuntimeException("No binder returned") 95 | return v(asInterface(service)) 96 | } finally { 97 | persistedConnectionCounter.decrementAndGet() 98 | } 99 | } 100 | 101 | suspend fun withService(v: suspend (I) -> T): T { 102 | connect() 103 | val service = persistedServiceConnection?.service ?: throw RuntimeException("No binder returned") 104 | return try { 105 | v(asInterface(service)) 106 | } finally { 107 | disconnect() 108 | } 109 | } 110 | 111 | override fun getLifecycle(): Lifecycle = lifecycle 112 | } 113 | 114 | internal class ContinuedServiceConnection(private val continuation: Continuation) : ServiceConnection { 115 | var service: IBinder? = null 116 | private var continued: Boolean = false 117 | 118 | override fun onServiceConnected(name: ComponentName?, service: IBinder?) { 119 | this.service = service 120 | if (!continued) { 121 | continued = true 122 | continuation.resume(this) 123 | } 124 | } 125 | 126 | override fun onServiceDisconnected(name: ComponentName?) { 127 | if (!continued) { 128 | continued = true 129 | continuation.resumeWithException(RuntimeException("Disconnected")) 130 | } 131 | } 132 | 133 | override fun onBindingDied(name: ComponentName?) { 134 | if (!continued) { 135 | continued = true 136 | continuation.resumeWithException(RuntimeException("Binding diead")) 137 | } 138 | } 139 | 140 | override fun onNullBinding(name: ComponentName?) { 141 | if (!continued) { 142 | continued = true 143 | continuation.resume(this) 144 | } 145 | } 146 | } 147 | 148 | internal class StringsCallback(private val continuation: Continuation>) : IStringsCallback.Stub() { 149 | override fun onStrings(statusCode: Int, strings: MutableList) { 150 | if (statusCode == Constants.STATUS_OK) { 151 | continuation.resume(strings) 152 | } else { 153 | continuation.resumeWithException(RuntimeException("Status: $statusCode")) 154 | } 155 | } 156 | } 157 | 158 | internal class StatusCallback(private val continuation: Continuation) : IStatusCallback.Stub() { 159 | override fun onStatus(statusCode: Int) { 160 | if (statusCode == Constants.STATUS_OK) { 161 | continuation.resume(Unit) 162 | } else { 163 | continuation.resumeWithException(RuntimeException("Status: $statusCode")) 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /client/src/main/kotlin/org/microg/nlp/client/GeocodeClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.client 7 | 8 | import android.content.Context 9 | import android.location.Address 10 | import android.os.Bundle 11 | import androidx.lifecycle.Lifecycle 12 | import org.microg.nlp.service.api.* 13 | import org.microg.nlp.service.api.Constants.STATUS_OK 14 | import java.util.concurrent.* 15 | import kotlin.coroutines.Continuation 16 | import kotlin.coroutines.resume 17 | import kotlin.coroutines.resumeWithException 18 | import kotlin.coroutines.suspendCoroutine 19 | 20 | class GeocodeClient(context: Context, lifecycle: Lifecycle) : BaseClient(context, lifecycle, { IGeocodeService.Stub.asInterface(it) }) { 21 | private val syncThreads: ThreadPoolExecutor = ThreadPoolExecutor(1, Runtime.getRuntime().availableProcessors(), 1, TimeUnit.SECONDS, LinkedBlockingQueue()) 22 | override val action: String 23 | get() = Constants.ACTION_GEOCODE 24 | 25 | fun requestGeocodeSync(request: GeocodeRequest, options: Bundle = defaultOptions): List
= executeWithTimeout { 26 | withConnectedServiceSync { service -> 27 | service.requestGeocodeSync(request, options) 28 | } 29 | } 30 | 31 | suspend fun requestGeocode(request: GeocodeRequest, options: Bundle = defaultOptions): List
= withService { service -> 32 | suspendCoroutine { 33 | service.requestGeocode(request, AddressesCallback(it), options) 34 | } 35 | } 36 | 37 | fun requestReverseGeocodeSync(request: ReverseGeocodeRequest, options: Bundle = defaultOptions): List
= executeWithTimeout { 38 | withConnectedServiceSync { service -> 39 | service.requestReverseGeocodeSync(request, options) 40 | } 41 | } 42 | 43 | suspend fun requestReverseGeocode(request: ReverseGeocodeRequest, options: Bundle = defaultOptions): List
= withService { service -> 44 | suspendCoroutine { 45 | service.requestReverseGeocode(request, AddressesCallback(it), options) 46 | } 47 | } 48 | 49 | suspend fun getGeocodeBackends(options: Bundle = defaultOptions): List = withService { service -> 50 | suspendCoroutine { 51 | service.getGeocodeBackends(StringsCallback(it), options) 52 | } 53 | } 54 | 55 | suspend fun setGeocodeBackends(backends: List, options: Bundle = defaultOptions): Unit = withService { service -> 56 | suspendCoroutine { 57 | service.setGeocodeBackends(backends, StatusCallback(it), options) 58 | } 59 | } 60 | 61 | private fun executeWithTimeout(timeout: Long = CALL_TIMEOUT, action: () -> T): T { 62 | var result: T? = null 63 | val latch = CountDownLatch(1) 64 | var err: Exception? = null 65 | syncThreads.execute { 66 | try { 67 | result = action() 68 | } catch (e: Exception) { 69 | err = e 70 | } finally { 71 | latch.countDown() 72 | } 73 | } 74 | if (!latch.await(timeout, TimeUnit.MILLISECONDS)) 75 | throw TimeoutException() 76 | err?.let { throw it } 77 | return result ?: throw NullPointerException() 78 | } 79 | 80 | companion object { 81 | private const val CALL_TIMEOUT = 10000L 82 | } 83 | } 84 | 85 | private class AddressesCallback(private val continuation: Continuation>) : IAddressesCallback.Stub() { 86 | override fun onAddresses(statusCode: Int, addresses: List
?) { 87 | if (statusCode == STATUS_OK) { 88 | continuation.resume(addresses.orEmpty()) 89 | } else { 90 | continuation.resumeWithException(RuntimeException("Status: $statusCode")) 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /client/src/main/kotlin/org/microg/nlp/client/IntentResolver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.client 7 | 8 | import android.content.Context 9 | import android.content.Intent 10 | import android.content.pm.ApplicationInfo 11 | import android.content.pm.ResolveInfo 12 | import android.util.Log 13 | 14 | internal fun resolveIntent(context: Context, action: String): Intent? { 15 | val intent = Intent(action) 16 | 17 | val pm = context.packageManager 18 | var resolveInfos = pm.queryIntentServices(intent, 0) 19 | if (resolveInfos.size > 1) { 20 | 21 | // Restrict to self if possible 22 | val isSelf: (it: ResolveInfo) -> Boolean = { 23 | it.serviceInfo.packageName == context.packageName 24 | } 25 | if (resolveInfos.size > 1 && resolveInfos.any(isSelf)) { 26 | Log.d("IntentResolver", "Found more than one service for $action, restricted to own package " + context.packageName) 27 | resolveInfos = resolveInfos.filter(isSelf) 28 | } 29 | 30 | // Restrict to package with matching signature if possible 31 | val isSelfSig: (it: ResolveInfo) -> Boolean = { 32 | it.serviceInfo.packageName == context.packageName 33 | } 34 | if (resolveInfos.size > 1 && resolveInfos.any(isSelfSig)) { 35 | Log.d("IntentResolver", "Found more than one service for $action, restricted to related packages") 36 | resolveInfos = resolveInfos.filter(isSelfSig) 37 | } 38 | 39 | // Restrict to system if any package is system 40 | val isSystem: (it: ResolveInfo) -> Boolean = { 41 | (it.serviceInfo?.applicationInfo?.flags ?: 0) and ApplicationInfo.FLAG_SYSTEM > 0 42 | } 43 | if (resolveInfos.size > 1 && resolveInfos.any(isSystem)) { 44 | Log.d("IntentResolver", "Found more than one service for $action, restricted to system packages") 45 | resolveInfos = resolveInfos.filter(isSystem) 46 | } 47 | 48 | val highestPriority: ResolveInfo? = resolveInfos.maxByOrNull { it.priority } 49 | intent.setPackage(highestPriority!!.serviceInfo.packageName) 50 | intent.setClassName(highestPriority.serviceInfo.packageName, highestPriority.serviceInfo.name) 51 | if (resolveInfos.size > 1) { 52 | Log.d("IntentResolver", "Found more than one service for $action, picked highest priority " + intent.component) 53 | } 54 | return intent 55 | } else if (!resolveInfos.isEmpty()) { 56 | intent.setPackage(resolveInfos[0].serviceInfo.packageName) 57 | return intent 58 | } else { 59 | Log.w("IntentResolver", "No service to bind to, your system does not support unified service") 60 | return null 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /client/src/main/kotlin/org/microg/nlp/client/LocationClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.client 7 | 8 | import android.content.ComponentName 9 | import android.content.Context 10 | import android.content.ServiceConnection 11 | import android.location.Location 12 | import android.os.Bundle 13 | import androidx.lifecycle.Lifecycle 14 | import kotlinx.coroutines.sync.Mutex 15 | import kotlinx.coroutines.sync.withLock 16 | import org.microg.nlp.service.api.* 17 | import kotlin.coroutines.Continuation 18 | import kotlin.coroutines.resume 19 | import kotlin.coroutines.resumeWithException 20 | import kotlin.coroutines.suspendCoroutine 21 | 22 | class LocationClient(context: Context, lifecycle: Lifecycle) : BaseClient(context, lifecycle, { ILocationService.Stub.asInterface(it) }) { 23 | private val requests = hashSetOf() 24 | private val requestsMutex = Mutex(false) 25 | 26 | override val action: String 27 | get() = Constants.ACTION_LOCATION 28 | 29 | suspend fun getLastLocation(options: Bundle = defaultOptions): Location? = withService { service -> 30 | suspendCoroutine { 31 | service.getLastLocation(SingleLocationListener(it), options) 32 | } 33 | } 34 | 35 | suspend fun getLastLocationForBackend(componentName: ComponentName, options: Bundle = defaultOptions) = getLastLocationForBackend(componentName.packageName, componentName.className, null, options) 36 | 37 | suspend fun getLastLocationForBackend(packageName: String, className: String, signatureDigest: String? = null, options: Bundle = defaultOptions): Location? = withService { service -> 38 | suspendCoroutine { 39 | service.getLastLocationForBackend(packageName, className, signatureDigest, SingleLocationListener(it), options) 40 | } 41 | } 42 | 43 | private suspend fun withRequestService(v: suspend (ILocationService) -> T): T { 44 | return requestsMutex.withLock { 45 | try { 46 | if (requests.isEmpty()) connect() 47 | withService(v) 48 | } finally { 49 | if (requests.isEmpty()) disconnect() 50 | } 51 | } 52 | } 53 | 54 | suspend fun updateLocationRequest(request: LocationRequest, options: Bundle = defaultOptions): Unit = withRequestService { service -> 55 | suspendCoroutine { 56 | service.updateLocationRequest(request, StatusCallback(it), options) 57 | } 58 | requests.removeAll { it.id == request.id } 59 | requests.add(request) 60 | } 61 | 62 | suspend fun cancelLocationRequestByListener(listener: ILocationListener, options: Bundle = defaultOptions): Unit = withRequestService { service -> 63 | suspendCoroutine { 64 | service.cancelLocationRequestByListener(listener, StatusCallback(it), options) 65 | } 66 | requests.removeAll { it.listener == listener } 67 | } 68 | 69 | suspend fun cancelLocationRequestById(id: String, options: Bundle = defaultOptions): Unit = withRequestService { service -> 70 | suspendCoroutine { 71 | service.cancelLocationRequestById(id, StatusCallback(it), options) 72 | } 73 | requests.removeAll { it.id == id } 74 | } 75 | 76 | suspend fun forceLocationUpdate(options: Bundle = defaultOptions): Unit = withService { service -> 77 | suspendCoroutine { 78 | service.forceLocationUpdate(StatusCallback(it), options) 79 | } 80 | } 81 | 82 | suspend fun getLocationBackends(options: Bundle = defaultOptions): List = withService { service -> 83 | suspendCoroutine { 84 | service.getLocationBackends(StringsCallback(it), options) 85 | } 86 | } 87 | 88 | suspend fun setLocationBackends(backends: List, options: Bundle = defaultOptions): Unit = withService { service -> 89 | suspendCoroutine { 90 | service.setLocationBackends(backends, StatusCallback(it), options) 91 | } 92 | } 93 | } 94 | 95 | private class SingleLocationListener(private val continuation: Continuation) : ILocationListener.Stub() { 96 | override fun onLocation(statusCode: Int, location: Location?) { 97 | if (statusCode == Constants.STATUS_OK) { 98 | continuation.resume(location) 99 | } else { 100 | continuation.resumeWithException(RuntimeException("Status: $statusCode")) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /compat/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'java' 7 | 8 | def sdkDir = System.env.ANDROID_HOME 9 | if (!sdkDir) { 10 | Properties properties = new Properties() 11 | properties.load(project.rootProject.file('local.properties').newDataInputStream()) 12 | sdkDir = properties.getProperty('sdk.dir') 13 | } 14 | 15 | sourceSets { 16 | main { 17 | java { 18 | srcDir 'src/current/java' 19 | srcDir 'src/v9/java' 20 | } 21 | } 22 | } 23 | 24 | dependencies { 25 | compileOnly files("$sdkDir/platforms/android-$androidCompileSdk/android.jar") 26 | } 27 | -------------------------------------------------------------------------------- /compat/src/current/java/android/location/GeocoderParams.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package android.location; 8 | 9 | import android.content.Context; 10 | import android.os.Parcel; 11 | import android.os.Parcelable; 12 | 13 | import java.util.Locale; 14 | 15 | /** 16 | * This class contains extra parameters to pass to an IGeocodeProvider 17 | * implementation from the Geocoder class. Currently this contains the 18 | * language, country and variant information from the Geocoder's locale 19 | * as well as the Geocoder client's package name for geocoder server 20 | * logging. This information is kept in a separate class to allow for 21 | * future expansion of the IGeocodeProvider interface. 22 | * 23 | * @hide 24 | */ 25 | public class GeocoderParams implements Parcelable { 26 | /** 27 | * This object is only constructed by the Geocoder class 28 | * 29 | * @hide 30 | */ 31 | public GeocoderParams(Context context, Locale locale) { 32 | } 33 | 34 | /** 35 | * returns the Geocoder's locale 36 | */ 37 | public Locale getLocale() { 38 | return null; 39 | } 40 | 41 | /** 42 | * returns the package name of the Geocoder's client 43 | */ 44 | public String getClientPackage() { 45 | return null; 46 | } 47 | 48 | public static final Parcelable.Creator CREATOR = 49 | new Parcelable.Creator() { 50 | public GeocoderParams createFromParcel(Parcel in) { 51 | return null; 52 | } 53 | 54 | public GeocoderParams[] newArray(int size) { 55 | return null; 56 | } 57 | }; 58 | 59 | public int describeContents() { 60 | return 0; 61 | } 62 | 63 | public void writeToParcel(Parcel parcel, int flags) { 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /compat/src/current/java/android/location/Geofence.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package android.location; 8 | 9 | import android.os.Parcel; 10 | import android.os.Parcelable; 11 | 12 | /** 13 | * Represents a geographical boundary, also known as a geofence. 14 | * 15 | *

Currently only circular geofences are supported and they do not support altitude changes. 16 | * 17 | * @hide 18 | */ 19 | public final class Geofence implements Parcelable { 20 | /** @hide */ 21 | public static final int TYPE_HORIZONTAL_CIRCLE = 1; 22 | 23 | private final int mType; 24 | private final double mLatitude; 25 | private final double mLongitude; 26 | private final float mRadius; 27 | 28 | /** 29 | * Create a circular geofence (on a flat, horizontal plane). 30 | * 31 | * @param latitude latitude in degrees, between -90 and +90 inclusive 32 | * @param longitude longitude in degrees, between -180 and +180 inclusive 33 | * @param radius radius in meters 34 | * @return a new geofence 35 | * @throws IllegalArgumentException if any parameters are out of range 36 | */ 37 | public static Geofence createCircle(double latitude, double longitude, float radius) { 38 | return new Geofence(latitude, longitude, radius); 39 | } 40 | 41 | private Geofence(double latitude, double longitude, float radius) { 42 | checkRadius(radius); 43 | checkLatLong(latitude, longitude); 44 | mType = TYPE_HORIZONTAL_CIRCLE; 45 | mLatitude = latitude; 46 | mLongitude = longitude; 47 | mRadius = radius; 48 | } 49 | 50 | /** @hide */ 51 | public int getType() { 52 | return mType; 53 | } 54 | 55 | /** @hide */ 56 | public double getLatitude() { 57 | return mLatitude; 58 | } 59 | 60 | /** @hide */ 61 | public double getLongitude() { 62 | return mLongitude; 63 | } 64 | 65 | /** @hide */ 66 | public float getRadius() { 67 | return mRadius; 68 | } 69 | 70 | private static void checkRadius(float radius) { 71 | if (radius <= 0) { 72 | throw new IllegalArgumentException("invalid radius: " + radius); 73 | } 74 | } 75 | 76 | private static void checkLatLong(double latitude, double longitude) { 77 | if (latitude > 90.0 || latitude < -90.0) { 78 | throw new IllegalArgumentException("invalid latitude: " + latitude); 79 | } 80 | if (longitude > 180.0 || longitude < -180.0) { 81 | throw new IllegalArgumentException("invalid longitude: " + longitude); 82 | } 83 | } 84 | 85 | private static void checkType(int type) { 86 | if (type != TYPE_HORIZONTAL_CIRCLE) { 87 | throw new IllegalArgumentException("invalid type: " + type); 88 | } 89 | } 90 | 91 | public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { 92 | @Override 93 | public Geofence createFromParcel(Parcel in) { 94 | int type = in.readInt(); 95 | double latitude = in.readDouble(); 96 | double longitude = in.readDouble(); 97 | float radius = in.readFloat(); 98 | checkType(type); 99 | return Geofence.createCircle(latitude, longitude, radius); 100 | } 101 | @Override 102 | public Geofence[] newArray(int size) { 103 | return new Geofence[size]; 104 | } 105 | }; 106 | 107 | @Override 108 | public int describeContents() { 109 | return 0; 110 | } 111 | 112 | @Override 113 | public void writeToParcel(Parcel parcel, int flags) { 114 | parcel.writeInt(mType); 115 | parcel.writeDouble(mLatitude); 116 | parcel.writeDouble(mLongitude); 117 | parcel.writeFloat(mRadius); 118 | } 119 | 120 | private static String typeToString(int type) { 121 | switch (type) { 122 | case TYPE_HORIZONTAL_CIRCLE: 123 | return "CIRCLE"; 124 | default: 125 | checkType(type); 126 | return null; 127 | } 128 | } 129 | 130 | @Override 131 | public String toString() { 132 | return String.format("Geofence[%s %.6f, %.6f %.0fm]", 133 | typeToString(mType), mLatitude, mLongitude, mRadius); 134 | } 135 | 136 | @Override 137 | public int hashCode() { 138 | final int prime = 31; 139 | int result = 1; 140 | long temp; 141 | temp = Double.doubleToLongBits(mLatitude); 142 | result = prime * result + (int) (temp ^ (temp >>> 32)); 143 | temp = Double.doubleToLongBits(mLongitude); 144 | result = prime * result + (int) (temp ^ (temp >>> 32)); 145 | result = prime * result + Float.floatToIntBits(mRadius); 146 | result = prime * result + mType; 147 | return result; 148 | } 149 | 150 | /** 151 | * Two geofences are equal if they have identical properties. 152 | */ 153 | @Override 154 | public boolean equals(Object obj) { 155 | if (this == obj) 156 | return true; 157 | if (obj == null) 158 | return false; 159 | if (!(obj instanceof Geofence)) 160 | return false; 161 | Geofence other = (Geofence) obj; 162 | if (mRadius != other.mRadius) 163 | return false; 164 | if (mLatitude != other.mLatitude) 165 | return false; 166 | if (mLongitude != other.mLongitude) 167 | return false; 168 | if (mType != other.mType) 169 | return false; 170 | return true; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/internal/location/ProviderProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.internal.location; 8 | 9 | import android.os.Parcel; 10 | import android.os.Parcelable; 11 | 12 | /** 13 | * A Parcelable containing (legacy) location provider properties. 14 | * This object is just used inside the framework and system services. 15 | * 16 | * @hide 17 | */ 18 | public final class ProviderProperties implements Parcelable { 19 | /** 20 | * True if provider requires access to a 21 | * data network (e.g., the Internet), false otherwise. 22 | */ 23 | public final boolean mRequiresNetwork; 24 | 25 | /** 26 | * True if the provider requires access to a 27 | * satellite-based positioning system (e.g., GPS), false 28 | * otherwise. 29 | */ 30 | public final boolean mRequiresSatellite; 31 | 32 | /** 33 | * True if the provider requires access to an appropriate 34 | * cellular network (e.g., to make use of cell tower IDs), false 35 | * otherwise. 36 | */ 37 | public final boolean mRequiresCell; 38 | 39 | /** 40 | * True if the use of this provider may result in a 41 | * monetary charge to the user, false if use is free. It is up to 42 | * each provider to give accurate information. Cell (network) usage 43 | * is not considered monetary cost. 44 | */ 45 | public final boolean mHasMonetaryCost; 46 | 47 | /** 48 | * True if the provider is able to provide altitude 49 | * information, false otherwise. A provider that reports altitude 50 | * under most circumstances but may occasionally not report it 51 | * should return true. 52 | */ 53 | public final boolean mSupportsAltitude; 54 | 55 | /** 56 | * True if the provider is able to provide speed 57 | * information, false otherwise. A provider that reports speed 58 | * under most circumstances but may occasionally not report it 59 | * should return true. 60 | */ 61 | public final boolean mSupportsSpeed; 62 | 63 | /** 64 | * True if the provider is able to provide bearing 65 | * information, false otherwise. A provider that reports bearing 66 | * under most circumstances but may occasionally not report it 67 | * should return true. 68 | */ 69 | public final boolean mSupportsBearing; 70 | 71 | /** 72 | * Power requirement for this provider. 73 | * 74 | * @return the power requirement for this provider, as one of the 75 | * constants Criteria.POWER_*. 76 | */ 77 | public final int mPowerRequirement; 78 | 79 | /** 80 | * Constant describing the horizontal accuracy returned 81 | * by this provider. 82 | * 83 | * @return the horizontal accuracy for this provider, as one of the 84 | * constants Criteria.ACCURACY_COARSE or Criteria.ACCURACY_FINE 85 | */ 86 | public final int mAccuracy; 87 | 88 | public ProviderProperties(boolean mRequiresNetwork, 89 | boolean mRequiresSatellite, boolean mRequiresCell, boolean mHasMonetaryCost, 90 | boolean mSupportsAltitude, boolean mSupportsSpeed, boolean mSupportsBearing, 91 | int mPowerRequirement, int mAccuracy) { 92 | this.mRequiresNetwork = mRequiresNetwork; 93 | this.mRequiresSatellite = mRequiresSatellite; 94 | this.mRequiresCell = mRequiresCell; 95 | this.mHasMonetaryCost = mHasMonetaryCost; 96 | this.mSupportsAltitude = mSupportsAltitude; 97 | this.mSupportsSpeed = mSupportsSpeed; 98 | this.mSupportsBearing = mSupportsBearing; 99 | this.mPowerRequirement = mPowerRequirement; 100 | this.mAccuracy = mAccuracy; 101 | } 102 | 103 | public static final Parcelable.Creator CREATOR = 104 | new Parcelable.Creator() { 105 | @Override 106 | public ProviderProperties createFromParcel(Parcel in) { 107 | return null; 108 | } 109 | 110 | @Override 111 | public ProviderProperties[] newArray(int size) { 112 | return null; 113 | } 114 | }; 115 | 116 | @Override 117 | public int describeContents() { 118 | return 0; 119 | } 120 | 121 | @Override 122 | public void writeToParcel(Parcel parcel, int flags) { 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/internal/location/ProviderRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.internal.location; 8 | 9 | import android.location.LocationRequest; 10 | import android.os.Parcel; 11 | import android.os.Parcelable; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * @hide 17 | */ 18 | public final class ProviderRequest implements Parcelable { 19 | /** 20 | * Location reporting is requested (true) 21 | */ 22 | public boolean reportLocation = false; 23 | 24 | /** 25 | * The smallest requested interval 26 | */ 27 | public long interval = Long.MAX_VALUE; 28 | 29 | /** 30 | * A more detailed set of requests. 31 | *

Location Providers can optionally use this to 32 | * fine tune location updates, for example when there 33 | * is a high power slow interval request and a 34 | * low power fast interval request. 35 | */ 36 | public List locationRequests = null; 37 | 38 | public ProviderRequest() { 39 | } 40 | 41 | public static final Parcelable.Creator CREATOR = 42 | new Parcelable.Creator() { 43 | @Override 44 | public ProviderRequest createFromParcel(Parcel in) { 45 | return null; 46 | } 47 | 48 | @Override 49 | public ProviderRequest[] newArray(int size) { 50 | return null; 51 | } 52 | }; 53 | 54 | @Override 55 | public int describeContents() { 56 | return 0; 57 | } 58 | 59 | @Override 60 | public void writeToParcel(Parcel parcel, int flags) { 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/location/provider/GeocodeProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.location.provider; 8 | 9 | import android.location.Address; 10 | import android.location.GeocoderParams; 11 | import android.os.IBinder; 12 | 13 | import java.util.List; 14 | 15 | /** 16 | * Base class for geocode providers implemented as unbundled services. 17 | *

18 | *

Geocode providers can be implemented as services and return the result of 19 | * {@link GeocodeProvider#getBinder()} in its getBinder() method. 20 | *

21 | *

IMPORTANT: This class is effectively a public API for unbundled 22 | * applications, and must remain API stable. See README.txt in the root 23 | * of this package for more information. 24 | */ 25 | public abstract class GeocodeProvider { 26 | /** 27 | * This method is overridden to implement the 28 | * {@link android.location.Geocoder#getFromLocation(double, double, int)} method. 29 | * Classes implementing this method should not hold a reference to the params parameter. 30 | */ 31 | public abstract String onGetFromLocation(double latitude, double longitude, int maxResults, 32 | GeocoderParams params, List

addrs); 33 | 34 | /** 35 | * This method is overridden to implement the 36 | * {@link android.location.Geocoder#getFromLocationName(String, int, double, double, double, double)} method. 37 | * Classes implementing this method should not hold a reference to the params parameter. 38 | */ 39 | public abstract String onGetFromLocationName(String locationName, 40 | double lowerLeftLatitude, double lowerLeftLongitude, 41 | double upperRightLatitude, double upperRightLongitude, int maxResults, 42 | GeocoderParams params, List
addrs); 43 | 44 | /** 45 | * Returns the Binder interface for the geocode provider. 46 | * This is intended to be used for the onBind() method of 47 | * a service that implements a geocoder service. 48 | * 49 | * @return the IBinder instance for the provider 50 | */ 51 | public IBinder getBinder() { 52 | return null; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/location/provider/LocationProviderBase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2010, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.location.provider; 8 | 9 | import android.location.Location; 10 | import android.location.LocationManager; 11 | import android.os.Bundle; 12 | import android.os.IBinder; 13 | import android.os.WorkSource; 14 | 15 | import java.io.FileDescriptor; 16 | import java.io.PrintWriter; 17 | 18 | /** 19 | * Base class for location providers implemented as unbundled services. 20 | *

21 | *

The network location provider must export a service with action 22 | * "com.android.location.service.v2.NetworkLocationProvider" 23 | * and a valid minor version in a meta-data field on the service, and 24 | * then return the result of {@link #getBinder()} on service binding. 25 | *

26 | *

The fused location provider must export a service with action 27 | * "com.android.location.service.FusedLocationProvider" 28 | * and a valid minor version in a meta-data field on the service, and 29 | * then return the result of {@link #getBinder()} on service binding. 30 | *

31 | *

IMPORTANT: This class is effectively a public API for unbundled 32 | * applications, and must remain API stable. See README.txt in the root 33 | * of this package for more information. 34 | */ 35 | public abstract class LocationProviderBase { 36 | 37 | /** 38 | * Bundle key for a version of the location containing no GPS data. 39 | * Allows location providers to flag locations as being safe to 40 | * feed to LocationFudger. 41 | */ 42 | public static final String EXTRA_NO_GPS_LOCATION = Location.EXTRA_NO_GPS_LOCATION; 43 | 44 | /** 45 | * Name of the Fused location provider. 46 | *

47 | *

This provider combines inputs for all possible location sources 48 | * to provide the best possible Location fix. 49 | */ 50 | public static final String FUSED_PROVIDER = LocationManager.FUSED_PROVIDER; 51 | 52 | public LocationProviderBase(String tag, ProviderPropertiesUnbundled properties) { 53 | } 54 | 55 | public IBinder getBinder() { 56 | return null; 57 | } 58 | 59 | /** 60 | * Used by the location provider to report new locations. 61 | * 62 | * @param location new Location to report 63 | *

64 | * Requires the android.permission.INSTALL_LOCATION_PROVIDER permission. 65 | */ 66 | public final void reportLocation(Location location) { 67 | } 68 | 69 | /** 70 | * Enable the location provider. 71 | *

The provider may initialize resources, but does 72 | * not yet need to report locations. 73 | */ 74 | public abstract void onEnable(); 75 | 76 | /** 77 | * Disable the location provider. 78 | *

The provider must release resources, and stop 79 | * performing work. It may no longer report locations. 80 | */ 81 | public abstract void onDisable(); 82 | 83 | /** 84 | * Set the {@link ProviderRequest} requirements for this provider. 85 | *

Each call to this method overrides all previous requests. 86 | *

This method might trigger the provider to start returning 87 | * locations, or to stop returning locations, depending on the 88 | * parameters in the request. 89 | */ 90 | public abstract void onSetRequest(ProviderRequestUnbundled request, WorkSource source); 91 | 92 | /** 93 | * Dump debug information. 94 | */ 95 | public void onDump(FileDescriptor fd, PrintWriter pw, String[] args) { 96 | } 97 | 98 | /** 99 | * Returns a information on the status of this provider. 100 | *

{@link android.location.LocationProvider#OUT_OF_SERVICE} is returned if the provider is 101 | * out of service, and this is not expected to change in the near 102 | * future; {@link android.location.LocationProvider#TEMPORARILY_UNAVAILABLE} is returned if 103 | * the provider is temporarily unavailable but is expected to be 104 | * available shortly; and {@link android.location.LocationProvider#AVAILABLE} is returned 105 | * if the provider is currently available. 106 | *

107 | *

If extras is non-null, additional status information may be 108 | * added to it in the form of provider-specific key/value pairs. 109 | */ 110 | public abstract int onGetStatus(Bundle extras); 111 | 112 | /** 113 | * Returns the time at which the status was last updated. It is the 114 | * responsibility of the provider to appropriately set this value using 115 | * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 116 | * there is a status update that it wishes to broadcast to all its 117 | * listeners. The provider should be careful not to broadcast 118 | * the same status again. 119 | * 120 | * @return time of last status update in millis since last reboot 121 | */ 122 | public abstract long onGetStatusUpdateTime(); 123 | 124 | /** 125 | * Implements addditional location provider specific additional commands. 126 | * 127 | * @param command name of the command to send to the provider. 128 | * @param extras optional arguments for the command (or null). 129 | * The provider may optionally fill the extras Bundle with results from the command. 130 | * @return true if the command succeeds. 131 | */ 132 | public boolean onSendExtraCommand(String command, Bundle extras) { 133 | return false; 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/location/provider/LocationRequestUnbundled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.location.provider; 8 | 9 | import android.location.LocationRequest; 10 | 11 | /** 12 | * This class is an interface to LocationRequests for unbundled applications. 13 | *

14 | *

IMPORTANT: This class is effectively a public API for unbundled 15 | * applications, and must remain API stable. See README.txt in the root 16 | * of this package for more information. 17 | */ 18 | public final class LocationRequestUnbundled { 19 | /** 20 | * Returned by {@link #getQuality} when requesting the most accurate locations available. 21 | *

22 | *

This may be up to 1 meter accuracy, although this is implementation dependent. 23 | */ 24 | public static final int ACCURACY_FINE = LocationRequest.ACCURACY_FINE; 25 | 26 | /** 27 | * Returned by {@link #getQuality} when requesting "block" level accuracy. 28 | *

29 | *

Block level accuracy is considered to be about 100 meter accuracy, 30 | * although this is implementation dependent. Using a coarse accuracy 31 | * such as this often consumes less power. 32 | */ 33 | public static final int ACCURACY_BLOCK = LocationRequest.ACCURACY_BLOCK; 34 | 35 | /** 36 | * Returned by {@link #getQuality} when requesting "city" level accuracy. 37 | *

38 | *

City level accuracy is considered to be about 10km accuracy, 39 | * although this is implementation dependent. Using a coarse accuracy 40 | * such as this often consumes less power. 41 | */ 42 | public static final int ACCURACY_CITY = LocationRequest.ACCURACY_CITY; 43 | 44 | /** 45 | * Returned by {@link #getQuality} when requiring no direct power impact (passive locations). 46 | *

47 | *

This location request will not trigger any active location requests, 48 | * but will receive locations triggered by other applications. Your application 49 | * will not receive any direct power blame for location work. 50 | */ 51 | public static final int POWER_NONE = LocationRequest.POWER_NONE; 52 | 53 | /** 54 | * Returned by {@link #getQuality} when requesting low power impact. 55 | *

56 | *

This location request will avoid high power location work where 57 | * possible. 58 | */ 59 | public static final int POWER_LOW = LocationRequest.POWER_LOW; 60 | 61 | /** 62 | * Returned by {@link #getQuality} when allowing high power consumption for location. 63 | *

64 | *

This location request will allow high power location work. 65 | */ 66 | public static final int POWER_HIGH = LocationRequest.POWER_HIGH; 67 | 68 | /** 69 | * Get the desired interval of this request, in milliseconds. 70 | * 71 | * @return desired interval in milliseconds, inexact 72 | */ 73 | public long getInterval() { 74 | return 0; 75 | } 76 | 77 | /** 78 | * Get the fastest interval of this request, in milliseconds. 79 | *

80 | *

The system will never provide location updates faster 81 | * than the minimum of {@link #getFastestInterval} and 82 | * {@link #getInterval}. 83 | * 84 | * @return fastest interval in milliseconds, exact 85 | */ 86 | public long getFastestInterval() { 87 | return 0; 88 | } 89 | 90 | /** 91 | * Get the quality of the request. 92 | * 93 | * @return an accuracy or power constant 94 | */ 95 | public int getQuality() { 96 | return 0; 97 | } 98 | 99 | /** 100 | * Get the minimum distance between location updates, in meters. 101 | * 102 | * @return minimum distance between location updates in meters 103 | */ 104 | public float getSmallestDisplacement() { 105 | return 0; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/location/provider/ProviderPropertiesUnbundled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.location.provider; 8 | 9 | import com.android.internal.location.ProviderProperties; 10 | 11 | /** 12 | * This class is an interface to Provider Properties for unbundled applications. 13 | *

14 | *

IMPORTANT: This class is effectively a public API for unbundled 15 | * applications, and must remain API stable. See README.txt in the root 16 | * of this package for more information. 17 | */ 18 | public final class ProviderPropertiesUnbundled { 19 | public static ProviderPropertiesUnbundled create(boolean requiresNetwork, 20 | boolean requiresSatellite, boolean requiresCell, boolean hasMonetaryCost, 21 | boolean supportsAltitude, boolean supportsSpeed, boolean supportsBearing, 22 | int powerRequirement, int accuracy) { 23 | return null; 24 | } 25 | 26 | public ProviderProperties getProviderProperties() { 27 | return null; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /compat/src/current/java/com/android/location/provider/ProviderRequestUnbundled.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2012, The Android Open Source Project 3 | * SPDX-FileCopyrightText: 2014, microG Project Team 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | package com.android.location.provider; 8 | 9 | import com.android.internal.location.ProviderRequest; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * This class is an interface to Provider Requests for unbundled applications. 15 | *

16 | *

IMPORTANT: This class is effectively a public API for unbundled 17 | * applications, and must remain API stable. See README.txt in the root 18 | * of this package for more information. 19 | */ 20 | public final class ProviderRequestUnbundled { 21 | public ProviderRequestUnbundled(ProviderRequest request) { 22 | } 23 | 24 | public boolean getReportLocation() { 25 | return false; 26 | } 27 | 28 | public long getInterval() { 29 | return 0; 30 | } 31 | 32 | /** 33 | * Never null. 34 | */ 35 | public List getLocationRequests() { 36 | return null; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/backend-sample/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.application' 7 | 8 | dependencies { 9 | implementation project(':api') 10 | } 11 | 12 | android { 13 | compileSdkVersion androidCompileSdk 14 | buildToolsVersion "$androidBuildVersionTools" 15 | 16 | defaultConfig { 17 | minSdkVersion androidMinSdk 18 | targetSdkVersion androidTargetSdk 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility JavaVersion.VERSION_1_8 23 | targetCompatibility JavaVersion.VERSION_1_8 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /docs/backend-sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | 27 | 32 | 33 | 34 | 35 | 38 | 39 | 40 | 44 | 45 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /docs/backend-sample/src/main/java/org/microg/nlp/api/sample/SampleBackendService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api.sample; 7 | 8 | import android.location.Location; 9 | import android.util.Log; 10 | import org.microg.nlp.api.LocationBackendService; 11 | import org.microg.nlp.api.LocationHelper; 12 | 13 | public class SampleBackendService extends LocationBackendService { 14 | private static final String TAG = SampleBackendService.class.getName(); 15 | 16 | @Override 17 | protected Location update() { 18 | if (System.currentTimeMillis() % 60000 > 2000) { 19 | Log.d(TAG, "I decided not to answer now..."); 20 | return null; 21 | } 22 | Location location = LocationHelper.create("sample", 42, 42, 42); 23 | Log.d(TAG, "I was asked for location and I answer: " + location); 24 | return location; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docs/backend-sample/src/main/java/org/microg/nlp/api/sample/SecondSampleService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api.sample; 7 | 8 | import android.location.Location; 9 | import android.util.Log; 10 | import org.microg.nlp.api.LocationBackendService; 11 | import org.microg.nlp.api.LocationHelper; 12 | 13 | public class SecondSampleService extends LocationBackendService { 14 | private static final String TAG = SecondSampleService.class.getName(); 15 | 16 | @Override 17 | protected Location update() { 18 | Location location = LocationHelper.create("second-sample", 13, 13, (System.currentTimeMillis() / 1000) % 100); 19 | Log.d(TAG, "I was asked for location and I answer: " + location); 20 | return location; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /docs/backend-sample/src/main/java/org/microg/nlp/api/sample/SecondSettings.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api.sample; 7 | 8 | import android.app.Activity; 9 | import android.os.Bundle; 10 | 11 | public class SecondSettings extends Activity { 12 | public void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | } 15 | } -------------------------------------------------------------------------------- /docs/backend-sample/src/main/java/org/microg/nlp/api/sample/ThirdSampleService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2013, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.api.sample; 7 | 8 | import android.location.Location; 9 | import android.util.Log; 10 | import org.microg.nlp.api.LocationBackendService; 11 | import org.microg.nlp.api.LocationHelper; 12 | 13 | import java.util.Random; 14 | 15 | public class ThirdSampleService extends LocationBackendService { 16 | private static final String TAG = ThirdSampleService.class.getName(); 17 | 18 | private Thread regular; 19 | private final Random random = new Random(); 20 | 21 | @Override 22 | protected void onOpen() { 23 | super.onOpen(); 24 | regular = new Thread(new Runnable() { 25 | @Override 26 | public void run() { 27 | synchronized (regular) { 28 | while (!regular.isInterrupted()) { 29 | try { 30 | regular.wait(60000); 31 | } catch (InterruptedException e) { 32 | return; 33 | } 34 | Location location = LocationHelper.create("random", random.nextDouble() * 90, random.nextDouble() * 90, random.nextFloat() * 90); 35 | Log.d(TAG, "Just reported: " + location); 36 | report(location); 37 | } 38 | } 39 | } 40 | }); 41 | regular.start(); 42 | } 43 | 44 | @Override 45 | protected void onClose() { 46 | if (regular != null && regular.isAlive()) { 47 | regular.interrupt(); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /docs/backend-sample/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | NetworkLocationV2-SamplePlugin 9 | 10 | -------------------------------------------------------------------------------- /geocode-v1/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | apply plugin: 'kotlin-android' 8 | apply plugin: 'maven-publish' 9 | apply plugin: 'signing' 10 | 11 | android { 12 | compileSdkVersion androidCompileSdk 13 | buildToolsVersion "$androidBuildVersionTools" 14 | 15 | defaultConfig { 16 | versionName version 17 | minSdkVersion androidMinSdk 18 | targetSdkVersion androidTargetSdk 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility = 1.8 23 | targetCompatibility = 1.8 24 | } 25 | } 26 | 27 | apply from: '../gradle/publish.gradle' 28 | 29 | description = 'UnifiedNlp service to implement Geocode API v1' 30 | 31 | dependencies { 32 | implementation project(':client') 33 | compileOnly project(':compat') 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 35 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" 36 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" 37 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" 38 | implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" 39 | } 40 | -------------------------------------------------------------------------------- /geocode-v1/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package org.microg.nlp.geocode.v1 6 | 7 | import android.content.Context 8 | import android.location.Address 9 | import android.location.GeocoderParams 10 | import android.os.Bundle 11 | import android.util.Log 12 | import androidx.lifecycle.Lifecycle 13 | import androidx.lifecycle.LifecycleOwner 14 | import com.android.location.provider.GeocodeProvider 15 | import org.microg.nlp.client.GeocodeClient 16 | import org.microg.nlp.service.api.GeocodeRequest 17 | import org.microg.nlp.service.api.LatLon 18 | import org.microg.nlp.service.api.LatLonBounds 19 | import org.microg.nlp.service.api.ReverseGeocodeRequest 20 | 21 | class GeocodeProvider(context: Context, lifecycle: Lifecycle) : GeocodeProvider() { 22 | private val client: GeocodeClient = GeocodeClient(context, lifecycle) 23 | 24 | override fun onGetFromLocation(latitude: Double, longitude: Double, maxResults: Int, params: GeocoderParams, addrs: MutableList

): String? { 25 | return try { 26 | handleResult(addrs, client.requestReverseGeocodeSync( 27 | ReverseGeocodeRequest(LatLon(latitude, longitude), maxResults, params.locale), 28 | Bundle().apply { putString("packageName", params.clientPackage) } 29 | )) 30 | } catch (e: Exception) { 31 | Log.w(TAG, e) 32 | e.message 33 | } 34 | } 35 | 36 | override fun onGetFromLocationName(locationName: String?, lowerLeftLatitude: Double, lowerLeftLongitude: Double, upperRightLatitude: Double, upperRightLongitude: Double, maxResults: Int, params: GeocoderParams, addrs: MutableList
): String? { 37 | return try { 38 | handleResult(addrs, client.requestGeocodeSync( 39 | GeocodeRequest(locationName!!, LatLonBounds(LatLon(lowerLeftLatitude, lowerLeftLongitude), LatLon(upperRightLatitude, upperRightLongitude)), maxResults, params.locale), 40 | Bundle().apply { putString("packageName", params.clientPackage) } 41 | )) 42 | } catch (e: Exception) { 43 | Log.w(TAG, e) 44 | e.message 45 | } 46 | } 47 | 48 | private fun handleResult(realResult: MutableList
, fuserResult: List
): String? { 49 | return if (fuserResult.isEmpty()) { 50 | "no result" 51 | } else { 52 | realResult.addAll(fuserResult) 53 | null 54 | } 55 | } 56 | 57 | suspend fun connect() = client.connect() 58 | suspend fun disconnect() = client.disconnect() 59 | 60 | companion object { 61 | private const val TAG = "GeocodeProvider" 62 | private const val TIMEOUT: Long = 10000 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /geocode-v1/src/main/java/org/microg/nlp/geocode/v1/GeocodeService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package org.microg.nlp.geocode.v1 6 | 7 | import android.content.Intent 8 | import android.os.IBinder 9 | import android.util.Log 10 | import androidx.lifecycle.LifecycleService 11 | import androidx.lifecycle.lifecycleScope 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.runBlocking 14 | 15 | class GeocodeService : LifecycleService() { 16 | private lateinit var provider: GeocodeProvider 17 | 18 | override fun onCreate() { 19 | super.onCreate() 20 | Log.d(TAG, "Creating system service...") 21 | provider = GeocodeProvider(this, lifecycle) 22 | lifecycleScope.launchWhenStarted { 23 | delay(5000) 24 | provider.connect() 25 | } 26 | Log.d(TAG, "Created system service.") 27 | } 28 | 29 | override fun onBind(intent: Intent): IBinder? { 30 | super.onBind(intent) 31 | Log.d(TAG, "onBind: $intent") 32 | return provider.binder 33 | } 34 | 35 | override fun onUnbind(intent: Intent): Boolean { 36 | return super.onUnbind(intent) 37 | } 38 | 39 | override fun onDestroy() { 40 | runBlocking { provider.disconnect() } 41 | super.onDestroy() 42 | } 43 | 44 | companion object { 45 | private const val TAG = "GeocodeService" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2020, microG Project Team 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | #android.enableJetifier=true 5 | android.useAndroidX=true 6 | -------------------------------------------------------------------------------- /gradle/publish.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | task androidSourcesJar(type: Jar) { 7 | classifier = 'sources' 8 | from android.sourceSets.main.java.source 9 | } 10 | 11 | artifacts { 12 | archives androidSourcesJar 13 | } 14 | 15 | afterEvaluate { 16 | publishing { 17 | publications { 18 | release(MavenPublication) { 19 | pom { 20 | name = project.name 21 | description = project.description 22 | url = 'https://github.com/microg/UnifiedNlp' 23 | licenses { 24 | license { 25 | name = 'The Apache Software License, Version 2.0' 26 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 27 | } 28 | } 29 | developers { 30 | developer { 31 | id = 'microg' 32 | name = 'microG Team' 33 | } 34 | } 35 | scm { 36 | url = 'https://github.com/microg/UnifiedNlp' 37 | connection = 'scm:git:https://github.com/microg/UnifiedNlp.git' 38 | developerConnection = 'scm:git:ssh://github.com/microg/UnifiedNlp.git' 39 | } 40 | } 41 | 42 | from components.release 43 | artifact androidSourcesJar 44 | } 45 | } 46 | if (project.hasProperty('sonatype.username')) { 47 | repositories { 48 | maven { 49 | name = 'sonatype' 50 | url = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' 51 | credentials { 52 | username project.getProperty('sonatype.username') 53 | password project.getProperty('sonatype.password') 54 | } 55 | } 56 | } 57 | } 58 | } 59 | if (project.hasProperty('signing.keyId')) { 60 | signing { 61 | sign publishing.publications 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microg/UnifiedNlp/38a857f034d688fe19bcf41030d31478d90668a0/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar.license: -------------------------------------------------------------------------------- 1 | Copyright 2015 the original author or authors. 2 | SPDX-License-Identifier: Apache-2.0 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2015, microG Project Team 2 | # SPDX-License-Identifier: CC0-1.0 3 | 4 | distributionBase=GRADLE_USER_HOME 5 | distributionPath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem SPDX-License-Identifier: Apache-2.0 2 | @rem 3 | @rem Copyright 2015 the original author or authors. 4 | @rem 5 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 6 | @rem you may not use this file except in compliance with the License. 7 | @rem You may obtain a copy of the License at 8 | @rem 9 | @rem https://www.apache.org/licenses/LICENSE-2.0 10 | @rem 11 | @rem Unless required by applicable law or agreed to in writing, software 12 | @rem distributed under the License is distributed on an "AS IS" BASIS, 13 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | @rem See the License for the specific language governing permissions and 15 | @rem limitations under the License. 16 | @rem 17 | 18 | @if "%DEBUG%" == "" @echo off 19 | @rem ########################################################################## 20 | @rem 21 | @rem Gradle startup script for Windows 22 | @rem 23 | @rem ########################################################################## 24 | 25 | @rem Set local scope for the variables with windows NT shell 26 | if "%OS%"=="Windows_NT" setlocal 27 | 28 | set DIRNAME=%~dp0 29 | if "%DIRNAME%" == "" set DIRNAME=. 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if "%ERRORLEVEL%" == "0" goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /location-v2/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | apply plugin: 'kotlin-android' 8 | apply plugin: 'maven-publish' 9 | apply plugin: 'signing' 10 | 11 | android { 12 | compileSdkVersion androidCompileSdk 13 | buildToolsVersion "$androidBuildVersionTools" 14 | 15 | defaultConfig { 16 | versionName version 17 | minSdkVersion androidMinSdk 18 | targetSdkVersion androidTargetSdk 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility = 1.8 23 | targetCompatibility = 1.8 24 | } 25 | } 26 | 27 | apply from: '../gradle/publish.gradle' 28 | 29 | description = 'UnifiedNlp service to implement Location API v2' 30 | 31 | dependencies { 32 | implementation project(':client') 33 | compileOnly project(':compat') 34 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 35 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" 36 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" 37 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" 38 | implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" 39 | } 40 | -------------------------------------------------------------------------------- /location-v2/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | 33 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /location-v2/src/main/java/org/microg/nlp/location/v2/LocationProvider.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package org.microg.nlp.location.v2 6 | 7 | import android.content.Context 8 | import android.location.Criteria 9 | import android.location.Location 10 | import android.location.LocationProvider 11 | import android.os.Bundle 12 | import android.os.SystemClock 13 | import android.os.WorkSource 14 | import android.util.Log 15 | import androidx.lifecycle.Lifecycle 16 | import androidx.lifecycle.LifecycleOwner 17 | import androidx.lifecycle.lifecycleScope 18 | import com.android.location.provider.LocationProviderBase 19 | import com.android.location.provider.ProviderPropertiesUnbundled 20 | import com.android.location.provider.ProviderRequestUnbundled 21 | import kotlinx.coroutines.launch 22 | import org.microg.nlp.client.LocationClient 23 | import org.microg.nlp.service.api.Constants.STATUS_OK 24 | import org.microg.nlp.service.api.ILocationListener 25 | import org.microg.nlp.service.api.LocationRequest 26 | import java.io.FileDescriptor 27 | import java.io.PrintWriter 28 | import java.util.* 29 | 30 | class LocationProvider(private val context: Context, private val lifecycle: Lifecycle) : LocationProviderBase(TAG, ProviderPropertiesUnbundled.create(false, false, false, false, true, true, true, Criteria.POWER_LOW, Criteria.ACCURACY_COARSE)), LifecycleOwner { 31 | private val client: LocationClient = LocationClient(context, lifecycle) 32 | private val id = UUID.randomUUID().toString() 33 | private var statusUpdateTime = SystemClock.elapsedRealtime() 34 | private val listener = object : ILocationListener.Stub() { 35 | override fun onLocation(statusCode: Int, location: Location?) { 36 | if (statusCode == STATUS_OK && location != null) { 37 | val reportableLocation = Location(location) 38 | for (key in reportableLocation.extras.keySet().toList()) { 39 | if (key?.startsWith("org.microg.nlp.") == true) { 40 | reportableLocation.extras.remove(key) 41 | } 42 | } 43 | Log.d(TAG, "reportLocation: $reportableLocation") 44 | reportLocation(reportableLocation) 45 | } 46 | } 47 | } 48 | private var opPackageName: String? = null 49 | private var opPackageNames: Set = emptySet() 50 | private var autoTime = Long.MAX_VALUE 51 | private var autoUpdate = false 52 | 53 | init { 54 | client.defaultOptions.putString("source", "LocationProvider") 55 | client.defaultOptions.putString("requestId", id) 56 | } 57 | 58 | override fun onEnable() { 59 | Log.d(TAG, "onEnable") 60 | statusUpdateTime = SystemClock.elapsedRealtime() 61 | } 62 | 63 | override fun onDisable() { 64 | Log.d(TAG, "onDisable") 65 | unsetRequest() 66 | statusUpdateTime = SystemClock.elapsedRealtime() 67 | } 68 | 69 | override fun onSetRequest(requests: ProviderRequestUnbundled, source: WorkSource) { 70 | Log.v(TAG, "onSetRequest: $requests by $source") 71 | opPackageName = null 72 | try { 73 | val namesField = WorkSource::class.java.getDeclaredField("mNames") 74 | namesField.isAccessible = true 75 | val names = namesField[source] as Array 76 | if (names != null) { 77 | opPackageNames = setOfNotNull(*names) 78 | for (name in names) { 79 | if (!EXCLUDED_PACKAGES.contains(name)) { 80 | opPackageName = name 81 | break 82 | } 83 | } 84 | if (opPackageName == null && names.isNotEmpty()) opPackageName = names[0] 85 | } else { 86 | opPackageNames = emptySet() 87 | } 88 | } catch (ignored: Exception) { 89 | } 90 | autoTime = requests.interval.coerceAtLeast(FASTEST_REFRESH_INTERVAL) 91 | autoUpdate = requests.reportLocation 92 | Log.v(TAG, "using autoUpdate=$autoUpdate autoTime=$autoTime") 93 | lifecycleScope.launch { 94 | updateRequest() 95 | } 96 | } 97 | 98 | suspend fun updateRequest() { 99 | if (client.isConnected()) { 100 | if (autoUpdate) { 101 | client.packageName = opPackageName ?: context.packageName 102 | client.updateLocationRequest(LocationRequest(listener, autoTime, Int.MAX_VALUE, id)) 103 | } else { 104 | client.cancelLocationRequestById(id) 105 | } 106 | } 107 | } 108 | 109 | fun unsetRequest() { 110 | lifecycleScope.launch { 111 | client.cancelLocationRequestById(id) 112 | } 113 | } 114 | 115 | override fun onGetStatus(extras: Bundle?): Int { 116 | return LocationProvider.AVAILABLE 117 | } 118 | 119 | override fun onGetStatusUpdateTime(): Long { 120 | return statusUpdateTime 121 | } 122 | 123 | override fun onSendExtraCommand(command: String?, extras: Bundle?): Boolean { 124 | Log.d(TAG, "onSendExtraCommand: $command, $extras") 125 | return false 126 | } 127 | 128 | override fun onDump(fd: FileDescriptor?, pw: PrintWriter?, args: Array?) { 129 | dump(pw) 130 | } 131 | 132 | fun dump(writer: PrintWriter?) { 133 | writer?.println("ID: $id") 134 | writer?.println("connected: ${client.isConnectedUnsafe}") 135 | writer?.println("active: $autoUpdate") 136 | writer?.println("interval: $autoTime") 137 | writer?.println("${opPackageNames.size} sources:") 138 | for (packageName in opPackageNames) { 139 | writer?.println(" $packageName") 140 | } 141 | } 142 | 143 | suspend fun connect() { 144 | Log.d(TAG, "Connecting to userspace service...") 145 | client.connect() 146 | updateRequest() 147 | Log.d(TAG, "Connected to userspace service.") 148 | } 149 | suspend fun disconnect() = client.disconnect() 150 | 151 | override fun getLifecycle(): Lifecycle = lifecycle 152 | 153 | companion object { 154 | private val EXCLUDED_PACKAGES = listOf("android", "com.android.location.fused", "com.google.android.gms") 155 | private const val FASTEST_REFRESH_INTERVAL: Long = 2500 156 | private const val TAG = "LocationProvider" 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /location-v2/src/main/java/org/microg/nlp/location/v2/LocationService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package org.microg.nlp.location.v2 6 | 7 | import androidx.lifecycle.LifecycleService 8 | import android.content.Intent 9 | import android.os.IBinder 10 | import android.util.Log 11 | import androidx.lifecycle.lifecycleScope 12 | import kotlinx.coroutines.delay 13 | import kotlinx.coroutines.runBlocking 14 | import java.io.FileDescriptor 15 | import java.io.PrintWriter 16 | 17 | open class LocationService : LifecycleService() { 18 | private lateinit var provider: LocationProvider 19 | 20 | override fun onCreate() { 21 | super.onCreate() 22 | Log.d(TAG, "Creating system service...") 23 | provider = LocationProvider(this, lifecycle) 24 | lifecycleScope.launchWhenStarted { 25 | delay(5000) 26 | provider.connect() 27 | } 28 | Log.d(TAG, "Created system service.") 29 | } 30 | 31 | override fun onBind(intent: Intent): IBinder? { 32 | super.onBind(intent) 33 | Log.d(TAG, "onBind: $intent") 34 | return provider.binder 35 | } 36 | 37 | override fun onDestroy() { 38 | runBlocking { provider.disconnect() } 39 | super.onDestroy() 40 | } 41 | 42 | override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array?) { 43 | if (!this::provider.isInitialized) { 44 | writer?.println("Not yet initialized") 45 | } 46 | provider.dump(writer) 47 | } 48 | 49 | companion object { 50 | private const val TAG = "LocationService" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /location-v3/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | apply plugin: 'kotlin-android' 8 | apply plugin: 'maven-publish' 9 | apply plugin: 'signing' 10 | 11 | android { 12 | compileSdkVersion androidCompileSdk 13 | buildToolsVersion "$androidBuildVersionTools" 14 | 15 | defaultConfig { 16 | versionName version 17 | minSdkVersion androidMinSdk 18 | targetSdkVersion androidTargetSdk 19 | } 20 | 21 | compileOptions { 22 | sourceCompatibility = 1.8 23 | targetCompatibility = 1.8 24 | } 25 | } 26 | 27 | apply from: '../gradle/publish.gradle' 28 | 29 | description = 'UnifiedNlp service to implement Location API v3' 30 | 31 | dependencies { 32 | implementation project(':location-v2') 33 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 34 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" 35 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" 36 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" 37 | implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" 38 | } 39 | -------------------------------------------------------------------------------- /location-v3/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 32 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /location-v3/src/main/java/org/microg/nlp/location/v3/LocationService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | package org.microg.nlp.location.v3 6 | 7 | import org.microg.nlp.location.v2.LocationService 8 | 9 | class LocationService : LocationService() 10 | -------------------------------------------------------------------------------- /patches/android_frameworks_base-N.patch: -------------------------------------------------------------------------------- 1 | Remove PackageManager.MATCH_SYSTEM_ONLY flag in ServiceWatcher 2 | Patch for Android 7 "Nougat" 3 | 4 | SPDX-FileCopyrightText: 2016, microg Project Team 5 | SPDX-License-Identifier: CC0-1.0 6 | 7 | diff --git a/services/core/java/com/android/server/ServiceWatcher.java b/services/core/java/com/android/server/ServiceWatcher.java 8 | index 383e25a..31ae918 100644 9 | --- a/services/core/java/com/android/server/ServiceWatcher.java 10 | +++ b/services/core/java/com/android/server/ServiceWatcher.java 11 | @@ -92,8 +92,7 @@ public class ServiceWatcher implements ServiceConnection { 12 | String pkg = initialPackageNames.get(i); 13 | try { 14 | HashSet set = new HashSet(); 15 | - Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.MATCH_SYSTEM_ONLY 16 | - | PackageManager.GET_SIGNATURES).signatures; 17 | + Signature[] sigs = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES).signatures; 18 | set.addAll(Arrays.asList(sigs)); 19 | sigSets.add(set); 20 | } catch (NameNotFoundException e) { 21 | -------------------------------------------------------------------------------- /service-api/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | apply plugin: 'com.android.library' 7 | apply plugin: 'maven-publish' 8 | apply plugin: 'signing' 9 | 10 | android { 11 | compileSdkVersion androidCompileSdk 12 | buildToolsVersion "$androidBuildVersionTools" 13 | 14 | defaultConfig { 15 | versionName version 16 | minSdkVersion androidMinSdk 17 | targetSdkVersion androidTargetSdk 18 | } 19 | 20 | compileOptions { 21 | sourceCompatibility = 1.8 22 | targetCompatibility = 1.8 23 | } 24 | } 25 | 26 | dependencies { 27 | api "org.microg:safe-parcel:1.7.0" 28 | } 29 | 30 | apply from: '../gradle/publish.gradle' 31 | 32 | description = 'API interfaces and helpers to access UnifiedNlp service' 33 | -------------------------------------------------------------------------------- /service-api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/GeocodeRequest.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | parcelable GeocodeRequest; 9 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/IAddressesCallback.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import android.location.Address; 9 | 10 | interface IAddressesCallback { 11 | oneway void onAddresses(int statusCode, in List
location) ; 12 | } 13 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/IGeocodeService.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import android.location.Address; 9 | import org.microg.nlp.service.api.GeocodeRequest; 10 | import org.microg.nlp.service.api.IAddressesCallback; 11 | import org.microg.nlp.service.api.IStatusCallback; 12 | import org.microg.nlp.service.api.IStringsCallback; 13 | import org.microg.nlp.service.api.ReverseGeocodeRequest; 14 | 15 | interface IGeocodeService { 16 | oneway void requestGeocode(in GeocodeRequest request, IAddressesCallback callback, in Bundle options) = 0; 17 | oneway void requestReverseGeocode(in ReverseGeocodeRequest request, IAddressesCallback callback, in Bundle options) = 1; 18 | List
requestGeocodeSync(in GeocodeRequest request, in Bundle options) = 2; 19 | List
requestReverseGeocodeSync(in ReverseGeocodeRequest request, in Bundle options) = 3; 20 | 21 | oneway void reloadPreferences(IStatusCallback callback, in Bundle options) = 20; 22 | oneway void getGeocodeBackends(IStringsCallback callback, in Bundle options) = 21; 23 | oneway void setGeocodeBackends(in List backends, IStatusCallback callback, in Bundle options) = 22; 24 | } 25 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/ILocationListener.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import android.location.Location; 9 | 10 | interface ILocationListener { 11 | oneway void onLocation(int statusCode, in Location location); 12 | } 13 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/ILocationService.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import android.location.Location; 9 | import org.microg.nlp.service.api.ILocationListener; 10 | import org.microg.nlp.service.api.IStatusCallback; 11 | import org.microg.nlp.service.api.IStringsCallback; 12 | import org.microg.nlp.service.api.LocationRequest; 13 | 14 | interface ILocationService { 15 | oneway void getLastLocation(ILocationListener listener, in Bundle options) = 0; 16 | oneway void getLastLocationForBackend(String packageName, String className, String signatureDigest, ILocationListener listener, in Bundle options) = 1; 17 | 18 | oneway void updateLocationRequest(in LocationRequest request, IStatusCallback callback, in Bundle options) = 10; 19 | oneway void cancelLocationRequestByListener(ILocationListener listener, IStatusCallback callback, in Bundle options) = 11; 20 | oneway void cancelLocationRequestById(String id, IStatusCallback callback, in Bundle options) = 12; 21 | oneway void forceLocationUpdate(IStatusCallback callback, in Bundle options) = 13; 22 | 23 | oneway void reloadPreferences(IStatusCallback callback, in Bundle options) = 20; 24 | oneway void getLocationBackends(IStringsCallback callback, in Bundle options) = 21; 25 | oneway void setLocationBackends(in List backends, IStatusCallback callback, in Bundle options) = 22; 26 | } 27 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/IStatusCallback.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | interface IStatusCallback { 9 | oneway void onStatus(int statusCode); 10 | } 11 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/IStringsCallback.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | interface IStringsCallback { 9 | oneway void onStrings(int statusCode, in List strings); 10 | } 11 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/LocationRequest.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | parcelable LocationRequest; 9 | -------------------------------------------------------------------------------- /service-api/src/main/aidl/org/microg/nlp/service/api/ReverseGeocodeRequest.aidl: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2022, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | parcelable ReverseGeocodeRequest; 9 | -------------------------------------------------------------------------------- /service-api/src/main/java/org/microg/nlp/service/api/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | public final class Constants { 9 | public static final int STATUS_OK = 0; 10 | public static final int STATUS_NOT_IMPLEMENTED = 1; 11 | public static final int STATUS_PERMISSION_ERROR = 2; 12 | public static final int STATUS_INVALID_ARGS = 3; 13 | 14 | public static final String ACTION_LOCATION = "org.microg.nlp.service.LOCATION"; 15 | public static final String ACTION_GEOCODE = "org.microg.nlp.service.GEOCODE"; 16 | 17 | public static final String LOCATION_EXTRA_BACKEND_PROVIDER = "org.microg.nlp.extra.SERVICE_BACKEND_PROVIDER"; 18 | public static final String LOCATION_EXTRA_BACKEND_COMPONENT = "org.microg.nlp.extra.SERVICE_BACKEND_COMPONENT"; 19 | public static final String LOCATION_EXTRA_OTHER_BACKENDS = "org.microg.nlp.extra.OTHER_BACKEND_RESULTS"; 20 | } 21 | -------------------------------------------------------------------------------- /service-api/src/main/java/org/microg/nlp/service/api/GeocodeRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import org.microg.safeparcel.AutoSafeParcelable; 9 | 10 | import java.util.Locale; 11 | 12 | public class GeocodeRequest extends AutoSafeParcelable { 13 | @Field(1) 14 | public String locationName; 15 | @Field(2) 16 | public LatLonBounds bounds; 17 | @Field(3) 18 | public int maxResults; 19 | @Field(4) 20 | public String locale; 21 | 22 | private GeocodeRequest() { 23 | } 24 | 25 | public GeocodeRequest(String locationName, LatLonBounds bounds) { 26 | this(locationName, bounds, 1); 27 | } 28 | 29 | public GeocodeRequest(String locationName, LatLonBounds bounds, int maxResults) { 30 | this(locationName, bounds, maxResults, Locale.getDefault()); 31 | } 32 | 33 | public GeocodeRequest(String locationName, LatLonBounds bounds, int maxResults, Locale locale) { 34 | this(locationName, bounds, maxResults, locale.toString()); 35 | } 36 | 37 | public GeocodeRequest(String locationName, LatLonBounds bounds, int maxResults, String locale) { 38 | this.locationName = locationName; 39 | this.bounds = bounds; 40 | this.maxResults = maxResults; 41 | this.locale = locale; 42 | } 43 | 44 | public static final Creator CREATOR = new AutoCreator<>(GeocodeRequest.class); 45 | } 46 | -------------------------------------------------------------------------------- /service-api/src/main/java/org/microg/nlp/service/api/LatLon.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import android.os.Parcel; 9 | import android.os.Parcelable; 10 | 11 | public class LatLon implements Parcelable { 12 | private double latitude; 13 | private double longitude; 14 | 15 | public LatLon(double latitude, double longitude) { 16 | this.latitude = latitude; 17 | this.longitude = longitude; 18 | } 19 | 20 | public double getLatitude() { 21 | return latitude; 22 | } 23 | 24 | public double getLongitude() { 25 | return longitude; 26 | } 27 | 28 | public static final Creator CREATOR = new Creator() { 29 | @Override 30 | public LatLon createFromParcel(Parcel source) { 31 | return new LatLon(source.readDouble(), source.readDouble()); 32 | } 33 | 34 | @Override 35 | public LatLon[] newArray(int size) { 36 | return new LatLon[size]; 37 | } 38 | }; 39 | 40 | @Override 41 | public int describeContents() { 42 | return 0; 43 | } 44 | 45 | @Override 46 | public void writeToParcel(Parcel dest, int flags) { 47 | dest.writeDouble(latitude); 48 | dest.writeDouble(longitude); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /service-api/src/main/java/org/microg/nlp/service/api/LatLonBounds.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import org.microg.safeparcel.AutoSafeParcelable; 9 | 10 | public class LatLonBounds extends AutoSafeParcelable { 11 | @Field(1) 12 | public LatLon lowerLeft; 13 | @Field(2) 14 | public LatLon upperRight; 15 | 16 | public LatLonBounds(LatLon lowerLeft, LatLon upperRight) { 17 | this.lowerLeft = lowerLeft; 18 | this.upperRight = upperRight; 19 | } 20 | 21 | public static final Creator CREATOR = new AutoCreator<>(LatLonBounds.class); 22 | } 23 | -------------------------------------------------------------------------------- /service-api/src/main/java/org/microg/nlp/service/api/LocationRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import org.microg.safeparcel.AutoSafeParcelable; 9 | 10 | import java.util.UUID; 11 | 12 | public class LocationRequest extends AutoSafeParcelable { 13 | @Field(1) 14 | public ILocationListener listener; 15 | @Field(2) 16 | public long interval; 17 | @Field(3) 18 | public int numUpdates; 19 | @Field(4) 20 | public String id; 21 | 22 | private LocationRequest() { 23 | } 24 | 25 | public LocationRequest(ILocationListener listener, long interval) { 26 | this(listener, interval, Integer.MAX_VALUE, UUID.randomUUID().toString()); 27 | } 28 | 29 | public LocationRequest(ILocationListener listener, long interval, int numUpdates) { 30 | this(listener, interval, numUpdates, UUID.randomUUID().toString()); 31 | } 32 | 33 | public LocationRequest(ILocationListener listener, long interval, int numUpdates, String id) { 34 | this.listener = listener; 35 | this.interval = interval; 36 | this.numUpdates = numUpdates; 37 | this.id = id; 38 | } 39 | 40 | public static final Creator CREATOR = new AutoCreator<>(LocationRequest.class); 41 | } 42 | -------------------------------------------------------------------------------- /service-api/src/main/java/org/microg/nlp/service/api/ReverseGeocodeRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service.api; 7 | 8 | import org.microg.safeparcel.AutoSafeParcelable; 9 | 10 | import java.util.Locale; 11 | 12 | public class ReverseGeocodeRequest extends AutoSafeParcelable { 13 | @Field(1) 14 | public LatLon location; 15 | @Field(2) 16 | public int maxResults; 17 | @Field(3) 18 | public String locale; 19 | 20 | private ReverseGeocodeRequest() { 21 | } 22 | 23 | public ReverseGeocodeRequest(LatLon location) { 24 | this(location, 1); 25 | } 26 | 27 | public ReverseGeocodeRequest(LatLon location, int maxResults) { 28 | this(location, maxResults, Locale.getDefault()); 29 | } 30 | 31 | public ReverseGeocodeRequest(LatLon location, int maxResults, Locale locale) { 32 | this(location, maxResults, locale.toString()); 33 | } 34 | 35 | public ReverseGeocodeRequest(LatLon location, int maxResults, String locale) { 36 | this.location = location; 37 | this.maxResults = maxResults; 38 | this.locale = locale; 39 | } 40 | 41 | public static final Creator CREATOR = new AutoCreator<>(ReverseGeocodeRequest.class); 42 | } 43 | -------------------------------------------------------------------------------- /service/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | 7 | apply plugin: 'com.android.library' 8 | apply plugin: 'kotlin-android' 9 | apply plugin: 'maven-publish' 10 | apply plugin: 'signing' 11 | 12 | android { 13 | compileSdkVersion androidCompileSdk 14 | buildToolsVersion "$androidBuildVersionTools" 15 | 16 | defaultConfig { 17 | versionName version 18 | minSdkVersion androidMinSdk 19 | targetSdkVersion androidTargetSdk 20 | buildConfigField "String", "VERSION_NAME", "\"$version\"" 21 | } 22 | 23 | sourceSets { 24 | main.java.srcDirs += 'src/main/kotlin' 25 | } 26 | 27 | compileOptions { 28 | sourceCompatibility = 1.8 29 | targetCompatibility = 1.8 30 | } 31 | } 32 | 33 | apply from: '../gradle/publish.gradle' 34 | 35 | description = 'UnifiedNlp service library' 36 | 37 | dependencies { 38 | implementation project(':api') 39 | implementation project(':service-api') 40 | implementation project(':client') 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 42 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" 43 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" 44 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" 45 | implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion" 46 | } 47 | -------------------------------------------------------------------------------- /service/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 31 | 32 | 33 | 34 | 35 | 36 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /service/src/main/kotlin/org/microg/nlp/service/AbstractBackendHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2014, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service 7 | 8 | import android.annotation.SuppressLint 9 | import android.content.ComponentName 10 | import android.content.Context 11 | import android.content.Intent 12 | import android.content.ServiceConnection 13 | import android.content.pm.PackageInfo 14 | import android.content.pm.PackageManager 15 | import android.os.IBinder 16 | import android.util.Log 17 | import androidx.lifecycle.Lifecycle 18 | import androidx.lifecycle.LifecycleOwner 19 | import kotlinx.coroutines.CoroutineScope 20 | import java.io.PrintWriter 21 | 22 | import java.security.MessageDigest 23 | import java.security.NoSuchAlgorithmException 24 | 25 | fun Array?.isNotNullOrEmpty(): Boolean { 26 | return this != null && this.isNotEmpty() 27 | } 28 | 29 | abstract class AbstractBackendHelper(private val TAG: String, private val context: Context, private val lifecycle: Lifecycle, val serviceIntent: Intent, val signatureDigest: String?) : ServiceConnection, LifecycleOwner { 30 | private var bound: Boolean = false 31 | 32 | protected abstract suspend fun close() 33 | 34 | override fun getLifecycle(): Lifecycle = lifecycle 35 | 36 | protected abstract fun hasBackend(): Boolean 37 | 38 | override fun onServiceConnected(name: ComponentName, service: IBinder) { 39 | bound = true 40 | Log.d(TAG, "Bound to: $name") 41 | } 42 | 43 | override fun onServiceDisconnected(name: ComponentName) { 44 | bound = false 45 | Log.d(TAG, "Unbound from: $name") 46 | } 47 | 48 | suspend fun unbind() { 49 | if (bound) { 50 | if (hasBackend()) { 51 | try { 52 | close() 53 | } catch (e: Exception) { 54 | Log.w(TAG, e) 55 | } 56 | } 57 | unbindNow() 58 | } 59 | } 60 | 61 | fun unbindNow() { 62 | if (bound) { 63 | try { 64 | Log.d(TAG, "Unbinding from: $serviceIntent") 65 | context.unbindService(this) 66 | } catch (e: Exception) { 67 | Log.w(TAG, e) 68 | } 69 | 70 | bound = false 71 | } 72 | } 73 | 74 | fun bind() { 75 | if (!bound) { 76 | Log.d(TAG, "Binding to: $serviceIntent sig: $signatureDigest") 77 | if (serviceIntent.getPackage() == null) { 78 | Log.w(TAG, "Intent is not properly resolved, can't verify signature. Aborting.") 79 | return 80 | } 81 | val computedDigest = firstSignatureDigest(context, serviceIntent.getPackage(), "SHA-256") 82 | if (signatureDigest != null && signatureDigest != computedDigest) { 83 | Log.w(TAG, "Target signature does not match selected package ($signatureDigest != $computedDigest). Aborting.") 84 | return 85 | } 86 | try { 87 | context.bindService(serviceIntent, this, Context.BIND_AUTO_CREATE) 88 | } catch (e: Exception) { 89 | Log.w(TAG, e) 90 | } 91 | 92 | } 93 | } 94 | 95 | open fun dump(writer: PrintWriter?) { 96 | writer?.println(" ${javaClass.simpleName} $serviceIntent bound=$bound") 97 | } 98 | 99 | companion object { 100 | @Suppress("DEPRECATION") 101 | @SuppressLint("PackageManagerGetSignatures") 102 | fun firstSignatureDigest(context: Context, packageName: String?, algorithm: String): String? { 103 | val packageManager = context.packageManager 104 | val info: PackageInfo? 105 | try { 106 | info = packageManager.getPackageInfo(packageName!!, PackageManager.GET_SIGNATURES) 107 | } catch (e: PackageManager.NameNotFoundException) { 108 | return null 109 | } 110 | 111 | if (info?.signatures.isNotNullOrEmpty()) { 112 | for (sig in info.signatures) { 113 | digest(sig.toByteArray(), algorithm)?.let { return it } 114 | } 115 | } 116 | return null 117 | } 118 | 119 | private fun digest(bytes: ByteArray, algorithm: String): String? { 120 | try { 121 | val md = MessageDigest.getInstance(algorithm) 122 | val digest = md.digest(bytes) 123 | val sb = StringBuilder(2 * digest.size) 124 | for (b in digest) { 125 | sb.append(String.format("%02x", b)) 126 | } 127 | return sb.toString() 128 | } catch (e: NoSuchAlgorithmException) { 129 | return null 130 | } 131 | } 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /service/src/main/kotlin/org/microg/nlp/service/AsyncLocationBackend.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service 7 | 8 | import android.content.Intent 9 | import android.location.Location 10 | import android.os.* 11 | import kotlinx.coroutines.sync.Mutex 12 | import kotlinx.coroutines.sync.withLock 13 | import org.microg.nlp.api.LocationBackend 14 | import org.microg.nlp.api.LocationCallback 15 | import java.lang.Exception 16 | import java.util.concurrent.CountDownLatch 17 | import kotlin.coroutines.resume 18 | import kotlin.coroutines.suspendCoroutine 19 | 20 | class AsyncLocationBackend(binder: IBinder, name: String = "location-backend-thread") : Thread(name) { 21 | private lateinit var looper: Looper 22 | private lateinit var handler: Handler 23 | private val mutex = Mutex(true) 24 | private val backend = LocationBackend.Stub.asInterface(binder) 25 | 26 | override fun run() { 27 | Looper.prepare() 28 | looper = Looper.myLooper()!! 29 | handler = Handler(looper) 30 | handler.post { 31 | mutex.unlock() 32 | } 33 | Looper.loop() 34 | } 35 | 36 | suspend fun updateWithOptions(options: Bundle?): Location = mutex.withLock { 37 | suspendCoroutine { 38 | handler.post { 39 | val result = try { 40 | Result.success(backend.updateWithOptions(options)) 41 | } catch (e: Exception) { 42 | Result.failure(e) 43 | } 44 | it.resumeWith(result) 45 | } 46 | } 47 | } 48 | 49 | suspend fun getSettingsIntent(): Intent = mutex.withLock { 50 | suspendCoroutine { 51 | handler.post { 52 | val result = try { 53 | Result.success(backend.settingsIntent) 54 | } catch (e: Exception) { 55 | Result.failure(e) 56 | } 57 | it.resumeWith(result) 58 | } 59 | } 60 | } 61 | 62 | suspend fun getInitIntent(): Intent = mutex.withLock { 63 | suspendCoroutine { 64 | handler.post { 65 | val result = try { 66 | Result.success(backend.initIntent) 67 | } catch (e: Exception) { 68 | Result.failure(e) 69 | } 70 | it.resumeWith(result) 71 | } 72 | } 73 | } 74 | 75 | suspend fun open(callback: LocationCallback) { 76 | start() 77 | mutex.withLock { 78 | suspendCoroutine { 79 | handler.post { 80 | val result = try { 81 | backend.open(callback) 82 | Result.success(Unit) 83 | } catch (e: Exception) { 84 | Result.failure(e) 85 | } 86 | it.resumeWith(result) 87 | } 88 | } 89 | } 90 | } 91 | 92 | suspend fun getAboutIntent(): Intent = mutex.withLock { 93 | suspendCoroutine { 94 | handler.post { 95 | val result = try { 96 | Result.success(backend.aboutIntent) 97 | } catch (e: Exception) { 98 | Result.failure(e) 99 | } 100 | it.resumeWith(result) 101 | } 102 | } 103 | } 104 | 105 | suspend fun update(): Location = mutex.withLock { 106 | suspendCoroutine { 107 | handler.post { 108 | val result = try { 109 | Result.success(backend.update()) 110 | } catch (e: Exception) { 111 | Result.failure(e) 112 | } 113 | it.resumeWith(result) 114 | } 115 | } 116 | } 117 | 118 | suspend fun close() { 119 | mutex.withLock { 120 | suspendCoroutine { 121 | handler.post { 122 | val result = try { 123 | backend.close() 124 | Result.success(Unit) 125 | } catch (e: Exception) { 126 | Result.failure(e) 127 | } 128 | it.resumeWith(result) 129 | } 130 | } 131 | } 132 | looper.quit() 133 | } 134 | } -------------------------------------------------------------------------------- /service/src/main/kotlin/org/microg/nlp/service/PackageChangedReceiver.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service 7 | 8 | import android.content.BroadcastReceiver 9 | import android.content.Context 10 | import android.content.Intent 11 | import android.content.Intent.* 12 | import android.util.Log 13 | 14 | @Deprecated("Registered in LocationService or GeocodeService") 15 | class PackageChangedReceiver : BroadcastReceiver() { 16 | 17 | private fun isProtectedAction(action: String) = when (action) { 18 | ACTION_PACKAGE_CHANGED, ACTION_PACKAGE_REMOVED, ACTION_PACKAGE_REPLACED, ACTION_PACKAGE_RESTARTED -> true 19 | else -> false 20 | } 21 | 22 | override fun onReceive(context: Context, intent: Intent) { 23 | Log.d(TAG, "Intent received: $intent") 24 | if (intent.action?.let { isProtectedAction(it) } != true) return 25 | 26 | val packageName = intent.data!!.schemeSpecificPart 27 | val preferences = Preferences(context) 28 | for (backend in preferences.locationBackends) { 29 | if (backend.startsWith("$packageName/")) { 30 | Log.d(TAG, "Reloading location service for $packageName") 31 | UnifiedLocationServiceEntryPoint.reloadPreferences() 32 | 33 | return 34 | } 35 | } 36 | for (backend in preferences.geocoderBackends) { 37 | if (backend.startsWith("$packageName/")) { 38 | Log.d(TAG, "Reloading geocoding service for $packageName") 39 | UnifiedLocationServiceEntryPoint.reloadPreferences() 40 | return 41 | } 42 | } 43 | } 44 | 45 | companion object { 46 | private const val TAG = "UnifiedService" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /service/src/main/kotlin/org/microg/nlp/service/Preferences.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2014, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service 7 | 8 | import android.content.Context 9 | import android.content.SharedPreferences 10 | import android.os.Build 11 | import java.io.File 12 | 13 | 14 | class Preferences(private val context: Context) { 15 | 16 | private val preferences: SharedPreferences 17 | get() = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) 18 | 19 | private val oldPreferences: SharedPreferences 20 | get() = context.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE) 21 | 22 | private val systemDefaultPreferences: SharedPreferences? 23 | get() = try { 24 | Context::class.java.getDeclaredMethod("getSharedPreferences", File::class.java, Int::class.javaPrimitiveType).invoke(context, File("/system/etc/microg.xml"), Context.MODE_PRIVATE) as SharedPreferences 25 | } catch (e: java.lang.Exception) { 26 | null 27 | } 28 | 29 | private fun SharedPreferences.getStringSetCompat(key: String, defValues: Set? = null): Set? { 30 | if (Build.VERSION.SDK_INT >= 11) { 31 | try { 32 | val res = getStringSet(key, null) 33 | if (res != null) return res.filter { it.isNotEmpty() }.toSet() 34 | } catch (ignored: Exception) { 35 | // Ignore 36 | } 37 | } 38 | try { 39 | val str = getString(key, null) 40 | if (str != null) return str.split("\\|".toRegex()).filter { it.isNotEmpty() }.toSet() 41 | } catch (ignored: Exception) { 42 | // Ignore 43 | } 44 | return defValues 45 | } 46 | 47 | private fun SharedPreferences.Editor.putStringSetCompat(key: String, values: Set): SharedPreferences.Editor { 48 | return if (Build.VERSION.SDK_INT >= 11) { 49 | putStringSet(key, values.filter { it.isNotEmpty() }.toSet()) 50 | } else { 51 | putString(key, values.filter { it.isNotEmpty() }.joinToString("|")) 52 | } 53 | } 54 | 55 | private fun getStringSetFromAny(key: String): Set? { 56 | migratePreference(key) 57 | val fromNewSettings = preferences.getStringSetCompat(key) 58 | if (fromNewSettings != null) return fromNewSettings 59 | return systemDefaultPreferences?.getStringSetCompat(key) 60 | } 61 | 62 | private fun migratePreference(key: String): Set? { 63 | val fromOldSettings = oldPreferences.getStringSetCompat(key) 64 | if (fromOldSettings != null) { 65 | var newSettings: MutableSet = mutableSetOf() 66 | newSettings.addAll(preferences.getStringSetCompat(key).orEmpty()) 67 | for (oldBackend in fromOldSettings) { 68 | // Get package name and sha1 69 | val parts = oldBackend.split("/".toRegex()).dropLastWhile(String::isEmpty).toTypedArray() 70 | if (parts.size < 3) continue // skip unsigned 71 | val pkgName = parts[0] 72 | val component = parts[1] 73 | val oldSig = parts[2] 74 | if (oldSig?.length != 40) continue // skip if not sha1 75 | // Get matching sha256 76 | val sha1 = AbstractBackendHelper.firstSignatureDigest(context, pkgName, "SHA-1") 77 | val sha256 = AbstractBackendHelper.firstSignatureDigest(context, pkgName, "SHA-256") 78 | // If the system sha1 matches what we had stored 79 | if (oldSig == sha1) { 80 | // Replace it with the sha256 81 | val newBackend = "${pkgName}/${component}/${sha256}" 82 | newSettings.add(newBackend) 83 | } 84 | } 85 | if (preferences.edit().putStringSetCompat(key, newSettings.toSet()).commit()) { 86 | // Only delete the old preference once committed. 87 | oldPreferences.edit().remove(key).apply() 88 | } 89 | } 90 | return null 91 | } 92 | 93 | var locationBackends: Set 94 | get() = getStringSetFromAny(PREF_LOCATION_BACKENDS) ?: emptySet() 95 | set(backends) { 96 | preferences.edit().putStringSetCompat(PREF_LOCATION_BACKENDS, backends).apply() 97 | } 98 | 99 | var geocoderBackends: Set 100 | get() = getStringSetFromAny(PREF_GEOCODER_BACKENDS) ?: emptySet() 101 | set(backends) { 102 | preferences.edit().putStringSetCompat(PREF_GEOCODER_BACKENDS, backends).apply() 103 | } 104 | 105 | companion object { 106 | private const val PREFERENCES_NAME = "unified_nlp" 107 | private const val PREF_LOCATION_BACKENDS = "location_backends" 108 | private const val PREF_GEOCODER_BACKENDS = "geocoder_backends" 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceEntryPoint.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service 7 | 8 | import android.content.Intent 9 | import android.os.IBinder 10 | import android.util.Log 11 | import androidx.lifecycle.LifecycleService 12 | import androidx.lifecycle.lifecycleScope 13 | import java.io.FileDescriptor 14 | import java.io.PrintWriter 15 | 16 | @Deprecated("Use LocationService or GeocodeService") 17 | class UnifiedLocationServiceEntryPoint : LifecycleService() { 18 | private var root: UnifiedLocationServiceRoot = UnifiedLocationServiceRoot(this, lifecycle) 19 | 20 | override fun onCreate() { 21 | singleton = this 22 | super.onCreate() 23 | Log.d(TAG, "onCreate") 24 | lifecycleScope.launchWhenStarted { root.reset() } 25 | } 26 | 27 | override fun onBind(intent: Intent): IBinder? { 28 | super.onBind(intent) 29 | Log.d(TAG, "onBind: $intent") 30 | return root.asBinder() 31 | } 32 | 33 | override fun onDestroy() { 34 | super.onDestroy() 35 | Log.d(TAG, "onDestroy") 36 | root.destroy() 37 | singleton = null 38 | } 39 | 40 | override fun dump(fd: FileDescriptor?, writer: PrintWriter?, args: Array?) { 41 | writer?.println("Singleton: ${singleton != null}") 42 | root.dump(writer) 43 | } 44 | 45 | companion object { 46 | private val TAG = "ULocService" 47 | private var singleton: UnifiedLocationServiceEntryPoint? = null 48 | 49 | fun reloadPreferences() { 50 | singleton?.root?.reloadPreferences() 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /service/src/main/kotlin/org/microg/nlp/service/UnifiedLocationServiceInstance.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.service 7 | 8 | import android.app.ActivityManager 9 | import android.content.Context 10 | import android.content.pm.PackageManager.PERMISSION_GRANTED 11 | import android.location.Location 12 | import android.os.Binder.getCallingPid 13 | import android.os.Binder.getCallingUid 14 | import android.os.Bundle 15 | import android.os.RemoteException 16 | import android.util.Log 17 | import androidx.lifecycle.lifecycleScope 18 | import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_FORCE_NEXT_UPDATE 19 | import org.microg.nlp.client.UnifiedLocationClient.Companion.KEY_OP_PACKAGE_NAME 20 | import org.microg.nlp.client.UnifiedLocationClient.Companion.PERMISSION_SERVICE_ADMIN 21 | import java.io.PrintWriter 22 | 23 | @Deprecated("Use LocationService or GeocodeService") 24 | class UnifiedLocationServiceInstance(private val root: UnifiedLocationServiceRoot) : UnifiedLocationService.Default() { 25 | private var callback: LocationCallback? = null 26 | private var interval: Long = 0 27 | private var singleUpdatePending = false 28 | val callingPackage = root.context.getCallingPackage() 29 | 30 | private var opPackage: String? = null 31 | private val debugPackageString: String? 32 | get() { 33 | if (opPackage == callingPackage || opPackage == null) return callingPackage 34 | return "$callingPackage for $opPackage" 35 | } 36 | 37 | private fun Context.getCallingPackage(): String? { 38 | val manager = getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager 39 | val callingPid = getCallingPid() 40 | if (manager != null && callingPid > 0) { 41 | manager.runningAppProcesses.find { it.pid == callingPid }?.pkgList?.singleOrNull()?.let { return it } 42 | } 43 | return packageManager.getPackagesForUid(getCallingUid())?.singleOrNull() 44 | } 45 | 46 | fun reportLocation(location: Location) { 47 | try { 48 | if (callback != null) { 49 | callback!!.onLocationUpdate(location) 50 | } 51 | if (singleUpdatePending) { 52 | singleUpdatePending = false 53 | root.updateLocationInterval() 54 | } 55 | } catch (e: RemoteException) { 56 | root.onDisconnected(this) 57 | } 58 | } 59 | 60 | fun getInterval(): Long { 61 | // TODO: Do not report interval if client should no longer receive 62 | return if (singleUpdatePending) UnifiedLocationServiceRoot.MIN_LOCATION_INTERVAL else interval 63 | } 64 | 65 | override fun registerLocationCallback(callback: LocationCallback, options: Bundle) { 66 | if (root.context.checkCallingPermission(PERMISSION_SERVICE_ADMIN) == PERMISSION_GRANTED && options.containsKey(KEY_OP_PACKAGE_NAME)) { 67 | opPackage = options.getString(KEY_OP_PACKAGE_NAME) 68 | } 69 | Log.d(TAG, "registerLocationCallback[$callingPackage]") 70 | this.callback = callback 71 | } 72 | 73 | override fun setUpdateInterval(interval: Long, options: Bundle) { 74 | if (root.context.checkCallingPermission(PERMISSION_SERVICE_ADMIN) == PERMISSION_GRANTED && options.containsKey(KEY_OP_PACKAGE_NAME)) { 75 | opPackage = options.getString(KEY_OP_PACKAGE_NAME) 76 | } 77 | Log.d(TAG, "setUpdateInterval[$debugPackageString] interval: $interval") 78 | this.interval = interval 79 | root.updateLocationInterval() 80 | } 81 | 82 | override fun requestSingleUpdate(options: Bundle) { 83 | if (root.context.checkCallingPermission(PERMISSION_SERVICE_ADMIN) == PERMISSION_GRANTED && options.containsKey(KEY_OP_PACKAGE_NAME)) { 84 | opPackage = options.getString(KEY_OP_PACKAGE_NAME) 85 | } 86 | val lastLocation = root.lastReportedLocation 87 | if (lastLocation == null || lastLocation.time < System.currentTimeMillis() - UnifiedLocationServiceRoot.MAX_LOCATION_AGE || options.getBoolean(KEY_FORCE_NEXT_UPDATE, false)) { 88 | Log.d(TAG, "requestSingleUpdate[$debugPackageString] requesting new location") 89 | singleUpdatePending = true 90 | root.lifecycleScope.launchWhenStarted { 91 | root.locationFuser.update() 92 | root.updateLocationInterval() 93 | } 94 | } else if (callback != null) { 95 | Log.d(TAG, "requestSingleUpdate[$debugPackageString] using last location ") 96 | try { 97 | this.callback!!.onLocationUpdate(lastLocation) 98 | } catch (e: RemoteException) { 99 | root.onDisconnected(this) 100 | throw e 101 | } 102 | } 103 | } 104 | 105 | fun dump(writer: PrintWriter?) { 106 | writer?.println("$debugPackageString: interval $interval, single $singleUpdatePending") 107 | } 108 | 109 | companion object { 110 | private val TAG = "ULocService" 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2015, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | include ':api' 7 | include ':service' 8 | include ':service-api' 9 | include ':compat' 10 | include ':client' 11 | include ':location-v1' 12 | include ':location-v2' 13 | include ':location-v3' 14 | include ':geocode-v1' 15 | 16 | include ':ui' 17 | 18 | //include ':service-app' 19 | //include ':client-app' 20 | //include ':app' 21 | 22 | include ':docs:backend-sample' 23 | -------------------------------------------------------------------------------- /ui/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2019, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | 7 | apply plugin: 'com.android.library' 8 | apply plugin: 'kotlin-android' 9 | apply plugin: 'kotlin-kapt' 10 | apply plugin: 'kotlin-android-extensions' 11 | apply plugin: 'maven-publish' 12 | apply plugin: 'signing' 13 | 14 | android { 15 | compileSdkVersion androidCompileSdk 16 | buildToolsVersion "$androidBuildVersionTools" 17 | 18 | buildFeatures { 19 | dataBinding = true 20 | } 21 | 22 | defaultConfig { 23 | versionName version 24 | minSdkVersion Math.max(androidMinSdk, 14) 25 | targetSdkVersion androidTargetSdk 26 | } 27 | 28 | sourceSets { 29 | main.java.srcDirs += 'src/main/kotlin' 30 | } 31 | 32 | compileOptions { 33 | sourceCompatibility = 1.8 34 | targetCompatibility = 1.8 35 | } 36 | 37 | kotlinOptions { 38 | jvmTarget = "1.8" 39 | } 40 | 41 | lintOptions { 42 | warning "MissingTranslation" 43 | } 44 | } 45 | 46 | apply from: '../gradle/publish.gradle' 47 | 48 | description = 'UnifiedNlp UI library for common configuration fragments' 49 | 50 | dependencies { 51 | implementation project(':api') 52 | implementation project(':client') 53 | 54 | // Kotlin 55 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" 56 | 57 | // AndroidX UI 58 | implementation "androidx.appcompat:appcompat:$appcompatVersion" 59 | implementation "androidx.fragment:fragment:$fragmentVersion" 60 | implementation "androidx.recyclerview:recyclerview:$recyclerviewVersion" 61 | implementation "androidx.preference:preference:$preferenceVersion" 62 | 63 | // Kotlin coroutine for android 64 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion" 65 | implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" 66 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutineVersion" 67 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutineVersion" 68 | 69 | // Navigation 70 | implementation "androidx.navigation:navigation-fragment:$navigationVersion" 71 | implementation "androidx.navigation:navigation-ui:$navigationVersion" 72 | implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion" 73 | implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion" 74 | } 75 | -------------------------------------------------------------------------------- /ui/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ui/src/main/kotlin/org/microg/nlp/ui/ActivityResultProcessor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020 microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.ui 7 | 8 | import android.content.Intent 9 | import android.os.Bundle 10 | import android.util.Log 11 | import android.util.SparseArray 12 | import androidx.core.util.set 13 | import androidx.fragment.app.Fragment 14 | import java.util.concurrent.atomic.AtomicInteger 15 | import kotlin.coroutines.resume 16 | import kotlin.coroutines.suspendCoroutine 17 | 18 | private val requestCodeCounter = AtomicInteger(1) 19 | private val continuationMap = SparseArray<(Int, Intent?) -> Unit>() 20 | 21 | fun Fragment.startActivityForResult(intent: Intent, options: Bundle? = null, callback: (Int, Intent?) -> Unit) { 22 | val requestCode = requestCodeCounter.getAndIncrement() 23 | continuationMap.put(requestCode, callback) 24 | startActivityForResult(intent, requestCode, options) 25 | } 26 | 27 | suspend fun Fragment.startActivityForResultCode(intent: Intent, options: Bundle? = null): Int = suspendCoroutine { continuation -> 28 | startActivityForResult(intent, options) { responseCode, _ -> 29 | continuation.resume(responseCode) 30 | } 31 | } 32 | 33 | fun handleActivityResult(requestCode: Int, responseCode: Int, data: Intent?) { 34 | Log.d("ActivityResultProc", "handleActivityResult: $requestCode, $responseCode") 35 | try { 36 | continuationMap[requestCode]?.let { it(responseCode, data) } 37 | } catch (e: Exception) { 38 | Log.w("ActivityResultProc", "Error while handling activity result", e) 39 | } 40 | continuationMap.remove(requestCode) 41 | } 42 | -------------------------------------------------------------------------------- /ui/src/main/kotlin/org/microg/nlp/ui/BackendSettingsActivity.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.ui 7 | 8 | import android.os.Bundle 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.navigation.NavController 11 | import androidx.navigation.fragment.NavHostFragment 12 | import androidx.navigation.ui.AppBarConfiguration 13 | import androidx.navigation.ui.navigateUp 14 | import androidx.navigation.ui.setupActionBarWithNavController 15 | 16 | class BackendSettingsActivity : AppCompatActivity() { 17 | private lateinit var appBarConfiguration: AppBarConfiguration 18 | 19 | private val navController: NavController 20 | get() = (supportFragmentManager.findFragmentById(R.id.navhost) as NavHostFragment).navController 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.backend_settings_activity) 25 | 26 | appBarConfiguration = AppBarConfiguration(navController.graph) 27 | setupActionBarWithNavController(navController, appBarConfiguration) 28 | } 29 | 30 | override fun onSupportNavigateUp(): Boolean { 31 | return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp() 32 | } 33 | } -------------------------------------------------------------------------------- /ui/src/main/kotlin/org/microg/nlp/ui/binding/BindingAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.ui.binding 7 | 8 | import android.content.Context 9 | import android.util.TypedValue 10 | import android.view.View 11 | import androidx.annotation.AttrRes 12 | import androidx.annotation.ColorInt 13 | import androidx.core.content.ContextCompat 14 | import androidx.databinding.BindingAdapter 15 | 16 | @ColorInt 17 | private fun Context.resolveColor(@AttrRes resid: Int): Int? { 18 | val typedValue = TypedValue() 19 | if (!theme.resolveAttribute(resid, typedValue, true)) return null 20 | val colorRes = if (typedValue.resourceId != 0) typedValue.resourceId else typedValue.data 21 | return ContextCompat.getColor(this, colorRes) 22 | } 23 | 24 | @BindingAdapter("app:backgroundColorAttr") 25 | fun View.setBackgroundColorAttribute(@AttrRes resId: Int) = context.resolveColor(resId)?.let { setBackgroundColor(it) } 26 | -------------------------------------------------------------------------------- /ui/src/main/kotlin/org/microg/nlp/ui/model/BackendDetailsCallback.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.ui.model 7 | 8 | interface BackendDetailsCallback { 9 | fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) 10 | fun onAppClicked(entry: BackendInfo?) 11 | fun onAboutClicked(entry: BackendInfo?) 12 | fun onConfigureClicked(entry: BackendInfo?) 13 | } 14 | -------------------------------------------------------------------------------- /ui/src/main/kotlin/org/microg/nlp/ui/model/BackendInfo.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.ui.model 7 | 8 | import android.content.Intent 9 | import android.content.pm.ServiceInfo 10 | import android.graphics.drawable.Drawable 11 | import androidx.databinding.ObservableBoolean 12 | import androidx.databinding.ObservableField 13 | 14 | class BackendInfo(val serviceInfo: ServiceInfo, val type: BackendType, val firstSignatureDigest: String?) { 15 | val enabled = ObservableBoolean() 16 | val appIcon = ObservableField() 17 | val name = ObservableField() 18 | val appName = ObservableField() 19 | val summary = ObservableField() 20 | 21 | val loaded = ObservableBoolean() 22 | 23 | val initIntent = ObservableField() 24 | val aboutIntent = ObservableField() 25 | val settingsIntent = ObservableField() 26 | 27 | val unsignedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}" 28 | val signedComponent: String = "${serviceInfo.packageName}/${serviceInfo.name}/$firstSignatureDigest" 29 | 30 | override fun equals(other: Any?): Boolean { 31 | return other is BackendInfo && other.name == name && other.enabled == enabled && other.appName == appName && other.unsignedComponent == unsignedComponent && other.summary == summary 32 | } 33 | } 34 | 35 | enum class BackendType { LOCATION, GEOCODER } 36 | -------------------------------------------------------------------------------- /ui/src/main/kotlin/org/microg/nlp/ui/model/BackendListEntryCallback.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2020, microG Project Team 3 | * SPDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package org.microg.nlp.ui.model 7 | 8 | interface BackendListEntryCallback { 9 | fun onEnabledChange(entry: BackendInfo?, newValue: Boolean) 10 | fun onOpenDetails(entry: BackendInfo?) 11 | } 12 | -------------------------------------------------------------------------------- /ui/src/main/res/layout/backend_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 16 | 17 | 18 | 22 | 23 | 33 | 34 | 46 | 47 | 48 | 56 | 57 | 61 | 62 | 72 | 73 | 85 | 86 | 87 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /ui/src/main/res/layout/backend_list_entry.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 17 | 18 | 21 | 22 | 23 | 30 | 31 | 45 | 46 | 56 | 57 | 63 | 64 | 65 | 70 | 71 | 80 | 81 | 82 | 83 | 93 | 94 | 98 | 99 | 100 | 108 | 109 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /ui/src/main/res/layout/backend_settings_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 19 | 20 | -------------------------------------------------------------------------------- /ui/src/main/res/navigation/nav_unlp.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 11 | 12 | 17 | 20 | 21 | 26 | 29 | 32 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /ui/src/main/res/values-be/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Навігацыйныя модулі 9 | Падрабязнасці пра модуль 10 | 11 | Сецевыя модулі геалакацыі 12 | Модулі пошуку адрасоў 13 | Усталяваныя модулі адсутнічаюць 14 | 15 | Наладзіць 16 | Аб праграме 17 | Выкарыстоўваць модуль 18 | Апісанне 19 | Апошняе месцазнаходжанне 20 | 21 | -------------------------------------------------------------------------------- /ui/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Module für Funknetz-basierte Ortung 9 | Module für Adressauflösung 10 | 11 | Konfigurieren 12 | Infos 13 | Modul benutzen 14 | Beschreibung 15 | Letzte Ortung 16 | 17 | -------------------------------------------------------------------------------- /ui/src/main/res/values-eo/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Pozici‑trovaj subservoj 9 | Detaloj pri la subservo 10 | 11 | Ret‑bazitaj pozici‑trovaj subservoj 12 | Adres‑trovaj subservoj 13 | Neniu instalita subservo 14 | 15 | Agordi 16 | Pri 17 | Uzi subservon 18 | Priskribo 19 | Antaŭa pozicio 20 | 21 | 22 | -------------------------------------------------------------------------------- /ui/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Módulos de localización 9 | Detalles del módulo de localización 10 | 11 | Módulos de geolocalización en red 12 | Módulos de búsqueda de direcciones 13 | No hay ningún módulo instalado 14 | 15 | Configurar 16 | Acerca 17 | Usar módulo 18 | Descripción 19 | Última localización 20 | -------------------------------------------------------------------------------- /ui/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Configurer 9 | À propos 10 | 11 | -------------------------------------------------------------------------------- /ui/src/main/res/values-in/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Modul lokasi 9 | Detail modul lokasi 10 | 11 | Modul Geolokasi berbasis jaringan 12 | Modul pencarian alamat 13 | Tidak ada modul yang dipasang 14 | 15 | Konfigurasi 16 | Tentang 17 | Gunakan modul 18 | Deskripsi 19 | Lokasi terakhir 20 | 21 | -------------------------------------------------------------------------------- /ui/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Moduli di geolocalizzazione 9 | Dettagli del modulo di geolocalizzazione 10 | 11 | Moduli di localizzazione basati sulle reti senza fili 12 | Moduli di geocodifica degli indirizzi 13 | Nessun modulo installato 14 | 15 | Configura 16 | Informazioni 17 | Abilita il modulo 18 | Descrizione 19 | Ultima posizione rilevata 20 | 21 | -------------------------------------------------------------------------------- /ui/src/main/res/values-pl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Usługi lokaliacji 9 | Szczeguły usługi 10 | 11 | Usługi lokalizacji oparte o sieć 12 | Usługi wyszukiwania adresów 13 | Brak zainstalowanych usług 14 | 15 | Konfiguruj 16 | O programie 17 | Użyj usługi 18 | Opis 19 | Ostatnia lokalizacja 20 | 21 | 22 | -------------------------------------------------------------------------------- /ui/src/main/res/values-ro/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Configuraţi 9 | 10 | -------------------------------------------------------------------------------- /ui/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Навигационные модули 9 | Подробности о модуле 10 | 11 | Сетевые модули геолокации 12 | Модули поиска адресов 13 | Установленные модули отсутствуют 14 | 15 | Настроить 16 | О программе 17 | Использовать модуль 18 | Описание 19 | Последнее местоположение 20 | 21 | -------------------------------------------------------------------------------- /ui/src/main/res/values-sr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Подеси 9 | О програму 10 | 11 | -------------------------------------------------------------------------------- /ui/src/main/res/values-uk/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Сконфігурувати 9 | Про 10 | 11 | -------------------------------------------------------------------------------- /ui/src/main/res/values-zh-rTW/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 設定 9 | 關於 10 | 11 | -------------------------------------------------------------------------------- /ui/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | Location modules 9 | Location module details 10 | 11 | Network-based Geolocation modules 12 | Address lookup modules 13 | No module installed 14 | 15 | Configure 16 | About 17 | Use module 18 | Description 19 | Last location 20 | 21 | --------------------------------------------------------------------------------