diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 0000000..e430284
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,111 @@
+/*
+ * SPDX-FileCopyrightText: 2019, microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlin-kapt'
+apply plugin: 'kotlin-android-extensions'
+apply plugin: 'maven-publish'
+apply plugin: 'signing'
+
+String getMyVersionName() {
+ def stdout = new ByteArrayOutputStream()
+ if (rootProject.file("gradlew").exists())
+ exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout }
+ else // automatic build system, don't tag dirty
+ exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout }
+ return stdout.toString().trim().substring(1)
+}
+
+int getMyVersionCode() {
+ def stdout = new ByteArrayOutputStream()
+ exec {
+ commandLine 'git', 'rev-list', '--count', "HEAD"
+ standardOutput = stdout
+ }
+ return Integer.parseInt(stdout.toString().trim())
+}
+
+android {
+ compileSdkVersion androidCompileSdk
+ buildToolsVersion "$androidBuildVersionTools"
+ dataBinding {
+ enabled = true
+ }
+
+ defaultConfig {
+ versionName getMyVersionName()
+ versionCode(20000 + getMyVersionCode())
+ minSdkVersion Math.max(androidMinSdk, 14)
+ targetSdkVersion androidTargetSdk
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ flavorDimensions 'default'
+ productFlavors {
+ NetworkLocation {
+ applicationId = 'com.google.android.gms'
+ minSdkVersion 19
+ dimension 'default'
+ }
+ LegacyNetworkLocation {
+ applicationId = 'com.google.android.location'
+ dimension 'default'
+ }
+ UnifiedNlp {
+ applicationId = 'org.microg.nlp'
+ dimension 'default'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = 1.8
+ targetCompatibility = 1.8
+ }
+
+ kotlinOptions {
+ jvmTarget = "1.8"
+ }
+
+ lintOptions {
+ warning "MissingTranslation"
+ }
+}
+
+apply from: "../gradle/androidJars.gradle"
+
+dependencies {
+ implementation project(':api')
+ implementation project(':geocode-v1')
+ implementation project(':location-v2')
+ implementation project(':location-v3')
+ implementation project(':service')
+ api project(':client')
+ api project(':ui')
+
+ // Kotlin
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
+
+ // AndroidX UI
+ implementation "androidx.appcompat:appcompat:$appcompatVersion"
+ implementation "androidx.preference:preference:$preferenceVersion"
+ implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
+
+ // Navigation
+ implementation "androidx.navigation:navigation-fragment:$navigationVersion"
+ implementation "androidx.navigation:navigation-ui:$navigationVersion"
+ implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
+ implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
+}
+
+afterEvaluate {
+ android.applicationVariants.all { variant ->
+ variant.resValue 'string', 'application_id', variant.applicationId
+ }
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..84b4b22
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/org/microg/nlp/app/AboutFragment.java b/app/src/main/java/org/microg/nlp/app/AboutFragment.java
new file mode 100644
index 0000000..8cb0004
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/AboutFragment.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app;
+
+import androidx.fragment.app.Fragment;
+
+import org.microg.nlp.app.BuildConfig;
+
+import org.microg.nlp.app.tools.ui.AbstractAboutFragment;
+import org.microg.nlp.app.tools.ui.AbstractSettingsActivity;
+
+import java.util.List;
+
+public class AboutFragment extends AbstractAboutFragment {
+
+ @Override
+ protected void collectLibraries(List libraries) {
+ libraries.add(new AbstractAboutFragment.Library("org.microg.nlp.service", "UnifiedNlp", "Apache License 2.0, microG Team"));
+ }
+
+ public static class AsActivity extends AbstractSettingsActivity {
+ public AsActivity() {
+ showHomeAsUp = true;
+ }
+
+ @Override
+ protected Fragment getFragment() {
+ return new AboutFragment();
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/LocationSettingsActivity.java b/app/src/main/java/org/microg/nlp/app/LocationSettingsActivity.java
new file mode 100644
index 0000000..ce9484e
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/LocationSettingsActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app;
+
+import android.app.Activity;
+
+public class LocationSettingsActivity extends Activity {
+}
diff --git a/app/src/main/java/org/microg/nlp/app/SelfCheckFragment.java b/app/src/main/java/org/microg/nlp/app/SelfCheckFragment.java
new file mode 100644
index 0000000..b56762a
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/SelfCheckFragment.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.LayoutInflater;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+
+import org.microg.nlp.app.tools.selfcheck.PermissionCheckGroup;
+import org.microg.nlp.app.tools.selfcheck.SelfCheckGroup;
+import org.microg.nlp.app.tools.ui.AbstractSelfCheckFragment;
+import org.microg.nlp.app.tools.ui.AbstractSettingsActivity;
+import org.microg.nlp.app.tools.selfcheck.NlpOsCompatChecks;
+import org.microg.nlp.app.tools.selfcheck.NlpStatusChecks;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.Manifest.permission.ACCESS_COARSE_LOCATION;
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.LOLLIPOP_MR1;
+
+public class SelfCheckFragment extends AbstractSelfCheckFragment {
+
+ @Override
+ protected void prepareSelfCheckList(List checks) {
+ if (SDK_INT > LOLLIPOP_MR1) {
+ checks.add(new PermissionCheckGroup(ACCESS_COARSE_LOCATION));
+ }
+ checks.add(new NlpOsCompatChecks());
+ checks.add(new NlpStatusChecks());
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ reset(LayoutInflater.from(getContext()));
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ reset(LayoutInflater.from(getContext()));
+ super.onActivityResult(requestCode, resultCode, data);
+ }
+
+ public static class AsActivity extends AbstractSettingsActivity {
+ public AsActivity() {
+ showHomeAsUp = true;
+ }
+
+ @Override
+ protected Fragment getFragment() {
+ return new SelfCheckFragment();
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/SettingsActivity.java b/app/src/main/java/org/microg/nlp/app/SettingsActivity.java
new file mode 100644
index 0000000..cf19a8f
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/SettingsActivity.java
@@ -0,0 +1,34 @@
+package org.microg.nlp.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.navigation.NavController;
+import androidx.navigation.fragment.NavHostFragment;
+import androidx.navigation.ui.AppBarConfiguration;
+import androidx.navigation.ui.NavigationUI;
+
+public class SettingsActivity extends AppCompatActivity {
+ private AppBarConfiguration appBarConfiguration;
+
+ private NavController getNavController() {
+ return ((NavHostFragment)getSupportFragmentManager().findFragmentById(R.id.navhost)).getNavController();
+ }
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.settings_root_activity);
+
+ appBarConfiguration = new AppBarConfiguration.Builder(getNavController().getGraph()).build();
+ NavigationUI.setupActionBarWithNavController(this, getNavController(), appBarConfiguration);
+ }
+
+ @Override
+ public boolean onSupportNavigateUp() {
+ return NavigationUI.navigateUp(getNavController(), appBarConfiguration) || super.onSupportNavigateUp();
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/selfcheck/NlpOsCompatChecks.java b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/NlpOsCompatChecks.java
new file mode 100644
index 0000000..6e752a1
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/NlpOsCompatChecks.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2013-2016 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.selfcheck;
+
+import android.content.Context;
+
+import org.microg.nlp.app.R;
+
+import java.util.Arrays;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN;
+import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR1;
+import static android.os.Build.VERSION_CODES.KITKAT;
+import static android.os.Build.VERSION_CODES.M;
+
+public class NlpOsCompatChecks implements SelfCheckGroup {
+
+ public static final String CONFIG_NL_PROVIDER = "config_networkLocationProvider";
+ public static final String CONFIG_NL_PROVIDER_PACKAGE_NAME = "config_networkLocationProviderPackageName";
+ public static final String CONFIG_ENABLE_NL_OVERLAY = "config_enableNetworkLocationOverlay";
+ public static final String CONFIG_NL_PROVIDER_PACKAGE_NAMES = "config_locationProviderPackageNames";
+
+ @Override
+ public String getGroupName(Context context) {
+ return context.getString(R.string.self_check_cat_nlpcompat);
+ }
+
+ @Override
+ public void doChecks(Context context, ResultCollector collector) {
+ checkSystemIsSupported(context, collector);
+ checkSystemIsConfigured(context, collector);
+ }
+
+ private boolean checkSystemIsSupported(Context context, ResultCollector collector) {
+ boolean isSupported = (SDK_INT >= KITKAT && SDK_INT <= M);
+ collector.addResult(context.getString(R.string.self_check_name_system_supported),
+ isSupported ? Result.Positive : Result.Unknown, context.getString(R.string.self_check_resolution_system_supported));
+ return isSupported;
+ }
+
+ private boolean checkSystemIsConfigured(Context context, ResultCollector collector) {
+ // 2.3+ com.android.internal.R.string.config_networkLocationProvider
+ // 4.1+ com.android.internal.R.string.config_networkLocationProviderPackageName
+ // 4.2+ com.android.internal.R.array.config_locationProviderPackageNames
+ // 4.3+ com.android.internal.R.array.config_locationProviderPackageNames /
+ // com.android.internal.R.string.config_networkLocationProviderPackageName /
+ // com.android.internal.R.bool.config_enableNetworkLocationOverlay
+ boolean systemMatchesPackage = false;
+ if (SDK_INT < JELLY_BEAN) {
+ systemMatchesPackage |= context.getPackageName().equals(getResourceString(context, CONFIG_NL_PROVIDER));
+ } else {
+ boolean overlay = getResourceBool(context, CONFIG_ENABLE_NL_OVERLAY);
+ if (SDK_INT < JELLY_BEAN_MR1 || (SDK_INT > JELLY_BEAN_MR1 && !overlay)) {
+ systemMatchesPackage |= context.getPackageName().equals(getResourceString(context, CONFIG_NL_PROVIDER_PACKAGE_NAME));
+ }
+ if (SDK_INT == JELLY_BEAN_MR1 || (SDK_INT > JELLY_BEAN_MR1 && overlay)) {
+ systemMatchesPackage |= Arrays.asList(getResourceArray(context, CONFIG_NL_PROVIDER_PACKAGE_NAMES)).contains(context.getPackageName());
+ }
+ }
+ collector.addResult(context.getString(R.string.self_check_name_nlp_package_name),
+ systemMatchesPackage ? Result.Positive : Result.Negative, context.getString(R.string.self_check_resolution_nlp_package_name));
+ return systemMatchesPackage;
+ }
+
+ private String[] getResourceArray(Context context, String identifier) {
+ try {
+ int resId = context.getResources().getIdentifier(identifier, "array", "android");
+ if (resId == 0)
+ resId = context.getResources().getIdentifier(identifier, "array", "com.android.internal");
+ return context.getResources().getStringArray(resId);
+ } catch (Exception e) {
+ return new String[0];
+ }
+ }
+
+ private boolean getResourceBool(Context context, String identifier) {
+ try {
+ int resId = context.getResources().getIdentifier(identifier, "bool", "android");
+ if (resId == 0)
+ resId = context.getResources().getIdentifier(identifier, "bool", "com.android.internal");
+ return context.getResources().getBoolean(resId);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ private String getResourceString(Context context, String identifier) {
+ try {
+ int resId = context.getResources().getIdentifier(identifier, "string", "android");
+ if (resId == 0)
+ resId = context.getResources().getIdentifier(identifier, "string", "com.android.internal");
+ return context.getString(resId);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/selfcheck/NlpStatusChecks.java b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/NlpStatusChecks.java
new file mode 100644
index 0000000..149c65c
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/NlpStatusChecks.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2013-2016 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.selfcheck;
+
+import android.content.Context;
+import android.location.Location;
+import android.location.LocationListener;
+import android.location.LocationManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+import org.microg.nlp.app.R;
+import org.microg.nlp.client.UnifiedLocationClient;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static android.content.Context.LOCATION_SERVICE;
+import static android.location.LocationManager.NETWORK_PROVIDER;
+import static org.microg.nlp.api.Constants.LOCATION_EXTRA_BACKEND_COMPONENT;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Negative;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Positive;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Unknown;
+
+public class NlpStatusChecks implements SelfCheckGroup {
+ @Override
+ public String getGroupName(Context context) {
+ return context.getString(R.string.self_check_cat_nlp_status);
+ }
+
+ @Override
+ public void doChecks(Context context, ResultCollector collector) {
+ providerWasBound(context, collector);
+ if (isNetworkLocationEnabled(context, collector)) {
+ isProvidingLastLocation(context, collector);
+ isProvidingLocation(context, collector);
+ }
+ }
+
+ private boolean providerWasBound(Context context, ResultCollector collector) {
+ collector.addResult(context.getString(R.string.self_check_name_nlp_bound),
+ UnifiedLocationClient.get(context).isAvailable() ? Positive : Negative, context.getString(R.string.self_check_resolution_nlp_bound));
+ return UnifiedLocationClient.get(context).isAvailable();
+ }
+
+ private boolean isNetworkLocationEnabled(Context context, ResultCollector collector) {
+ LocationManager locationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);
+ boolean networkEnabled = locationManager.getProviders(true).contains(NETWORK_PROVIDER);
+ collector.addResult(context.getString(R.string.self_check_name_network_enabled),
+ networkEnabled ? Positive : Negative, context.getString(R.string.self_check_resolution_network_enabled));
+ return networkEnabled;
+ }
+
+ private boolean isProvidingLastLocation(Context context, ResultCollector collector) {
+ LocationManager locationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);
+ try {
+ Location location = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
+ boolean hasKnown = location != null && location.getExtras() != null &&
+ location.getExtras().containsKey(LOCATION_EXTRA_BACKEND_COMPONENT);
+ collector.addResult(context.getString(R.string.self_check_name_last_location),
+ hasKnown ? Positive : Unknown, context.getString(R.string.self_check_resolution_last_location));
+ return hasKnown;
+ } catch (SecurityException e) {
+ collector.addResult(context.getString(R.string.self_check_name_last_location), Unknown, context.getString(R.string.self_check_loc_perm_missing));
+ return false;
+ }
+ }
+
+ private void isProvidingLocation(final Context context, final ResultCollector collector) {
+ final AtomicBoolean result = new AtomicBoolean(false);
+ LocationManager locationManager = (LocationManager) context.getSystemService(LOCATION_SERVICE);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (result) {
+ try {
+ result.wait(10000);
+ } catch (InterruptedException e) {
+ }
+ collector.addResult(context.getString(R.string.self_check_name_nlp_is_providing),
+ result.get() ? Positive : Unknown, context.getString(R.string.self_check_resolution_nlp_is_providing));
+ }
+ }
+ }).start();
+ try {
+ locationManager.requestSingleUpdate(NETWORK_PROVIDER, new LocationListener() {
+ @Override
+ public void onLocationChanged(Location location) {
+ synchronized (result) {
+ result.set(location != null && location.getExtras() != null &&
+ location.getExtras().containsKey(LOCATION_EXTRA_BACKEND_COMPONENT));
+ result.notifyAll();
+ }
+ }
+
+ @Override
+ public void onStatusChanged(String provider, int status, Bundle extras) {
+ }
+
+ @Override
+ public void onProviderEnabled(String provider) {
+ }
+
+ @Override
+ public void onProviderDisabled(String provider) {
+ }
+ }, null);
+ } catch (SecurityException e) {
+ collector.addResult(context.getString(R.string.self_check_name_last_location), Unknown, context.getString(R.string.self_check_loc_perm_missing));
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/selfcheck/PermissionCheckGroup.java b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/PermissionCheckGroup.java
new file mode 100644
index 0000000..9044675
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/PermissionCheckGroup.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.selfcheck;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.util.Log;
+
+import androidx.fragment.app.Fragment;
+
+import org.microg.nlp.app.R;
+
+import static android.os.Build.VERSION_CODES.M;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Negative;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Positive;
+
+@TargetApi(M)
+public class PermissionCheckGroup implements SelfCheckGroup {
+ private static final String TAG = "SelfCheckPerms";
+
+ private String[] permissions;
+
+ public PermissionCheckGroup(String... permissions) {
+ this.permissions = permissions;
+ }
+
+ @Override
+ public String getGroupName(Context context) {
+ return context.getString(R.string.self_check_cat_permissions);
+ }
+
+ @Override
+ public void doChecks(Context context, ResultCollector collector) {
+ for (String permission : permissions) {
+ doPermissionCheck(context, collector, permission);
+ }
+ }
+
+ private void doPermissionCheck(Context context, ResultCollector collector, final String permission) {
+ PackageManager pm = context.getPackageManager();
+ try {
+ PermissionInfo info = pm.getPermissionInfo(permission, 0);
+ CharSequence permLabel = info.loadLabel(pm);
+ collector.addResult(context.getString(R.string.self_check_name_permission, permLabel),
+ context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED ? Positive : Negative,
+ context.getString(R.string.self_check_resolution_permission),
+ fragment -> fragment.requestPermissions(new String[]{permission}, 0));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.w(TAG, e);
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/selfcheck/SelfCheckGroup.java b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/SelfCheckGroup.java
new file mode 100644
index 0000000..6dd0e43
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/selfcheck/SelfCheckGroup.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.selfcheck;
+
+import android.content.Context;
+
+import androidx.fragment.app.Fragment;
+
+public interface SelfCheckGroup {
+ String getGroupName(Context context);
+
+ void doChecks(Context context, ResultCollector collector);
+
+ interface ResultCollector {
+ void addResult(String name, Result value, String resolution);
+
+ void addResult(String name, Result value, String resolution, CheckResolver resolver);
+ }
+
+ interface CheckResolver {
+ void tryResolve(Fragment fragment);
+ }
+
+ enum Result {
+ Positive, Negative, Unknown
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractAboutFragment.java b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractAboutFragment.java
new file mode 100644
index 0000000..46f95d8
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractAboutFragment.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import org.microg.nlp.app.R;
+
+public abstract class AbstractAboutFragment extends Fragment {
+
+ protected abstract void collectLibraries(List libraries);
+
+ public static Drawable getIcon(Context context) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ return pm.getPackageInfo(context.getPackageName(), 0).applicationInfo.loadIcon(pm);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Never happens, self package always exists!
+ throw new RuntimeException(e);
+ }
+ }
+
+ public static String getAppName(Context context) {
+ try {
+ PackageManager pm = context.getPackageManager();
+ CharSequence label = pm.getPackageInfo(context.getPackageName(), 0).applicationInfo.loadLabel(pm);
+ if (TextUtils.isEmpty(label)) return context.getPackageName();
+ return label.toString().trim();
+ } catch (PackageManager.NameNotFoundException e) {
+ // Never happens, self package always exists!
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected String getAppName() {
+ return getAppName(getContext());
+ }
+
+ public static String getLibVersion(String packageName) {
+ try {
+ String versionName = (String) Class.forName(packageName + ".BuildConfig").getField("VERSION_NAME").get(null);
+ if (TextUtils.isEmpty(versionName)) return "";
+ return versionName.trim();
+ } catch (Exception e) {
+ return "";
+ }
+ }
+
+ public static String getSelfVersion(Context context) {
+ return getLibVersion(context.getPackageName());
+ }
+
+ protected String getSelfVersion() {
+ return getSelfVersion(getContext());
+ }
+
+ protected String getSummary() {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View aboutRoot = inflater.inflate(R.layout.about_root, container, false);
+ ((ImageView) aboutRoot.findViewById(android.R.id.icon)).setImageDrawable(getIcon(getContext()));
+ ((TextView) aboutRoot.findViewById(android.R.id.title)).setText(getAppName());
+ ((TextView) aboutRoot.findViewById(R.id.about_version)).setText(getString(R.string.about_version_str, getSelfVersion()));
+ String summary = getSummary();
+ if (summary != null) {
+ ((TextView) aboutRoot.findViewById(android.R.id.summary)).setText(summary);
+ aboutRoot.findViewById(android.R.id.summary).setVisibility(View.VISIBLE);
+ }
+
+ List libraries = new ArrayList();
+ collectLibraries(libraries);
+ Collections.sort(libraries);
+ ((ListView) aboutRoot.findViewById(android.R.id.list)).setAdapter(new LibraryAdapter(getContext(), libraries.toArray(new Library[libraries.size()])));
+
+ return aboutRoot;
+ }
+
+ private class LibraryAdapter extends ArrayAdapter {
+
+ public LibraryAdapter(Context context, Library[] libraries) {
+ super(context, android.R.layout.simple_list_item_2, android.R.id.text1, libraries);
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ View v = super.getView(position, convertView, parent);
+ ((TextView) v.findViewById(android.R.id.text1)).setText(getString(R.string.about_name_version_str, getItem(position).name, getLibVersion(getItem(position).packageName)));
+ ((TextView) v.findViewById(android.R.id.text2)).setText(getItem(position).copyright != null ? getItem(position).copyright : getString(R.string.about_default_license));
+ return v;
+ }
+ }
+
+ protected static class Library implements Comparable {
+ private final String packageName;
+ private final String name;
+ private final String copyright;
+
+ public Library(String packageName, String name, String copyright) {
+ this.packageName = packageName;
+ this.name = name;
+ this.copyright = copyright;
+ }
+
+ @Override
+ public String toString() {
+ return name + ", " + copyright;
+ }
+
+ @Override
+ public int compareTo(Library another) {
+ return name.toLowerCase(Locale.US).compareTo(another.name.toLowerCase(Locale.US));
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSelfCheckFragment.java b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSelfCheckFragment.java
new file mode 100644
index 0000000..13f8fbf
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSelfCheckFragment.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.CheckBox;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import org.microg.nlp.app.tools.selfcheck.SelfCheckGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.view.View.GONE;
+import static android.view.View.INVISIBLE;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Negative;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Positive;
+import static org.microg.nlp.app.tools.selfcheck.SelfCheckGroup.Result.Unknown;
+
+import org.microg.nlp.app.R;
+
+public abstract class AbstractSelfCheckFragment extends Fragment {
+ private static final String TAG = "SelfCheck";
+
+ private ViewGroup root;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View scrollRoot = inflater.inflate(R.layout.self_check, container, false);
+ root = (ViewGroup) scrollRoot.findViewById(R.id.self_check_root);
+ reset(inflater);
+ return scrollRoot;
+ }
+
+ protected abstract void prepareSelfCheckList(List checks);
+
+ protected void reset(LayoutInflater inflater) {
+ List selfCheckGroupList = new ArrayList();
+ prepareSelfCheckList(selfCheckGroupList);
+
+ root.removeAllViews();
+ for (SelfCheckGroup group : selfCheckGroupList) {
+ View groupView = inflater.inflate(R.layout.self_check_group, root, false);
+ ((TextView) groupView.findViewById(android.R.id.title)).setText(group.getGroupName(getContext()));
+ final ViewGroup viewGroup = (ViewGroup) groupView.findViewById(R.id.group_content);
+ final SelfCheckGroup.ResultCollector collector = new GroupResultCollector(viewGroup);
+ try {
+ group.doChecks(getContext(), collector);
+ } catch (Exception e) {
+ Log.w(TAG, "Failed during check " + group.getGroupName(getContext()), e);
+ collector.addResult("Self-check failed:", Negative, "An exception occurred during self-check. Please report this issue.");
+ }
+ root.addView(groupView);
+ }
+ }
+
+ private class GroupResultCollector implements SelfCheckGroup.ResultCollector {
+ private final ViewGroup viewGroup;
+
+ public GroupResultCollector(ViewGroup viewGroup) {
+ this.viewGroup = viewGroup;
+ }
+
+ @Override
+ public void addResult(final String name, final SelfCheckGroup.Result result, final String resolution) {
+ addResult(name, result, resolution, null);
+ }
+
+ @Override
+ public void addResult(final String name, final SelfCheckGroup.Result result, final String resolution,
+ final SelfCheckGroup.CheckResolver resolver) {
+ if (result == null || getActivity() == null) return;
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ View resultEntry = LayoutInflater.from(getContext()).inflate(R.layout.self_check_entry, viewGroup, false);
+ ((TextView) resultEntry.findViewById(R.id.self_check_name)).setText(name);
+ resultEntry.findViewById(R.id.self_check_result).setOnTouchListener(new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return true;
+ }
+ });
+ if (result == Positive) {
+ ((CheckBox) resultEntry.findViewById(R.id.self_check_result)).setChecked(true);
+ resultEntry.findViewById(R.id.self_check_resolution).setVisibility(GONE);
+ } else {
+ ((TextView) resultEntry.findViewById(R.id.self_check_resolution)).setText(resolution);
+ if (result == Unknown) {
+ resultEntry.findViewById(R.id.self_check_result).setVisibility(INVISIBLE);
+ }
+ if (resolver != null) {
+ resultEntry.setClickable(true);
+ resultEntry.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ resolver.tryResolve(AbstractSelfCheckFragment.this);
+ }
+ });
+ }
+ }
+ viewGroup.addView(resultEntry);
+ }
+ });
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSettingsActivity.java b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSettingsActivity.java
new file mode 100644
index 0000000..7de224f
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSettingsActivity.java
@@ -0,0 +1,78 @@
+package org.microg.nlp.app.tools.ui;
+
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.ViewGroup;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentTransaction;
+import org.microg.nlp.app.R;
+
+public abstract class AbstractSettingsActivity extends AppCompatActivity {
+ protected boolean showHomeAsUp = false;
+ protected int preferencesResource = 0;
+ private ViewGroup customBarContainer;
+ protected int customBarLayout = 0;
+ protected SwitchBar switchBar;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.settings_activity);
+ if (showHomeAsUp) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+
+ switchBar = (SwitchBar) findViewById(R.id.switch_bar);
+
+ customBarContainer = (ViewGroup) findViewById(R.id.custom_bar);
+ if (customBarLayout != 0) {
+ customBarContainer.addView(getLayoutInflater().inflate(customBarLayout, customBarContainer, false));
+ }
+
+ getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content_wrapper, getFragment())
+ .commit();
+ }
+
+ public void setCustomBarLayout(int layout) {
+ customBarLayout = layout;
+ if (customBarContainer != null) {
+ customBarContainer.removeAllViews();
+ customBarContainer.addView(getLayoutInflater().inflate(customBarLayout, customBarContainer, false));
+ }
+ }
+
+ public SwitchBar getSwitchBar() {
+ return switchBar;
+ }
+
+ public void replaceFragment(Fragment fragment) {
+ getSupportFragmentManager().beginTransaction()
+ .addToBackStack("root")
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
+ .replace(R.id.content_wrapper, fragment)
+ .commit();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ protected Fragment getFragment() {
+ if (preferencesResource == 0) {
+ throw new IllegalStateException("Neither preferencesResource given, nor overriden getFragment()");
+ }
+ ResourceSettingsFragment fragment = new ResourceSettingsFragment();
+ Bundle b = new Bundle();
+ b.putInt(ResourceSettingsFragment.EXTRA_PREFERENCE_RESOURCE, preferencesResource);
+ fragment.setArguments(b);
+ return fragment;
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSettingsFragment.java b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSettingsFragment.java
new file mode 100644
index 0000000..29e94f7
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/AbstractSettingsFragment.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+public abstract class AbstractSettingsFragment extends PreferenceFragmentCompat {
+ private static final String TAG = AbstractSettingsFragment.class.getSimpleName();
+
+ private static final String DIALOG_FRAGMENT_TAG = "androidx.preference.PreferenceFragment.DIALOG";
+
+ @Override
+ public void onDisplayPreferenceDialog(Preference preference) {
+ if (preference instanceof DialogPreference) {
+ DialogFragment f = DialogPreference.DialogPreferenceCompatDialogFragment.newInstance(preference.getKey());
+ f.setTargetFragment(this, 0);
+ f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ } else {
+ super.onDisplayPreferenceDialog(preference);
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/DialogPreference.java b/app/src/main/java/org/microg/nlp/app/tools/ui/DialogPreference.java
new file mode 100644
index 0000000..1410fa5
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/DialogPreference.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2013-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceDialogFragmentCompat;
+import androidx.preference.PreferenceFragmentCompat;
+import androidx.preference.PreferenceViewHolder;
+
+import org.microg.nlp.app.R;
+
+public class DialogPreference extends androidx.preference.DialogPreference implements PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
+
+ private static final String DIALOG_FRAGMENT_TAG =
+ "android.support.v7.preference.PreferenceFragment.DIALOG";
+
+ public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public DialogPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DialogPreference(Context context) {
+ super(context);
+ }
+
+ protected View onCreateDialogView() {
+ return null;
+ }
+
+ /**
+ * Called when the dialog is dismissed and should be used to save data to
+ * the {@link SharedPreferences}.
+ *
+ * @param positiveResult Whether the positive button was clicked (true), or
+ * the negative button was clicked or the dialog was canceled (false).
+ */
+ protected void onDialogClosed(boolean positiveResult) {
+ }
+
+ @Override
+ public boolean onPreferenceDisplayDialog(PreferenceFragmentCompat caller, Preference pref) {
+ DialogPreferenceCompatDialogFragment fragment = new DialogPreferenceCompatDialogFragment();
+ fragment.setTargetFragment(caller, 0);
+ fragment.show(caller.getFragmentManager(), DIALOG_FRAGMENT_TAG);
+ return true;
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+
+ ViewGroup.LayoutParams layoutParams = view.findViewById(R.id.icon_frame).getLayoutParams();
+ if (layoutParams instanceof LinearLayout.LayoutParams) {
+ if (((LinearLayout.LayoutParams) layoutParams).leftMargin < 0) {
+ ((LinearLayout.LayoutParams) layoutParams).leftMargin = 0;
+ }
+ }
+ }
+
+ public static class DialogPreferenceCompatDialogFragment extends PreferenceDialogFragmentCompat {
+
+ @Override
+ protected View onCreateDialogView(Context context) {
+ if (getPreference() instanceof DialogPreference) {
+ View view = ((DialogPreference) getPreference()).onCreateDialogView();
+ if (view != null) return view;
+ }
+ return super.onCreateDialogView(context);
+ }
+
+ @Override
+ public void onDialogClosed(boolean positiveResult) {
+ if (getPreference() instanceof DialogPreference) {
+ ((DialogPreference) getPreference()).onDialogClosed(positiveResult);
+ }
+ }
+
+ public static DialogFragment newInstance(String key) {
+ final DialogPreferenceCompatDialogFragment fragment = new DialogPreferenceCompatDialogFragment();
+ final Bundle b = new Bundle(1);
+ b.putString(ARG_KEY, key);
+ fragment.setArguments(b);
+ return fragment;
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/ResourceSettingsFragment.java b/app/src/main/java/org/microg/nlp/app/tools/ui/ResourceSettingsFragment.java
new file mode 100644
index 0000000..3d978cb
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/ResourceSettingsFragment.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+
+public class ResourceSettingsFragment extends AbstractSettingsFragment {
+
+ public static final String EXTRA_PREFERENCE_RESOURCE = "preferencesResource";
+
+ protected int preferencesResource;
+
+ @Override
+ public void onCreatePreferences(@Nullable Bundle savedInstanceState, String rootKey) {
+ Bundle b = getArguments();
+ if (b != null) {
+ preferencesResource = b.getInt(EXTRA_PREFERENCE_RESOURCE, preferencesResource);
+ }
+ if (preferencesResource != 0) {
+ addPreferencesFromResource(preferencesResource);
+ }
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/SwitchBar.java b/app/src/main/java/org/microg/nlp/app/tools/ui/SwitchBar.java
new file mode 100644
index 0000000..3559ebd
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/SwitchBar.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.style.TextAppearanceSpan;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.appcompat.widget.SwitchCompat;
+
+import java.util.ArrayList;
+
+import static android.os.Build.VERSION.SDK_INT;
+import org.microg.nlp.app.R;
+
+public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener,
+ View.OnClickListener {
+
+ public static interface OnSwitchChangeListener {
+ /**
+ * Called when the checked state of the Switch has changed.
+ *
+ * @param switchView The Switch view whose state has changed.
+ * @param isChecked The new checked state of switchView.
+ */
+ void onSwitchChanged(SwitchCompat switchView, boolean isChecked);
+ }
+
+ private final TextAppearanceSpan mSummarySpan;
+
+ private ToggleSwitch mSwitch;
+ private TextView mTextView;
+ private String mLabel;
+ private String mSummary;
+
+ private ArrayList mSwitchChangeListeners =
+ new ArrayList();
+
+ public SwitchBar(Context context) {
+ this(context, null);
+ }
+
+ public SwitchBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ LayoutInflater.from(context).inflate(R.layout.switch_bar, this);
+
+ mTextView = (TextView) findViewById(R.id.switch_text);
+ if (SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
+ mTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+ mLabel = getResources().getString(R.string.abc_capital_off);
+ mSummarySpan = new TextAppearanceSpan(context, androidx.appcompat.R.style.TextAppearance_AppCompat_Widget_Switch);
+ updateText();
+
+ mSwitch = (ToggleSwitch) findViewById(R.id.switch_widget);
+ // Prevent onSaveInstanceState() to be called as we are managing the state of the Switch
+ // on our own
+ mSwitch.setSaveEnabled(false);
+ if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mSwitch.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ addOnSwitchChangeListener(new OnSwitchChangeListener() {
+ @Override
+ public void onSwitchChanged(SwitchCompat switchView, boolean isChecked) {
+ setTextViewLabel(isChecked);
+ }
+ });
+
+ setOnClickListener(this);
+
+ // Default is hide
+ setVisibility(View.GONE);
+ }
+
+ public void setTextViewLabel(boolean isChecked) {
+ mLabel = getResources()
+ .getString(isChecked ? R.string.abc_capital_on : R.string.abc_capital_off);
+ updateText();
+ }
+
+ public void setSummary(String summary) {
+ mSummary = summary;
+ updateText();
+ }
+
+ private void updateText() {
+ if (TextUtils.isEmpty(mSummary)) {
+ mTextView.setText(mLabel);
+ return;
+ }
+ final SpannableStringBuilder ssb = new SpannableStringBuilder(mLabel).append('\n');
+ final int start = ssb.length();
+ ssb.append(mSummary);
+ ssb.setSpan(mSummarySpan, start, ssb.length(), 0);
+ mTextView.setText(ssb);
+ }
+
+ public void setChecked(boolean checked) {
+ setTextViewLabel(checked);
+ mSwitch.setChecked(checked);
+ }
+
+ public void setCheckedInternal(boolean checked) {
+ setTextViewLabel(checked);
+ mSwitch.setCheckedInternal(checked);
+ }
+
+ public boolean isChecked() {
+ return mSwitch.isChecked();
+ }
+
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ mTextView.setEnabled(enabled);
+ mSwitch.setEnabled(enabled);
+ }
+
+ public final ToggleSwitch getSwitch() {
+ return mSwitch;
+ }
+
+ public void show() {
+ if (!isShowing()) {
+ setVisibility(View.VISIBLE);
+ mSwitch.setOnCheckedChangeListener(this);
+ }
+ }
+
+ public void hide() {
+ if (isShowing()) {
+ setVisibility(View.GONE);
+ mSwitch.setOnCheckedChangeListener(null);
+ }
+ }
+
+ public boolean isShowing() {
+ return (getVisibility() == View.VISIBLE);
+ }
+
+ @Override
+ public void onClick(View v) {
+ final boolean isChecked = !mSwitch.isChecked();
+ setChecked(isChecked);
+ }
+
+ public void propagateChecked(boolean isChecked) {
+ final int count = mSwitchChangeListeners.size();
+ for (int n = 0; n < count; n++) {
+ mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
+ }
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ propagateChecked(isChecked);
+ }
+
+ public void addOnSwitchChangeListener(OnSwitchChangeListener listener) {
+ if (mSwitchChangeListeners.contains(listener)) {
+ throw new IllegalStateException("Cannot add twice the same OnSwitchChangeListener");
+ }
+ mSwitchChangeListeners.add(listener);
+ }
+
+ public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) {
+ if (!mSwitchChangeListeners.contains(listener)) {
+ throw new IllegalStateException("Cannot remove OnSwitchChangeListener");
+ }
+ mSwitchChangeListeners.remove(listener);
+ }
+
+ static class SavedState extends BaseSavedState {
+ boolean checked;
+ boolean visible;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ /**
+ * Constructor called from {@link #CREATOR}
+ */
+ private SavedState(Parcel in) {
+ super(in);
+ checked = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ visible = (Boolean) in.readValue(Boolean.class.getClassLoader());
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ super.writeToParcel(out, flags);
+ out.writeValue(checked);
+ out.writeValue(visible);
+ }
+
+ @Override
+ public String toString() {
+ return "SwitchBar.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " checked=" + checked
+ + " visible=" + visible + "}";
+ }
+
+ public static final Parcelable.Creator CREATOR
+ = new Parcelable.Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+
+ SavedState ss = new SavedState(superState);
+ ss.checked = mSwitch.isChecked();
+ ss.visible = isShowing();
+ return ss;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ SavedState ss = (SavedState) state;
+
+ super.onRestoreInstanceState(ss.getSuperState());
+
+ mSwitch.setCheckedInternal(ss.checked);
+ setTextViewLabel(ss.checked);
+ setVisibility(ss.visible ? View.VISIBLE : View.GONE);
+ mSwitch.setOnCheckedChangeListener(ss.visible ? this : null);
+
+ requestLayout();
+ }
+}
diff --git a/app/src/main/java/org/microg/nlp/app/tools/ui/ToggleSwitch.java b/app/src/main/java/org/microg/nlp/app/tools/ui/ToggleSwitch.java
new file mode 100644
index 0000000..dcf3860
--- /dev/null
+++ b/app/src/main/java/org/microg/nlp/app/tools/ui/ToggleSwitch.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ * Copyright (C) 2014-2017 microG Project Team
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.microg.nlp.app.tools.ui;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.appcompat.widget.SwitchCompat;
+
+@SuppressLint("NewApi")
+public class ToggleSwitch extends SwitchCompat {
+
+ private ToggleSwitch.OnBeforeCheckedChangeListener mOnBeforeListener;
+
+ public interface OnBeforeCheckedChangeListener {
+ boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked);
+ }
+
+ public ToggleSwitch(Context context) {
+ super(context);
+ }
+
+ public ToggleSwitch(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ToggleSwitch(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) {
+ mOnBeforeListener = listener;
+ }
+
+ @Override
+ public void setChecked(boolean checked) {
+ if (mOnBeforeListener != null
+ && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) {
+ return;
+ }
+ super.setChecked(checked);
+ }
+
+ public void setCheckedInternal(boolean checked) {
+ super.setChecked(checked);
+ }
+}
diff --git a/app/src/main/kotlin/org/microg/nlp/app/SettingsFragment.kt b/app/src/main/kotlin/org/microg/nlp/app/SettingsFragment.kt
new file mode 100644
index 0000000..ede1a49
--- /dev/null
+++ b/app/src/main/kotlin/org/microg/nlp/app/SettingsFragment.kt
@@ -0,0 +1,50 @@
+/*
+ * SPDX-FileCopyrightText: 2020, microG Project Team
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package org.microg.nlp.app
+
+import android.os.Bundle
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import androidx.preference.Preference
+import org.microg.nlp.client.UnifiedLocationClient
+import org.microg.nlp.app.tools.ui.ResourceSettingsFragment
+import org.microg.nlp.ui.navigate
+
+class SettingsFragment : ResourceSettingsFragment() {
+ override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
+ super.onCreatePreferences(savedInstanceState, rootKey)
+
+ findPreference(PREF_UNIFIEDNLP)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ findNavController().navigate(requireContext(), R.id.openUnifiedNlpSettings)
+ true
+ }
+ findPreference(PREF_ABOUT)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
+ findNavController().navigate(requireContext(), R.id.openAbout)
+ true
+ }
+ }
+
+ override fun onResume() {
+ super.onResume()
+ lifecycleScope.launchWhenResumed {
+ updateDetails()
+ }
+ }
+
+ private suspend fun updateDetails() {
+ val backendCount = UnifiedLocationClient[requireContext()].getLocationBackends().size + UnifiedLocationClient[requireContext()].getGeocoderBackends().size
+ findPreference(PREF_UNIFIEDNLP)!!.summary = resources.getQuantityString(R.plurals.pref_unifiednlp_summary, backendCount, backendCount);
+ }
+
+ companion object {
+ const val PREF_ABOUT = "pref_about"
+ const val PREF_UNIFIEDNLP = "pref_unifiednlp"
+ }
+
+ init {
+ preferencesResource = R.xml.preferences_start
+ }
+}
diff --git a/app/src/main/res/drawable/ic_info_outline.xml b/app/src/main/res/drawable/ic_info_outline.xml
new file mode 100644
index 0000000..8e049cc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_info_outline.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_map_marker.xml b/app/src/main/res/drawable/ic_map_marker.xml
new file mode 100644
index 0000000..b97f8b8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_map_marker.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/self_check.xml b/app/src/main/res/drawable/self_check.xml
new file mode 100644
index 0000000..714b5e1
--- /dev/null
+++ b/app/src/main/res/drawable/self_check.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/about_root.xml b/app/src/main/res/layout/about_root.xml
new file mode 100644
index 0000000..e563293
--- /dev/null
+++ b/app/src/main/res/layout/about_root.xml
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/preference_category_no_label.xml b/app/src/main/res/layout/preference_category_no_label.xml
new file mode 100644
index 0000000..dafabb2
--- /dev/null
+++ b/app/src/main/res/layout/preference_category_no_label.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/app/src/main/res/layout/self_check.xml b/app/src/main/res/layout/self_check.xml
new file mode 100644
index 0000000..4037035
--- /dev/null
+++ b/app/src/main/res/layout/self_check.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/self_check_entry.xml b/app/src/main/res/layout/self_check_entry.xml
new file mode 100644
index 0000000..da3d743
--- /dev/null
+++ b/app/src/main/res/layout/self_check_entry.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/self_check_group.xml b/app/src/main/res/layout/self_check_group.xml
new file mode 100644
index 0000000..3f97b14
--- /dev/null
+++ b/app/src/main/res/layout/self_check_group.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml
new file mode 100644
index 0000000..6914f34
--- /dev/null
+++ b/app/src/main/res/layout/settings_activity.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/settings_root_activity.xml b/app/src/main/res/layout/settings_root_activity.xml
new file mode 100644
index 0000000..a592e38
--- /dev/null
+++ b/app/src/main/res/layout/settings_root_activity.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/switch_bar.xml b/app/src/main/res/layout/switch_bar.xml
new file mode 100644
index 0000000..5907f4e
--- /dev/null
+++ b/app/src/main/res/layout/switch_bar.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-hdpi/ic_nlp_app.png b/app/src/main/res/mipmap-hdpi/ic_nlp_app.png
new file mode 100644
index 0000000..86a29e7
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_nlp_app.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_nlp_settings.png b/app/src/main/res/mipmap-hdpi/ic_nlp_settings.png
new file mode 100644
index 0000000..c08d3f6
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_nlp_settings.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_nlp_app.png b/app/src/main/res/mipmap-mdpi/ic_nlp_app.png
new file mode 100644
index 0000000..a7ae3c4
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_nlp_app.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_nlp_settings.png b/app/src/main/res/mipmap-mdpi/ic_nlp_settings.png
new file mode 100644
index 0000000..6d3adc6
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_nlp_settings.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_nlp_app.png b/app/src/main/res/mipmap-xhdpi/ic_nlp_app.png
new file mode 100644
index 0000000..cd30228
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_nlp_app.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_nlp_settings.png b/app/src/main/res/mipmap-xhdpi/ic_nlp_settings.png
new file mode 100644
index 0000000..35034cd
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_nlp_settings.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_nlp_app.png b/app/src/main/res/mipmap-xxhdpi/ic_nlp_app.png
new file mode 100644
index 0000000..7296a1c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_nlp_app.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_nlp_settings.png b/app/src/main/res/mipmap-xxhdpi/ic_nlp_settings.png
new file mode 100644
index 0000000..7fcda52
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_nlp_settings.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_nlp_app.png b/app/src/main/res/mipmap-xxxhdpi/ic_nlp_app.png
new file mode 100644
index 0000000..57c386e
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_nlp_app.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_nlp_settings.png b/app/src/main/res/mipmap-xxxhdpi/ic_nlp_settings.png
new file mode 100644
index 0000000..b2ab77d
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_nlp_settings.png differ
diff --git a/app/src/main/res/navigation/nav_settings.xml b/app/src/main/res/navigation/nav_settings.xml
new file mode 100644
index 0000000..e318a3b
--- /dev/null
+++ b/app/src/main/res/navigation/nav_settings.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..1a92a94
--- /dev/null
+++ b/app/src/main/res/values-pl/strings.xml
@@ -0,0 +1,20 @@
+
+
+
+ UnifiedNlp
+ Ustawienia UnifiedNlp
+
diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..07fd16b
--- /dev/null
+++ b/app/src/main/res/values-ro/strings.xml
@@ -0,0 +1,20 @@
+
+
+
+ Nlp Unificat
+ Setări Nlp Unificat
+
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..9d7daab
--- /dev/null
+++ b/app/src/main/res/values-sr/strings.xml
@@ -0,0 +1,42 @@
+
+
+
+ Обједињени МПЛ
+ Поставке Обједињеног МПЛ
+
+ Подршка за Мрежне Провајдере Локације
+ Издање Андроида подржано:
+ Ваше издање Андроида није службено подржано. Ово нужно не мора да значи ништа.
+ Систем подржава провајдере локације:
+ Ваш систем не подржава овај пакет Обједињеног МПЛ. Или инсталирајте одговарајући пакет или Xposed модул за компатибилност.
+
+ Стање Обједињеног МПЛ-а
+ Обједињени МПЛ је регистрован у систему:
+ Систем се није повезао са услугом Обједињеног МПЛ-а. Ако сте инсталирали Обједињени МПЛ покушајте са рестартом уређаја.
+ Обједињени МПЛ има познату локацију:
+ Обједињени МПЛ нема последњу познату локацију. Неке апликације ће краховати због овога.
+ Обједињени МПЛ пружа освежавања локације:
+ Систем није пружио ниједну локацију Обједињеног МПЛ-а у последњих 10 секунди.
+ Мрежна локација укључена:
+ Или сте искључили мрежну локацију (у поставкама система) или систем није подржан.
+ Дозвола за локацију још није одобрена
+
+ О програму
+ О микроГ Обједињеном МПЛ-у
+ Подаци о издању и коришћеним библиотекама
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 0000000..738c356
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,19 @@
+
+
+
+ #ff37474f
+
diff --git a/app/src/main/res/values/plurals.xml b/app/src/main/res/values/plurals.xml
new file mode 100644
index 0000000..654e922
--- /dev/null
+++ b/app/src/main/res/values/plurals.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+ - %1$d backend configured
+ - %1$d backends configured
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..9d098a6
--- /dev/null
+++ b/app/src/main/res/values/strings.xml
@@ -0,0 +1,61 @@
+
+
+
+ UnifiedNlp
+ UnifiedNlp Settings
+
+
+
+ Self-Check
+ Check if the system is correctly set up to use microG.
+
+ Network location provider support
+ Android version supported:
+ Your Android version is not officially supported. This does not necessarily mean anything.
+ System supports location provider:
+ Your system does not support this UnifiedNlp package. Either install a matching package or a compatibility Xposed module.
+
+ UnifiedNlp status
+ UnifiedNlp is registered in system:
+ The system did not bind the UnifiedNlp service. If you just installed UnifiedNlp you should try to reboot this device.
+ UnifiedNlp has known location:
+ UnifiedNlp has no last known location. This will cause some apps to fail.
+ UnifiedNlp provides location updates:
+ No UnifiedNlp location was provided by the system within 10 seconds.
+ Network-based location enabled:
+ You either disabled network-based location (in system settings) or the system is not supported.
+ Location permission not yet granted
+
+ Permissions granted
+ Permission to %1$s:
+ Touch here to grant permission. Not granting the permission can result in misbehaving applications.
+
+
+ Version %1$s
+ %1$s %2$s
+ All rights reserved.
+
+ UnifiedNlp UI Demo
+ Summary
+ Version v0.1.0
+ Included libraries
+
+ About
+ About microG UnifiedNlp
+ Version information and used libraries
+
+
diff --git a/app/src/main/res/xml/preferences_start.xml b/app/src/main/res/xml/preferences_start.xml
new file mode 100644
index 0000000..67e4765
--- /dev/null
+++ b/app/src/main/res/xml/preferences_start.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt b/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt
index 141a705..41e5305 100644
--- a/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt
+++ b/client/src/main/kotlin/org/microg/nlp/client/UnifiedLocationClient.kt
@@ -501,6 +501,8 @@ class UnifiedLocationClient private constructor(context: Context) {
} catch (e: Exception) {
Log.w(TAG, "Failed to register location callback", e)
}
+ context.get()?.resources?.getStringArray(R.array.force_location_backends)?.let { setLocationBackends(it + getLocationBackends()) }
+ context.get()?.resources?.getStringArray(R.array.force_geocoder_backends)?.let { setGeocoderBackends(it + getGeocoderBackends()) }
updateServiceInterval()
if (continuations.size > 0) {
Log.d(TAG, "Resuming ${continuations.size} continuations")
diff --git a/client/src/main/res/values/arrays.xml b/client/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..d71add4
--- /dev/null
+++ b/client/src/main/res/values/arrays.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
index 9581e9d..65a2052 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -16,6 +16,6 @@ include ':ui'
//include ':service-app'
//include ':client-app'
-//include ':app'
+include ':app'
include ':docs:backend-sample'