Add back standalone app

master
Erfan Abdi 2 years ago committed by Peter Cai
parent 876181e0d8
commit 1c678b4908

@ -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
}
}

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.microg.nlp.app">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_nlp_app"
android:label="@string/nlp_app_name"
android:theme="@style/Theme.AppCompat.DayNight">
<activity
android:name="org.microg.nlp.ui.BackendSettingsActivity"
android:process=":ui" />
<activity
android:name="org.microg.nlp.app.SettingsActivity"
android:icon="@mipmap/ic_nlp_settings"
android:label="@string/nlp_app_name"
android:process=":ui">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="org.microg.nlp.app.SelfCheckFragment$AsActivity"
android:label="@string/self_check_title"
android:process=":ui" />
<activity
android:name="org.microg.nlp.app.AboutFragment$AsActivity"
android:label="@string/pref_about_title"
android:process=":ui" />
</application>
</manifest>

@ -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<AbstractAboutFragment.Library> 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();
}
}
}

@ -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 {
}

@ -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<SelfCheckGroup> 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();
}
}
}

@ -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();
}
}

@ -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;
}
}
}

@ -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));
}
}
}

@ -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);
}
}
}

@ -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
}
}

@ -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<Library> 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<Library> libraries = new ArrayList<Library>();
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<Library> {
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<Library> {
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));
}
}
}

@ -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<SelfCheckGroup> checks);
protected void reset(LayoutInflater inflater) {
List<SelfCheckGroup> selfCheckGroupList = new ArrayList<SelfCheckGroup>();
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);
}
});
}
}
}

@ -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;
}
}

@ -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);
}
}
}

@ -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;
}
}
}

@ -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);
}
}
}

@ -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<OnSwitchChangeListener> mSwitchChangeListeners =
new ArrayList<OnSwitchChangeListener>();
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)) {