Repo Created

This commit is contained in:
Fr4nz D13trich 2025-11-15 17:44:12 +01:00
parent eb305e2886
commit a8c22c65db
4784 changed files with 329907 additions and 2 deletions

View file

@ -0,0 +1,224 @@
/*
* SPDX-FileCopyrightText: 2013 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
configurations {
mapboxRuntimeOnly
vtmRuntimeOnly
if (hasModule("hms", true)) hmsRuntimeOnly
defaultRuntimeOnly
}
dependencies {
implementation "com.squareup.wire:wire-runtime:$wireVersion"
implementation "de.hdodenhof:circleimageview:1.3.0"
implementation project(':fake-signature')
implementation project(':firebase-dynamic-links')
implementation project(':firebase-auth-core')
implementation project(':play-services-ads-core')
implementation project(':play-services-ads-identifier-core')
implementation project(':play-services-ads-lite-core')
implementation project(':play-services-appinvite-core')
implementation project(':play-services-appset-core')
implementation project(':play-services-auth-api-phone-core')
implementation project(':play-services-auth-blockstore-core')
implementation project(':play-services-auth-workaccount-core')
implementation project(':play-services-base-core')
implementation project(':play-services-cast-core')
implementation project(':play-services-cast-framework-core')
implementation project(':play-services-conscrypt-provider-core')
implementation project(':play-services-cronet-core')
implementation project(':play-services-droidguard-core')
implementation project(':play-services-fido-core')
implementation project(':play-services-fitness-core')
implementation project(':play-services-gmscompliance-core')
implementation project(':play-services-location-core')
implementation project(':play-services-location-core-base')
implementation project(':play-services-oss-licenses-core')
implementation project(':play-services-panorama-core')
implementation project(':play-services-pay-core')
implementation project(':play-services-recaptcha-core')
implementation project(':play-services-safetynet-core')
implementation project(':play-services-tapandpay-core')
implementation project(':play-services-threadnetwork-core')
implementation project(':play-services-vision-core')
implementation project(':play-services-wearable-core')
implementation project(':play-services-core-proto')
implementation project(':play-services-core:microg-ui-tools') // deprecated
implementation project(':play-services-base-core-package')
implementation project(':play-services-api')
implementation project(':play-services-appinvite')
implementation project(':play-services-auth-base')
implementation project(':play-services-auth')
implementation project(':play-services-clearcut')
implementation project(':play-services-drive')
implementation project(':play-services-games')
implementation project(':play-services-maps')
implementation project(':play-services-measurement-base')
implementation project(':play-services-places')
implementation project(':play-services-recaptcha')
implementation project(':play-services-safetynet')
implementation project(':play-services-tasks-ktx')
implementation project(':play-services-fitness')
mapboxRuntimeOnly project(':play-services-maps-core-mapbox')
vtmRuntimeOnly project(':play-services-maps-core-vtm')
defaultRuntimeOnly project(':play-services-location-core-provider')
if (hasModule("nearby", true)) runtimeOnly project(':play-services-nearby-core-package')
if (hasModule("hms", false)) hmsRuntimeOnly project(':play-services-maps-core-hms')
// AndroidX UI
implementation "androidx.multidex:multidex:$multidexVersion"
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.mediarouter:mediarouter:$mediarouterVersion"
implementation "androidx.preference:preference-ktx:$preferenceVersion"
implementation "androidx.webkit:webkit:$webkitVersion"
// Material Components
implementation "com.google.android.material:material:$materialVersion"
// Compose
def composeBom = platform('androidx.compose:compose-bom:2024.04.00')
implementation composeBom
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.ui:ui-tooling-preview'
debugImplementation 'androidx.compose.ui:ui-tooling'
implementation 'androidx.activity:activity-compose:1.8.2'
// Navigation
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
implementation "com.android.volley:volley:$volleyVersion"
implementation "androidx.lifecycle:lifecycle-service:$lifecycleVersion"
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion"
}
android {
namespace "com.google.android.gms"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
buildFeatures {
buildConfig = true
dataBinding = true
compose true
}
defaultConfig {
versionName version
versionCode appVersionCode
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
multiDexEnabled true
multiDexKeepProguard file('multidex-keep.pro')
manifestPlaceholders = [appLabel:"@string/gms_app_name"]
resValue "string", "package_id", "com.google.android.gms"
buildConfigField "String", "SAFETYNET_KEY", "\"${localProperties.get("safetynet.key", "")}\""
buildConfigField "String", "RECAPTCHA_SITE_KEY", "\"${localProperties.get("recaptcha.siteKey", "")}\""
buildConfigField "String", "RECAPTCHA_SECRET", "\"${localProperties.get("recaptcha.secret", "")}\""
buildConfigField "String", "RECAPTCHA_ENTERPRISE_PROJECT_ID", "\"${localProperties.get("recaptchaEnterpreise.projectId", "")}\""
buildConfigField "String", "RECAPTCHA_ENTERPRISE_SITE_KEY", "\"${localProperties.get("recaptchaEnterpreise.siteKey", "")}\""
buildConfigField "String", "RECAPTCHA_ENTERPRISE_API_KEY", "\"${localProperties.get("recaptchaEnterpreise.apiKey", "")}\""
ndk {
abiFilters "armeabi-v7a", "arm64-v8a", "x86", "x86_64"
}
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.10"
}
sourceSets {
main {
java.srcDirs += 'src/main/kotlin'
}
}
lintOptions {
disable 'MissingTranslation', 'GetLocales', 'InvalidPackage', 'BatteryLife', 'ImpliedQuantity', 'MissingQuantity', 'InvalidWakeLockTag', 'UniquePermission'
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
flavorDimensions = ['maps', 'target']
productFlavors {
"default" {
dimension 'target'
}
"huawei" {
dimension 'target'
versionNameSuffix "-hw"
}
"huaweilh" {
dimension 'target'
versionNameSuffix "-lh"
versionCode appVersionCode - 1000
matchingFallbacks = ['huawei']
}
"user" {
dimension 'target'
applicationId = "org.microg.gms"
versionNameSuffix "-user"
manifestPlaceholders = [appLabel:"@string/limited_services_app_name"]
matchingFallbacks = ['default']
resValue "string", "package_id", "org.microg.gms"
}
"hms" {
dimension 'maps'
}
"mapbox" {
dimension 'maps'
}
"vtm" {
dimension 'maps'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = 1.8
}
packagingOptions {
exclude 'META-INF/ASL2.0'
jniLibs {
useLegacyPackaging true
}
}
}
if (file('user.gradle').exists()) {
apply from: 'user.gradle'
}
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
output.outputFileName = variant.applicationId + "-" + variant.versionCode + variant.versionName.substring(version.length()) + ".apk"
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.
*/
apply plugin: 'com.android.library'
android {
namespace "org.microg.tools.ui"
compileSdkVersion androidCompileSdk
buildToolsVersion "$androidBuildVersionTools"
defaultConfig {
versionName version
minSdkVersion androidMinSdk
targetSdkVersion androidTargetSdk
}
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
lintOptions {
// TODO: Remove MissingTranslation once we have stable strings and proper translations.
disable 'MissingTranslation'
}
}
dependencies {
implementation "androidx.appcompat:appcompat:$appcompatVersion"
implementation "androidx.preference:preference:$preferenceVersion"
}

View file

@ -0,0 +1,18 @@
<?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 />

View file

@ -0,0 +1,65 @@
/*
* 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.tools.selfcheck;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.util.Log;
import androidx.annotation.RequiresApi;
import org.microg.tools.ui.R;
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Negative;
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Positive;
@RequiresApi(23)
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);
}
}
}

View file

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

View file

@ -0,0 +1,136 @@
/*
* 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.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.ImageView;
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;
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);
ViewGroup list = aboutRoot.findViewById(android.R.id.list);
for (Library library : libraries) {
View v = inflater.inflate(android.R.layout.simple_list_item_2, list, false);
((TextView) v.findViewById(android.R.id.text1)).setText(getString(R.string.about_name_version_str, library.name, getLibVersion(library.packageName)));
((TextView) v.findViewById(android.R.id.text2)).setText(library.copyright != null ? library.copyright : getString(R.string.about_default_license));
list.addView(v);
}
return aboutRoot;
}
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));
}
}
}

View file

@ -0,0 +1,113 @@
package org.microg.tools.ui;
import android.os.Bundle;
import android.view.ViewGroup;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import java.util.ArrayList;
import java.util.List;
public abstract class AbstractDashboardActivity extends AppCompatActivity {
protected int preferencesResource = 0;
private final List<Condition> conditions = new ArrayList<Condition>();
private ViewGroup conditionContainer;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dashboard_activity);
conditionContainer = (ViewGroup) findViewById(R.id.condition_container);
getSupportFragmentManager().beginTransaction()
.replace(R.id.content_wrapper, getFragment())
.commit();
}
@Override
protected void onResume() {
super.onResume();
forceConditionReevaluation();
}
private synchronized void resetConditionViews() {
conditionContainer.removeAllViews();
for (Condition condition : conditions) {
if (condition.isEvaluated()) {
if (condition.isActive(this)) {
addConditionToView(condition);
}
} else {
evaluateConditionAsync(condition);
}
}
}
private void evaluateConditionAsync(final Condition condition) {
if (condition.willBeEvaluating()) {
new Thread(new Runnable() {
@Override
public void run() {
if (condition.isActive(AbstractDashboardActivity.this)) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (conditions.contains(condition) && condition.isEvaluated()) {
addConditionToView(condition);
}
}
});
}
}
}).start();
}
}
protected void forceConditionReevaluation() {
for (Condition condition : conditions) {
condition.resetEvaluated();
}
resetConditionViews();
}
protected void addAllConditions(Condition[] conditions) {
for (Condition condition : conditions) {
addCondition(condition);
}
}
protected void addCondition(Condition condition) {
conditions.add(condition);
if (conditionContainer == null) return;
if (condition.isEvaluated()) {
addConditionToView(condition);
} else {
evaluateConditionAsync(condition);
}
}
private synchronized void addConditionToView(Condition condition) {
for (int i = 0; i < conditionContainer.getChildCount(); i++) {
if (conditionContainer.getChildAt(i).getTag() == condition) return;
}
conditionContainer.addView(condition.createView(this, conditionContainer));
}
protected void clearConditions() {
conditions.clear();
resetConditionViews();
}
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;
}
}

View file

@ -0,0 +1,128 @@
/*
* 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.tools.ui;
import android.content.Context;
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.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.tools.selfcheck.SelfCheckGroup.Result.Negative;
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Positive;
import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Unknown;
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(Context context, List<SelfCheckGroup> checks);
protected void reset(LayoutInflater inflater) {
List<SelfCheckGroup> selfCheckGroupList = new ArrayList<SelfCheckGroup>();
prepareSelfCheckList(getContext(), 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(v ->
resolver.tryResolve(AbstractSelfCheckFragment.this)
);
} else {
resultEntry.findViewById(R.id.self_check_result).setEnabled(false);
}
}
viewGroup.addView(resultEntry);
}
});
}
}
}

View file

@ -0,0 +1,80 @@
package org.microg.tools.ui;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.ViewGroup;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
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);
setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
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;
}
}

View file

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

View file

@ -0,0 +1,338 @@
/*
* 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.tools.ui;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.DrawableRes;
import androidx.annotation.PluralsRes;
import androidx.annotation.StringRes;
import androidx.core.content.res.ResourcesCompat;
public class Condition {
@DrawableRes
private final int iconRes;
private final Drawable icon;
@StringRes
private final int titleRes;
@PluralsRes
private final int titlePluralsRes;
private final CharSequence title;
@StringRes
private final int summaryRes;
@PluralsRes
private final int summaryPluralsRes;
private final CharSequence summary;
@StringRes
private final int firstActionTextRes;
@PluralsRes
private final int firstActionPluralsRes;
private final CharSequence firstActionText;
private final View.OnClickListener firstActionListener;
@StringRes
private final int secondActionTextRes;
@PluralsRes
private final int secondActionPluralsRes;
private final CharSequence secondActionText;
private final View.OnClickListener secondActionListener;
private final Evaluation evaluation;
private boolean evaluated = false;
private boolean evaluating = false;
private int evaluatedPlurals = -1;
private boolean active;
Condition(Builder builder) {
icon = builder.icon;
title = builder.title;
summary = builder.summary;
firstActionText = builder.firstActionText;
firstActionListener = builder.firstActionListener;
secondActionText = builder.secondActionText;
secondActionListener = builder.secondActionListener;
summaryRes = builder.summaryRes;
iconRes = builder.iconRes;
firstActionTextRes = builder.firstActionTextRes;
secondActionTextRes = builder.secondActionTextRes;
titleRes = builder.titleRes;
evaluation = builder.evaluation;
titlePluralsRes = builder.titlePluralsRes;
summaryPluralsRes = builder.summaryPluralsRes;
firstActionPluralsRes = builder.firstActionPluralsRes;
secondActionPluralsRes = builder.secondActionPluralsRes;
}
View createView(final Context context, ViewGroup container) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.condition_card, container, false);
Drawable icon = getIcon(context);
if (icon != null)
((ImageView) view.findViewById(android.R.id.icon)).setImageDrawable(icon);
((TextView) view.findViewById(android.R.id.title)).setText(getTitle(context));
((TextView) view.findViewById(android.R.id.summary)).setText(getSummary(context));
Button first = (Button) view.findViewById(R.id.first_action);
first.setText(getFirstActionText(context));
first.setOnClickListener(getFirstActionListener());
CharSequence secondActionText = getSecondActionText(context);
if (secondActionText != null) {
Button second = (Button) view.findViewById(R.id.second_action);
second.setText(secondActionText);
second.setOnClickListener(getSecondActionListener());
second.setVisibility(View.VISIBLE);
}
final View detailGroup = view.findViewById(R.id.detail_group);
final ImageView expandIndicator = (ImageView) view.findViewById(R.id.expand_indicator);
View.OnClickListener expandListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
if (detailGroup.getVisibility() == View.VISIBLE) {
expandIndicator.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_expand_more, context.getTheme()));
detailGroup.setVisibility(View.GONE);
} else {
expandIndicator.setImageDrawable(ResourcesCompat.getDrawable(context.getResources(), R.drawable.ic_expand_less, context.getTheme()));
detailGroup.setVisibility(View.VISIBLE);
}
}
};
view.findViewById(R.id.collapsed_group).setOnClickListener(expandListener);
expandIndicator.setOnClickListener(expandListener);
view.setTag(this);
return view;
}
public Drawable getIcon(Context context) {
if (iconRes != 0) {
return ResourcesCompat.getDrawable(context.getResources(), iconRes, context.getTheme());
}
return icon;
}
public CharSequence getTitle(Context context) {
if (titleRes != 0) {
return context.getString(titleRes);
}
if (titlePluralsRes != 0) {
return context.getResources().getQuantityString(titlePluralsRes, evaluatedPlurals);
}
return title;
}
public CharSequence getSummary(Context context) {
if (summaryRes != 0) {
return context.getString(summaryRes);
}
if (summaryPluralsRes != 0) {
return context.getResources().getQuantityString(summaryPluralsRes, evaluatedPlurals);
}
return summary;
}
public View.OnClickListener getFirstActionListener() {
return firstActionListener;
}
public CharSequence getFirstActionText(Context context) {
if (firstActionTextRes != 0) {
return context.getString(firstActionTextRes);
}
if (firstActionPluralsRes != 0) {
return context.getResources().getQuantityString(firstActionPluralsRes, evaluatedPlurals);
}
return firstActionText;
}
public View.OnClickListener getSecondActionListener() {
return secondActionListener;
}
public CharSequence getSecondActionText(Context context) {
if (secondActionTextRes != 0) {
return context.getString(secondActionTextRes);
}
if (secondActionPluralsRes != 0) {
return context.getResources().getQuantityString(secondActionPluralsRes, evaluatedPlurals);
}
return secondActionText;
}
public synchronized boolean willBeEvaluating() {
if (!evaluating && !evaluated && evaluation != null) {
return evaluating = true;
} else {
return false;
}
}
public boolean isEvaluated() {
return evaluated || evaluation == null;
}
public synchronized void evaluate(Context context) {
active = evaluation == null || evaluation.isActive(context);
evaluatedPlurals = evaluation.getPluralsCount();
evaluated = true;
evaluating = false;
}
public boolean isActive(Context context) {
if (!evaluated && evaluation != null) evaluate(context);
return active;
}
public void resetEvaluated() {
this.evaluated = false;
}
public static abstract class Evaluation {
public abstract boolean isActive(Context context);
public int getPluralsCount() {
return 1;
}
}
public static class Builder {
@DrawableRes
private int iconRes;
private Drawable icon;
@StringRes
private int titleRes;
@PluralsRes
private int titlePluralsRes;
private CharSequence title;
@StringRes
private int summaryRes;
@PluralsRes
private int summaryPluralsRes;
private CharSequence summary;
@StringRes
private int firstActionTextRes;
@PluralsRes
private int firstActionPluralsRes;
private CharSequence firstActionText;
private View.OnClickListener firstActionListener;
@StringRes
private int secondActionTextRes;
@PluralsRes
private int secondActionPluralsRes;
private CharSequence secondActionText;
private View.OnClickListener secondActionListener;
private Evaluation evaluation;
public Builder() {
}
public Builder icon(Drawable val) {
icon = val;
return this;
}
public Builder icon(@DrawableRes int val) {
iconRes = val;
return this;
}
public Builder title(CharSequence val) {
title = val;
return this;
}
public Builder title(@StringRes int val) {
titleRes = val;
return this;
}
public Builder titlePlurals(@PluralsRes int val) {
titlePluralsRes = val;
return this;
}
public Builder summary(CharSequence val) {
summary = val;
return this;
}
public Builder summary(@StringRes int val) {
summaryRes = val;
return this;
}
public Builder summaryPlurals(@PluralsRes int val) {
summaryPluralsRes = val;
return this;
}
public Builder firstAction(CharSequence text, View.OnClickListener listener) {
firstActionText = text;
firstActionListener = listener;
return this;
}
public Builder firstAction(@StringRes int val, View.OnClickListener listener) {
firstActionTextRes = val;
firstActionListener = listener;
return this;
}
public Builder firstActionPlurals(@PluralsRes int val, View.OnClickListener listener) {
firstActionPluralsRes = val;
firstActionListener = listener;
return this;
}
public Builder secondAction(CharSequence text, View.OnClickListener listener) {
secondActionText = text;
secondActionListener = listener;
return this;
}
public Builder secondAction(@StringRes int val, View.OnClickListener listener) {
secondActionTextRes = val;
secondActionListener = listener;
return this;
}
public Builder secondActionPlurals(@PluralsRes int val, View.OnClickListener listener) {
secondActionPluralsRes = val;
secondActionListener = listener;
return this;
}
public Builder evaluation(Evaluation evaluation) {
this.evaluation = evaluation;
return this;
}
public Condition build() {
return new Condition(this);
}
}
}

View file

@ -0,0 +1,114 @@
/*
* 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.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;
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;
}
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.tools.ui;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
public class LongTextPreference extends Preference {
public LongTextPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public LongTextPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public LongTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public LongTextPreference(Context context) {
super(context);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
TextView view = (TextView) holder.findViewById(android.R.id.summary);
if (view != null) {
view.setMaxLines(Integer.MAX_VALUE);
}
}
}

View file

@ -0,0 +1,46 @@
/*
* 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.tools.ui;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.CheckBoxPreference;
public class RadioButtonPreference extends CheckBoxPreference {
public RadioButtonPreference(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public RadioButtonPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
setWidgetLayoutResource(R.layout.preference_widget_radiobutton);
}
@SuppressLint("RestrictedApi")
public RadioButtonPreference(Context context, AttributeSet attrs) {
this(context, attrs, TypedArrayUtils.getAttr(context, androidx.preference.R.attr.checkBoxPreferenceStyle,
android.R.attr.checkBoxPreferenceStyle));
}
public RadioButtonPreference(Context context) {
this(context, null);
}
}

View file

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

View file

@ -0,0 +1,264 @@
/*
* 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.tools.ui;
import android.content.Context;
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;
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 > 16) {
mTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
}
mLabel = getResources().getString(androidx.appcompat.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 >= 16) {
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 ? androidx.appcompat.R.string.abc_capital_on : androidx.appcompat.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<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
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();
}
}

View file

@ -0,0 +1,69 @@
/*
* 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.tools.ui;
import android.os.Bundle;
import androidx.appcompat.widget.SwitchCompat;
public abstract class SwitchBarResourceSettingsFragment extends ResourceSettingsFragment implements SwitchBar.OnSwitchChangeListener {
protected SwitchBar switchBar;
private SwitchCompat switchCompat;
private boolean listenerSetup = false;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// switchBar = activity.getSwitchBar();
// switchBar.show();
// switchCompat = switchBar.getSwitch();
}
@Override
public void onDestroyView() {
super.onDestroyView();
// switchBar.hide();
}
@Override
public void onResume() {
super.onResume();
if (!listenerSetup) {
// switchBar.addOnSwitchChangeListener(this);
listenerSetup = true;
}
}
@Override
public void onPause() {
if (listenerSetup) {
// switchBar.removeOnSwitchChangeListener(this);
listenerSetup = false;
}
super.onPause();
}
@Override
public void onSwitchChanged(SwitchCompat switchView, boolean isChecked) {
if (switchView == switchCompat) {
onSwitchBarChanged(isChecked);
}
}
public abstract void onSwitchBarChanged(boolean isChecked);
}

View file

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

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/ripple_material_dark">
<item android:drawable="@color/switchbar_background_color"/>
</ripple>

View file

@ -0,0 +1,18 @@
<?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.
-->
<selector/>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~ Copyright (C) 2015-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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M12.0,8.0l-6.0,6.0 1.41,1.41L12.0,10.83l4.59,4.58L18.0,14.0z"/>
</vector>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~ Copyright (C) 2015-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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#ffffff"
android:pathData="M16.59,8.59L12.0,13.17 7.41,8.59 6.0,10.0l6.0,6.0 6.0,-6.0z"/>
</vector>

View file

@ -0,0 +1,20 @@
<?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.
-->
<color xmlns:android="http://schemas.android.com/apk/res/android"
android:color="@color/switchbar_background_color"/>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider"/>
<TextView android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dip"
android:paddingBottom="8dip"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingLeft="?android:attr/listPreferredItemPaddingLeft"
android:paddingRight="?android:attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingTop="8dip"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"/>
</LinearLayout>

View file

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~ Copyright (C) 2015-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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/selectableItemBackground"
android:baselineAligned="false"
android:clipToPadding="false"
android:focusable="true"
android:gravity="center_vertical"
android:minHeight="?android:attr/listPreferredItemHeightSmall"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingStart="?android:attr/listPreferredItemPaddingStart">
<LinearLayout
android:id="@+id/icon_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="start|center_vertical"
android:minWidth="56dp"
android:orientation="horizontal"
android:paddingBottom="4dp"
android:paddingEnd="12dp"
android:paddingTop="4dp">
<androidx.appcompat.widget.AppCompatImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:maxHeight="48dp"
app:maxWidth="48dp"/>
</LinearLayout>
<RelativeLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingBottom="16dp"
android:paddingTop="16dp">
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="marquee"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceListItem"/>
<TextView
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignStart="@android:id/title"
android:layout_below="@android:id/title"
android:maxLines="10"
android:textAppearance="?android:attr/textAppearanceListItemSecondary"
android:textColor="?android:attr/textColorSecondary"/>
</RelativeLayout>
<!-- Preference should place its actual preference widget here. -->
<LinearLayout
android:id="@android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="end|center_vertical"
android:orientation="vertical"
android:paddingStart="16dp"/>
</LinearLayout>

View file

@ -0,0 +1,76 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:padding="10dp"
android:src="@android:drawable/ic_dialog_alert"/>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="@string/about_root_title"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
android:textColor="?attr/colorAccent"/>
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:visibility="gone"
android:text="@string/about_root_summary"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textColor="?attr/colorAccent"/>
<TextView
android:id="@+id/about_version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="@string/about_root_version"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/>
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingTop="16dip"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:text="@string/about_root_libraries"
android:textColor="?attr/colorAccent"/>
<LinearLayout
android:orientation="vertical"
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>

View file

@ -0,0 +1,50 @@
<!--
~ 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.
-->
<RelativeLayout android:id="@+id/app_bar"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@drawable/switchbar_background"
android:clickable="true"
android:gravity="center_vertical">
<ImageView
android:id="@+id/app_icon"
android:layout_width="72dp"
android:layout_height="40dp"
android:layout_centerVertical="true"
android:gravity="end"/>
<TextView
android:id="@+id/app_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_alignWithParentIfMissing="true"
android:layout_centerVertical="true"
android:layout_marginEnd="16dp"
android:layout_marginLeft="72dp"
android:layout_marginRight="16dp"
android:layout_marginStart="72dp"
android:gravity="start"
android:textAlignment="viewStart"
android:textColor="?android:attr/textColorPrimaryInverse"/>
</RelativeLayout>

View file

@ -0,0 +1,137 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2015 The Android Open Source Project
~ Copyright (C) 2015-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.
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="0.25dp"
android:clipChildren="false"
android:clipToPadding="false">
<LinearLayout
android:id="@+id/content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorAccent"
android:clickable="true"
android:elevation="2dp"
android:focusable="true"
android:orientation="vertical"
android:paddingStart="16dp"
android:paddingLeft="16dp">
<LinearLayout
android:id="@+id/collapsed_group"
android:layout_width="match_parent"
android:layout_height="56dp"
android:gravity="center"
android:orientation="horizontal">
<ImageView
android:id="@android:id/icon"
android:layout_width="24dp"
android:layout_height="wrap_content"
android:layout_marginEnd="36dp"
android:layout_marginRight="36dp"
android:src="@android:drawable/ic_dialog_alert"
app:tint="?android:attr/textColorPrimaryInverse" />
<TextView
android:id="@android:id/title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="Test Condition"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimaryInverse" />
<ImageView
android:id="@+id/expand_indicator"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:padding="16dp"
android:src="@drawable/ic_expand_more"
app:tint="?android:attr/textColorPrimaryInverse" />
</LinearLayout>
<LinearLayout
android:id="@+id/detail_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="60dp"
android:paddingLeft="60dp"
android:visibility="gone">
<TextView
android:id="@android:id/summary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:alpha=".7"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingBottom="16dp"
android:text="This condition just exists for testing. This is a summary describing it."
android:textAppearance="?attr/textAppearanceListItemSmall"
android:textColor="?android:attr/textColorPrimaryInverse" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="0.25dp"
android:background="@android:color/white" />
<androidx.appcompat.widget.ButtonBarLayout
android:id="@+id/buttonBar"
style="?attr/buttonBarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingBottom="8dp">
<Button
android:id="@+id/first_action"
style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:paddingStart="0dp"
android:paddingLeft="0dp"
android:text="Fix it!"
android:textColor="?android:attr/textColorPrimaryInverse" />
<Button
android:id="@+id/second_action"
style="?attr/buttonBarButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0.8"
android:textColor="?android:attr/textColorPrimaryInverse"
android:visibility="gone" />
</androidx.appcompat.widget.ButtonBarLayout>
</LinearLayout>
</LinearLayout>
</FrameLayout>

View file

@ -0,0 +1,35 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
android:id="@+id/condition_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
<FrameLayout
android:id="@+id/content_wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="4dp"
android:layout_weight="1"/>
</LinearLayout>

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<RadioButton android:id="@android:id/checkbox"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false"
android:focusable="false"/>

View file

@ -0,0 +1,30 @@
<?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.
-->
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:id="@+id/self_check_root"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
</LinearLayout>
</ScrollView>

View file

@ -0,0 +1,58 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:gravity="center_vertical"
android:orientation="horizontal"
android:paddingBottom="5dp"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingStart="?android:attr/listPreferredItemPaddingStart">
<LinearLayout
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/self_check_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem"
android:textColor="?android:textColorPrimary"/>
<TextView
android:id="@+id/self_check_resolution"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
android:textColor="?android:textColorSecondary"/>
</LinearLayout>
<CheckBox
android:id="@+id/self_check_result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
android:gravity="right|center_vertical"
android:paddingTop="5dp"/>
</LinearLayout>

View file

@ -0,0 +1,40 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dip"
android:paddingLeft="?attr/listPreferredItemPaddingLeft"
android:paddingRight="?attr/listPreferredItemPaddingRight"
android:paddingTop="16dip"
android:textAppearance="@style/TextAppearance.AppCompat.Body2"
android:textColor="?attr/colorAccent"/>
<LinearLayout
android:id="@+id/group_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"/>
</LinearLayout>

View file

@ -0,0 +1,47 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme" />
<org.microg.tools.ui.SwitchBar
android:id="@+id/switch_bar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/switchbar_background_color"
android:visibility="gone"/>
<FrameLayout
android:id="@+id/custom_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<FrameLayout
android:id="@+id/content_wrapper"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"/>
</LinearLayout>

View file

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<merge xmlns:android="http://schemas.android.com/apk/res/android">
<TextView
android:id="@+id/switch_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginLeft="72dp"
android:layout_marginStart="72dp"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/v7_preference_on"
android:textAppearance="@style/TextAppearance.AppCompat.Title.Inverse"/>
<org.microg.tools.ui.ToggleSwitch
android:id="@+id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:background="@null"/>
</merge>

View file

@ -0,0 +1,24 @@
<!--
~ 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.
-->
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2020 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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Версія %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Ўсе правы абаронены.</string>
<string name="prefcat_setup">Ўсталяваць</string>
<string name="self_check_title">Працаздольнасць</string>
<string name="self_check_desc">Праверце, ці правільна настроена сістэма для выкарыстання microG.</string>
<string name="self_check_cat_permissions">Правы доступу прадастаўленыя</string>
<string name="self_check_name_permission">%1$s:</string>
<string name="self_check_resolution_permission">Краніце, каб даць дазвол. Адсутны дазвол можа прывесці да некарэктнай працы прыкладання.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Падрабязнасці</string>
<string name="about_root_version">Версія v0.1.0</string>
<string name="about_root_libraries">Выкарыстоўваемыя бібліятэкі</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Verze %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Všechna práva vyhrazena.</string>
<string name="prefcat_setup">Nastavení</string>
<string name="self_check_title">Vlastní kontrola</string>
<string name="self_check_desc">Zkontrolovat, zda je systém správně nastaven pro používání microG.</string>
<string name="self_check_cat_permissions">Udělená oprávnění</string>
<string name="self_check_name_permission">Oprávnění „%1$s“:</string>
<string name="self_check_resolution_permission">Klepěte sem pro udělení oprávnění. Neudělení oprávnění může mít za výsledek nesprávně fungující aplikace.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Souhrn</string>
<string name="about_root_version">Verze v0.1.0</string>
<string name="about_root_libraries">Zahrnuté knihovny</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Version %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Alle Rechte vorbehalten.</string>
<string name="prefcat_setup">Einrichtung</string>
<string name="self_check_title">Selbstprüfung</string>
<string name="self_check_desc">Prüft, ob das System zur Nutzung von microG konfiguriert ist.</string>
<string name="self_check_cat_permissions">Erteilte Berechtigungen</string>
<string name="self_check_name_permission">%1$s:</string>
<string name="self_check_resolution_permission">Hier drücken, um die Berechtigung zu erteilen. Verweigern einer Berechtigung kann zu Fehlverhalten in anderen Anwendungen führen.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Zusammenfassung</string>
<string name="about_root_version">Version v0.1.0</string>
<string name="about_root_libraries">Genutzte Bibliotheken</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,19 @@
<?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.
-->
<resources>
</resources>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2013-2020 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.
-->
<resources>
<string name="lib_name">Herramientas de IU de microG</string>
<string name="lib_license">Apache License 2.0, Equipo de microG</string>
<string name="about_version_str">Versión %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Todos los derechos reservados.</string>
<string name="prefcat_setup">Configuración</string>
<string name="self_check_title">Autocomprobación</string>
<string name="self_check_desc">Comprueba si el sistema está correctamente configurado para usar microG.</string>
<string name="self_check_cat_permissions">Permisos concedidos</string>
<string name="self_check_name_permission">Permiso para %1$s:</string>
<string name="self_check_resolution_permission">Toque aquí para conceder el permiso. No conceder el permiso puede resultar en comportamientos incorrectos de las aplicaciones.</string>
<string name="about_root_title">Demo de IU de microG</string>
<string name="about_root_summary">Resumen</string>
<string name="about_root_version">Versión v0.1.0</string>
<string name="about_root_libraries">Librerías incluidas</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,45 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Bersyon %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Nakalaan ang lahat ng karapatan.</string>
<string name="prefcat_setup">Setup</string>
<string name="self_check_title">Sariling Pagsusuri</string>
<string name="self_check_desc">Tignan kung tamang na-set up ang system para magamit ang microG</string>
<string name="self_check_cat_permissions">Mga ibinigay na pahintulot</string>
<string name="self_check_name_permission">Pahintulot na %1$s:</string>
<string name="self_check_name_permission_interact_across_profiles">Pahintulot na mag-interact sa profile sa trabaho:</string>
<string name="self_check_resolution_permission">Pindutin dito para ibigay ang pahintulot. Ang hindi pagbigay ng pahintulot ay maaaring magresulta sa maling pag-uugali ng mga application.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Pangkalahatang ideya</string>
<string name="about_root_version">Bersyon v0.1.0</string>
<string name="about_root_libraries">Mga kasamang library</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,42 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Version %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Tous droits réservés.</string>
<string name="prefcat_setup">Configuration</string>
<string name="self_check_title">Auto-vérification.</string>
<string name="self_check_desc">Vérifier si le système est correctement configuré pour utiliser microG.</string>
<string name="self_check_cat_permissions">Autorisations accordées</string>
<string name="self_check_name_permission">Autorisation à %1$s :</string>
<string name="self_check_resolution_permission">Touchez ici pour accorder lautorisation. Des applications peuvent mal se comporter si vous ne le faites pas.</string>
<string name="about_root_version">Version v0.1.0</string>
<string name="about_root_libraries">Librairies incluses</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">Strumenti interfaccia utente di microG</string>
<string name="lib_license">Licenza Apache 2.0, team di microG</string>
<string name="about_version_str">Versione %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Tutti i diritti sono riservati.</string>
<string name="prefcat_setup">Configurazione</string>
<string name="self_check_title">Controllo dei problemi</string>
<string name="self_check_desc">Verifica se il sistema è correttamente configurato per utilizzare microG.</string>
<string name="self_check_cat_permissions">Autorizzazioni concesse</string>
<string name="self_check_name_permission">%1$s:</string>
<string name="self_check_resolution_permission">Tocca qui per concedere l\'autorizzazione. Negare l\'autorizzazione può comportare il funzionamento anomalo di altre applicazioni.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Riepilogo</string>
<string name="about_root_version">Versione v0.1.0</string>
<string name="about_root_libraries">Librerie incluse</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Licenza Apache 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">microG UIツール</string>
<string name="lib_license">Apache License 2.0, microGチーム</string>
<string name="about_version_str">バージョン %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">All rights reserved.</string>
<string name="prefcat_setup">設定</string>
<string name="self_check_title">セルフチェック</string>
<string name="self_check_desc">システムがmicroGを使用するよう正しく設定されているか確認します。</string>
<string name="self_check_cat_permissions">権限付与</string>
<string name="self_check_name_permission">%1$sの権限:</string>
<string name="self_check_resolution_permission">ここをタップして権限を付与してください。 権限を付与しないと、アプリが正しく動作しない可能性があります。</string>
<string name="about_root_title">microG UIデモ</string>
<string name="about_root_summary">概要</string>
<string name="about_root_version">バージョン v0.1.0</string>
<string name="about_root_libraries">使用されているライブラリ</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,41 @@
<?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.
-->
<resources>
<string name="lib_name">Narzędzia UI microG</string>
<string name="lib_license">Licencja Apache 2.0, Zespół microG</string>
<string name="about_version_str">Wersja %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Wszelkie prawa zastrzeżone.</string>
<string name="prefcat_setup">Ustawienia</string>
<string name="self_check_title">Samo-sprawdzenie</string>
<string name="self_check_desc">Sprawdza, czy system jest poprawnie skonfigurowany do korzystania z microG.</string>
<string name="self_check_cat_permissions">Udzielono uprawnień</string>
<string name="self_check_name_permission">Uprawnienie do %1$s:</string>
<string name="self_check_resolution_permission">Stuknij, aby udzielić uprawnienia. Nieudzielenie uprawnienia może powodować problemy z aplikacjami.</string>
<string name="about_root_title">Demo microG UI</string>
<string name="about_root_summary">Podsumowanie</string>
<string name="about_root_version">Wersja v0.1.0</string>
<string name="about_root_libraries">Użyte biblioteki</string>
<string name="about_android_support_license">Licencja Apache 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,45 @@
<?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.
-->
<resources>
<string name="lib_name">Ferramentas de UI do microG</string>
<string name="lib_license">Licença "Apache 2.0", Equipe do microG</string>
<string name="about_version_str">Versão %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Todos os direitos reservados.</string>
<string name="prefcat_setup">Configuração</string>
<string name="self_check_title">Auto-verificação</string>
<string name="self_check_desc">Verifique se o sistema está configurado corretamente para usar o microG.</string>
<string name="self_check_cat_permissions">Permissões concedidas</string>
<string name="self_check_name_permission">Permissão para %1$s:</string>
<string name="self_check_name_permission_interact_across_profiles">Permissão para interagir com perfil de trabalho:</string>
<string name="self_check_resolution_permission">Toque aqui para conceder esta permissão. Não conceder esta permissão pode resultar em aplicativos com mal comportamento.</string>
<string name="about_root_title">Demo de UI do microG</string>
<string name="about_root_summary">Resumo</string>
<string name="about_root_version">Versão v0.1.0</string>
<string name="about_root_libraries">Bibliotecas inclusas</string>
<string name="about_android_support_v4">Biblioteca de suporte v4</string>
<string name="about_android_support_v7_appcompat">Biblioteca de suporte v7 appcompat</string>
<string name="about_android_support_v7_preference">Biblioteca de suporte v7 preferences</string>
<string name="about_android_support_license">Licença "Apache 2.0", Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,39 @@
<?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.
-->
<resources>
<string name="lib_name">Instrumente microG UI</string>
<string name="lib_license">Apache License 2.0, Echipa microG</string>
<string name="about_version_str">Versiune %1$s</string>
<string name="about_default_license">Toate drepturile rezervate.</string>
<string name="prefcat_setup">Configurare</string>
<string name="self_check_title">Autoverificare</string>
<string name="self_check_desc">Verifică dacă sistemul este configurat corect pentru a utiliza microG.</string>
<string name="self_check_cat_permissions">Permisiunile acordate</string>
<string name="self_check_name_permission">Permisiunea %1$s:</string>
<string name="self_check_name_permission_interact_across_profiles">Permisiunea de a interacționa cu profilul de serviciu:</string>
<string name="self_check_resolution_permission">Atinge aici pentru a acorda permisiunea. Neacordarea permisiunii poate duce la un comportament necorespunzător al aplicațiilor.</string>
<string name="about_root_title">Demo microG UI</string>
<string name="about_root_summary">Sumar</string>
<string name="about_root_version">Versiune v0.1.0</string>
<string name="about_root_libraries">Biblioteci incluse</string>
</resources>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2013-2020 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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Версия %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Все права защищены.</string>
<string name="prefcat_setup">Установить</string>
<string name="self_check_title">Работоспособность</string>
<string name="self_check_desc">Проверьте, правильно ли настроена система для использования microG.</string>
<string name="self_check_cat_permissions">Права доступа предоставлены</string>
<!-- it's better if the next line stays like this -->
<string name="self_check_name_permission">%1$s:</string>
<string name="self_check_resolution_permission">Коснитесь, чтобы предоставить разрешение. Отсутствующее разрешение может привести к некорректной работе приложения.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Подробности</string>
<string name="about_root_version">Версия v0.1.0</string>
<string name="about_root_libraries">Используемые библиотеки</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,28 @@
<?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.
-->
<resources>
<string name="prefcat_setup">Поставка</string>
<string name="self_check_title">микроГ самопровера</string>
<string name="self_check_desc">Провера исправности подешавања система за коришћење микроГ услуга.</string>
<string name="self_check_cat_permissions">Дозволе одобрене</string>
<string name="self_check_name_permission">Дозволе за %1$s:</string>
<string name="self_check_resolution_permission">Тапните овде да одобрите дозволе. Не одобравање дозвола може да резултира чудним понашањем апликација.</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Araçları</string>
<string name="lib_license">Apache Lisansı 2.0, microG Ekibi</string>
<string name="about_version_str">Sürüm %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Tüm hakları saklıdır.</string>
<string name="prefcat_setup">Kurulum</string>
<string name="self_check_title">Çalışma durumu</string>
<string name="self_check_desc">microG\'yi kullanmak için sistemin doğru şekilde ayarlanıp ayarlanmadığını kontrol edin.</string>
<string name="self_check_cat_permissions">Verilen izinler</string>
<string name="self_check_name_permission">%1$s izni:</string>
<string name="self_check_resolution_permission">Buraya dokunarak izin verin. İznin verilmemesi uygulamaların hatalı davranmasına neden olabilir.</string>
<string name="about_root_title">microG UI Demosu</string>
<string name="about_root_summary">Özet</string>
<string name="about_root_version">Sürüm v0.1.0</string>
<string name="about_root_libraries">Dahil olan kütüphaneler</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache Lisansı 2.0, Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Версія %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Всі права захищено.</string>
<string name="prefcat_setup">Налаштування</string>
<string name="self_check_title">Само-тестування</string>
<string name="self_check_desc">Перевірка, чи належним чином система використовує microG.</string>
<string name="self_check_cat_permissions">Доступ надано</string>
<string name="self_check_name_permission">Доступ до %1$s:</string>
<string name="self_check_resolution_permission">Торкніться, аби надати доступ. Без доступу не гарантується робота додатку належним чином.</string>
<string name="about_root_title">Демонстрація microG UI</string>
<string name="about_root_summary">Резюме</string>
<string name="about_root_version">Версія v0.1.0</string>
<string name="about_root_libraries">Використані бібліотеки</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="lib_name">Công cụ giao diện microG</string>
<string name="lib_license">Giấy phép Apache 2.0, nhóm microG</string>
<string name="about_version_str">Phiên bản %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">Đã đăng ký bản quyền.</string>
<string name="prefcat_setup">Thiết lập</string>
<string name="self_check_title">Tự kiểm tra</string>
<string name="self_check_desc">Kiểm tra xem hệ thống của bạn đã sẵn sàng để dùng microG chưa.</string>
<string name="self_check_cat_permissions">Cấp quyền</string>
<string name="self_check_name_permission">Quyền %1$s:</string>
<string name="self_check_name_permission_interact_across_profiles">Quyền tương tác với hồ sơ công việc:</string>
<string name="self_check_resolution_permission">Chạm vào đây để cấp quyền. Nếu không, một số ứng dụng có thể chạy không ổn định.</string>
<string name="about_root_title">Demo giao diện microG</string>
<string name="about_root_summary">Giới thiệu</string>
<string name="about_root_version">Phiên bản v0.1.0</string>
<string name="about_root_libraries">Thư viện đi kèm</string>
<string name="about_android_support_v4">Thư viện hỗ trợ v4</string>
<string name="about_android_support_v7_appcompat">Thư viện hỗ trợ v7 appcompat</string>
<string name="about_android_support_v7_preference">Thư viện hỗ trợ v7 preference</string>
<string name="about_android_support_license">Giấy phép Apache 2.0, Dự án mã nguồn mở Android</string>
</resources>

View file

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="about_version_str">版本 %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">保留所有权利。</string>
<string name="prefcat_setup">设置</string>
<string name="self_check_title">自我检查</string>
<string name="self_check_desc">确认系统是否已正确配置成 microG 可正常使用的状态</string>
<string name="self_check_cat_permissions">已授予权限</string>
<string name="self_check_name_permission">%1$s的权限</string>
<string name="self_check_name_permission_interact_across_profiles">与工作配置文件交互的权限:</string>
<string name="self_check_resolution_permission">点击授予权限。不授予权限可能导致应用工作异常。</string>
<string name="about_root_libraries">包含的库</string>
<string name="about_root_summary">概要</string>
</resources>

View file

@ -0,0 +1,44 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG 團隊</string>
<string name="about_version_str">版本 %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">保留所有權利。</string>
<string name="prefcat_setup">設定</string>
<string name="self_check_title">自我檢查</string>
<string name="self_check_desc">確認系統是否已正確配置以使用 microG。</string>
<string name="self_check_cat_permissions">允許權限</string>
<string name="self_check_name_permission">允許 %1$s 的權限:</string>
<string name="self_check_resolution_permission">點擊這裡以授權。未授權可能影響程式正常運作。</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">大綱</string>
<string name="about_root_version">版本 v0.1.0</string>
<string name="about_root_libraries">包含的函式庫</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,24 @@
<?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.
-->
<resources>
<color name="settings_theme_primary">#ff263238</color>
<color name="settings_theme_primary_dark">#ff21272b</color>
<color name="settings_theme_accent">#ff009688</color>
<color name="switchbar_background_color">#ff37474f</color>
<color name="switch_accent_color">#ff7fcac3</color>
</resources>

View file

@ -0,0 +1,45 @@
<?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.
-->
<resources>
<string name="lib_name">microG UI Tools</string>
<string name="lib_license">Apache License 2.0, microG Team</string>
<string name="about_version_str">Version %1$s</string>
<string name="about_name_version_str">%1$s %2$s</string>
<string name="about_default_license">All rights reserved.</string>
<string name="prefcat_setup">Setup</string>
<string name="self_check_title">Self-Check</string>
<string name="self_check_desc">Check if the system is correctly set up to use microG.</string>
<string name="self_check_cat_permissions">Permissions granted</string>
<string name="self_check_name_permission">Permission to %1$s:</string>
<string name="self_check_name_permission_interact_across_profiles">Permission to interact with work profile:</string>
<string name="self_check_resolution_permission">Touch here to grant permission. Not granting the permission can result in misbehaving applications.</string>
<string name="about_root_title">microG UI Demo</string>
<string name="about_root_summary">Summary</string>
<string name="about_root_version">Version v0.1.0</string>
<string name="about_root_libraries">Included libraries</string>
<string name="about_android_support_v4">v4 Support Library</string>
<string name="about_android_support_v7_appcompat">v7 appcompat Support Library</string>
<string name="about_android_support_v7_preference">v7 preference Support Library</string>
<string name="about_android_support_license">Apache License 2.0, The Android Open Source Project</string>
</resources>

View file

@ -0,0 +1,12 @@
# Make sure maps is in the primary dex file
-keep class com.google.android.gms.maps.** { *; }
-keep class org.microg.gms.maps.** { *; }
-keep class com.mapbox.** { *; }
-keep class org.oscim.** { *; }
# Keep Dynamite Loader in the primary dex file otherwise it will error out on legacy Android versions
-keep class com.google.android.gms.chimera.container.DynamiteLoaderImpl { *; }
# Keep Conscrypt in the primary dex file otherwise it will error out on legacy Android versions
-keep class com.google.android.gms.common.security.ProviderInstallerImpl { *; }
-keep class com.google.android.gms.org.conscrypt.** { *; }

1
play-services-core/proguard-rules.pro vendored Symbolic link
View file

@ -0,0 +1 @@
../proguard.flags

View file

@ -0,0 +1,62 @@
<!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<meta-data
android:name="org.microg.gms.settings.checkin_enable_service"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.gcm_enable_mcs_service"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.auth_manager_visible"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.auth_include_android_id"
android:value="false" />
<meta-data
android:name="org.microg.gms.settings.auth_strip_device_name"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.auth_two_step_verification"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.droidguard_enabled"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.safetynet_enabled"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_billing"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_licensing_purchase_free_apps"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_licensing"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_asset_delivery"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_device_sync"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_split_install"
android:value="false" />
<meta-data
android:name="org.microg.gms.settings.game_allow_create_player"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.allow_upload_game_played"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_apps_install"
android:value="true" />
</application>
</manifest>

View file

@ -0,0 +1,62 @@
<!--
~ SPDX-FileCopyrightText: 2023 microG Project Team
~ SPDX-License-Identifier: Apache-2.0
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<meta-data
android:name="org.microg.gms.settings.checkin_enable_service"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.gcm_enable_mcs_service"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.auth_manager_visible"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.auth_include_android_id"
android:value="false" />
<meta-data
android:name="org.microg.gms.settings.auth_strip_device_name"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.auth_two_step_verification"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.droidguard_enabled"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.safetynet_enabled"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_billing"
android:value="true" />
<meta-data
android:name="org.microg.gms.settings.vending_licensing"
android:value="true" />
<activity-alias
android:name="org.microg.gms.ui.SettingsActivity"
android:targetActivity="org.microg.gms.ui.MainSettingsActivity"
tools:node="remove" />
<activity-alias
android:name="org.lighthouse.SettingsActivity"
android:icon="@mipmap/ic_app_settings"
android:label="@string/gms_settings_name"
android:roundIcon="@mipmap/ic_app_settings"
android:process=":ui"
android:targetActivity="org.microg.gms.ui.MainSettingsActivity"
android:taskAffinity="org.microg.gms.settings"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity-alias>
</application>
</manifest>

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
/*
* 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 com.google.android.gms.analytics.service;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class AnalyticsService extends Service {
private static final String TAG = "GmsAnalyticsSvc";
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: " + intent);
return null;
}
}

View file

@ -0,0 +1,30 @@
/*
* 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 com.google.android.gms.auth;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import org.microg.gms.auth.AuthManagerServiceImpl;
public class GetToken extends Service {
@Override
public IBinder onBind(Intent intent) {
return new AuthManagerServiceImpl(this);
}
}

View file

@ -0,0 +1,54 @@
/*
* 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 com.google.android.gms.auth;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class TokenActivity extends Activity {
private static final String TAG = "GmsAuthTokenActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle extras = getIntent().getExtras();
extras.get("KEY");
Log.d(TAG, extras.toString());
/*AccountManager accountManager = AccountManager.get(this);
accountManager.getAuthToken(new Account(extras.getString("authAccount"), "com.google"), extras.getString("service"), extras.getBundle("callerExtras"), this, new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
if (result != null) {
result.get("KEY");
Log.d("TokenActivity", result.toString());
} else {
Log.d("TokenActivity", "null-result");
}
} catch (OperationCanceledException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (AuthenticatorException e) {
e.printStackTrace();
}
}
}, new Handler(getMainLooper()));*/
}
}

View file

@ -0,0 +1,103 @@
/**
* SPDX-FileCopyrightText: 2025 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.chimera;
import static android.os.Build.CPU_ABI;
import static android.os.Build.SUPPORTED_32_BIT_ABIS;
import static android.os.Build.SUPPORTED_64_BIT_ABIS;
import static android.os.Build.VERSION.SDK_INT;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
import android.util.Log;
import com.google.android.gms.chimera.container.DynamiteContext;
import com.google.android.gms.chimera.container.DynamiteModuleInfo;
import com.google.android.gms.chimera.container.FilteredClassLoader;
import org.microg.gms.common.Constants;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import dalvik.system.PathClassLoader;
public class DynamiteContextFactory {
private static final String TAG = "DynamiteContextFactory";
private static final Map<String, DynamiteContext> sContextCache = new WeakHashMap<>();
// WeakHashMap cannot be used, and there is a high probability that it will be recycled, causing ClassLoader to be rebuilt
private static final Map<String, ClassLoader> sClassLoaderCache = new HashMap<>();
public static DynamiteContext createDynamiteContext(String moduleId, Context originalContext) {
if (originalContext == null) {
Log.w(TAG, "create <DynamiteContext> Original context is null");
return null;
}
String cacheKey = moduleId + "-" + originalContext.getPackageName();
synchronized (sContextCache) {
DynamiteContext cached = sContextCache.get(cacheKey);
if (cached != null) {
Log.d(TAG, "Using cached DynamiteContext for cacheKey: " + cacheKey);
return cached;
}
}
try {
DynamiteModuleInfo moduleInfo = new DynamiteModuleInfo(moduleId);
Context gmsContext = originalContext.createPackageContext(Constants.GMS_PACKAGE_NAME, 0);
Context originalAppContext = originalContext.getApplicationContext();
DynamiteContext dynamiteContext;
if (originalAppContext == null || originalAppContext == originalContext) {
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, null);
} else {
dynamiteContext = new DynamiteContext(moduleInfo, originalContext, gmsContext, new DynamiteContext(moduleInfo, originalAppContext, gmsContext, null));
}
moduleInfo.init(dynamiteContext);
synchronized (sContextCache) {
sContextCache.put(cacheKey, dynamiteContext);
}
Log.d(TAG, "Created and cached a new DynamiteContext for cacheKey: " + cacheKey);
return dynamiteContext;
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, e);
return null;
}
}
public static ClassLoader createClassLoader(DynamiteModuleInfo moduleInfo, Context gmsContext, Context originalContext) {
String cacheKey = moduleInfo.getModuleId() + "-" + originalContext.getPackageName();
synchronized (sClassLoaderCache) {
ClassLoader cached = sClassLoaderCache.get(cacheKey);
if (cached != null) {
Log.d(TAG, "Using cached ClassLoader for cacheKey: " + cacheKey + " cached: " + cached.hashCode());
return cached;
}
}
StringBuilder nativeLoaderDirs = new StringBuilder(gmsContext.getApplicationInfo().nativeLibraryDir);
if (SDK_INT >= 23 && Process.is64Bit()) {
for (String abi : SUPPORTED_64_BIT_ABIS) {
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
}
} else if (SDK_INT >= 21) {
for (String abi : SUPPORTED_32_BIT_ABIS) {
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(abi);
}
} else {
nativeLoaderDirs.append(File.pathSeparator).append(gmsContext.getApplicationInfo().sourceDir).append("!/lib/").append(CPU_ABI);
}
ClassLoader classLoader = new PathClassLoader(gmsContext.getApplicationInfo().sourceDir, nativeLoaderDirs.toString(), new FilteredClassLoader(originalContext.getClassLoader(), moduleInfo.getMergedClasses(), moduleInfo.getMergedPackages()));
synchronized (sClassLoaderCache) {
sClassLoaderCache.put(cacheKey, classLoader);
}
Log.d(TAG, "Created and cached a new ClassLoader for cacheKey: " + cacheKey + " ClassLoader: " + classLoader.hashCode());
return classLoader;
}
}

View file

@ -0,0 +1,25 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.chimera;
import android.annotation.SuppressLint;
import android.util.Log;
import androidx.annotation.Keep;
import android.content.Context;
@Keep
public class DynamiteModuleInitializer {
private static final String TAG = "DynamiteModule";
public static void initializeModuleV1(Context context) {
initializeModuleV2(context, "com.google.android.gms".equals(context.getPackageName()));
}
public static void initializeModuleV2(Context context, boolean withGmsPackage) {
Log.d(TAG, "initializeModuleV2 context: " + context + ", withGmsPackage: " + withGmsPackage);
}
}

View file

@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.chimera.container;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo;
import androidx.annotation.RequiresApi;
import com.google.android.gms.chimera.DynamiteContextFactory;
public class DynamiteContext extends ContextWrapper {
private static final String TAG = "DynamiteContext";
private DynamiteModuleInfo moduleInfo;
private Context originalContext;
private Context gmsContext;
private DynamiteContext appContext;
private ClassLoader classLoader;
public DynamiteContext(DynamiteModuleInfo moduleInfo, Context base, Context gmsContext, DynamiteContext appContext) {
super(base);
this.moduleInfo = moduleInfo;
this.originalContext = base;
this.gmsContext = gmsContext;
this.appContext = appContext;
}
@Override
public ClassLoader getClassLoader() {
if (classLoader == null) {
classLoader = DynamiteContextFactory.createClassLoader(moduleInfo, gmsContext, originalContext);
}
return classLoader;
}
@Override
public String getPackageName() {
return gmsContext.getPackageName();
}
@Override
public ApplicationInfo getApplicationInfo() {
return gmsContext.getApplicationInfo();
}
@Override
public Context getApplicationContext() {
return appContext;
}
@RequiresApi(24)
@Override
public Context createDeviceProtectedStorageContext() {
return new DynamiteContext(moduleInfo, originalContext.createDeviceProtectedStorageContext(), gmsContext.createDeviceProtectedStorageContext(), appContext);
}
}

View file

@ -0,0 +1,104 @@
/*
* 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 com.google.android.gms.chimera.container;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.chimera.DynamiteContextFactory;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;
import com.google.android.gms.dynamite.IDynamiteLoader;
public class DynamiteLoaderImpl extends IDynamiteLoader.Stub {
private static final String TAG = "GmsDynamiteLoaderImpl";
@Override
public IObjectWrapper createModuleContext(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException {
// We don't have crash utils, so just forward
return createModuleContextV2(wrappedContext, moduleId, minVersion);
}
@Override
public IObjectWrapper createModuleContextV2(IObjectWrapper wrappedContext, String moduleId, int minVersion) throws RemoteException {
Log.d(TAG, "createModuleContext for " + moduleId + " at version " + minVersion);
final Context originalContext = (Context) ObjectWrapper.unwrap(wrappedContext);
return ObjectWrapper.wrap(DynamiteContextFactory.createDynamiteContext(moduleId, originalContext));
}
@Override
public IObjectWrapper createModuleContextV3(IObjectWrapper wrappedContext, String moduleId, int minVersion, IObjectWrapper wrappedCursor) throws RemoteException {
throw new UnsupportedOperationException();
}
@Override
public int getIDynamiteLoaderVersion() throws RemoteException {
return 2;
}
@Override
public int getModuleVersion(IObjectWrapper wrappedContext, String moduleId) throws RemoteException {
return getModuleVersion2(wrappedContext, moduleId, true);
}
@Override
public int getModuleVersion2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException {
// We don't have crash utils, so just forward
return getModuleVersionV2(wrappedContext, moduleId, updateConfigIfRequired);
}
@Override
public int getModuleVersionV2(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired) throws RemoteException {
final Context context = (Context) ObjectWrapper.unwrap(wrappedContext);
if (context == null) {
Log.w(TAG, "Invalid client context");
return 0;
}
try {
return Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor").getDeclaredField("MODULE_VERSION").getInt(null);
} catch (Exception e) {
Log.w(TAG, "No such module known: " + moduleId);
}
if (moduleId.equals("com.google.android.gms.firebase_database")) {
Log.d(TAG, "returning temp fix module version for " + moduleId + ". Firebase Database will not be functional!");
return com.google.android.gms.dynamite.descriptors.com.google.android.gms.firebase_database.ModuleDescriptor.MODULE_VERSION;
}
if (moduleId.equals("com.google.android.gms.googlecertificates")) {
return com.google.android.gms.dynamite.descriptors.com.google.android.gms.googlecertificates.ModuleDescriptor.MODULE_VERSION;
}
if (moduleId.equals("com.google.android.gms.cast.framework.dynamite")) {
Log.d(TAG, "returning temp fix module version for " + moduleId + ". Cast API wil not be functional!");
return 1;
}
if (moduleId.equals("com.google.android.gms.maps_dynamite")) {
Log.d(TAG, "returning v1 for maps");
return 1;
}
Log.d(TAG, "unimplemented Method: getModuleVersion for " + moduleId);
return 0;
}
@Override
public IObjectWrapper getModuleVersionV3(IObjectWrapper wrappedContext, String moduleId, boolean updateConfigIfRequired, long requestStartTime) throws RemoteException {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,61 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.chimera.container;
import java.util.Collection;
import java.util.Collections;
import android.content.Context;
public class DynamiteModuleInfo {
private Class<?> descriptor;
private String moduleId;
public DynamiteModuleInfo(String moduleId) {
this.moduleId = moduleId;
try {
this.descriptor = Class.forName("com.google.android.gms.dynamite.descriptors." + moduleId + ".ModuleDescriptor");
} catch (Exception e) {
// Ignore
}
}
public String getModuleId() {
return moduleId;
}
public int getVersion() {
try {
return descriptor.getDeclaredField("MODULE_VERSION").getInt(null);
} catch (Exception e) {
return 0;
}
}
public Collection<String> getMergedPackages() {
try {
return (Collection<String>) descriptor.getDeclaredField("MERGED_PACKAGES").get(null);
} catch (Exception e) {
return Collections.emptySet();
}
}
public Collection<String> getMergedClasses() {
try {
return (Collection<String>) descriptor.getDeclaredField("MERGED_CLASSES").get(null);
} catch (Exception e) {
return Collections.emptySet();
}
}
public void init(Context dynamiteContext) {
try {
descriptor.getMethod("init", Context.class).invoke(null, dynamiteContext);
} catch (Exception e) {
// Ignore
}
}
}

View file

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2021, microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.chimera.container;
import android.util.Log;
import java.util.Collection;
import java.util.HashSet;
public class FilteredClassLoader extends ClassLoader {
private static ClassLoader rootClassLoader;
private final Collection<String> allowedClasses;
private final Collection<String> allowedPackages;
static {
rootClassLoader = ClassLoader.getSystemClassLoader();
if (rootClassLoader == null) {
rootClassLoader = FilteredClassLoader.class.getClassLoader();
while (rootClassLoader.getParent() != null) {
rootClassLoader = rootClassLoader.getParent();
}
}
}
public FilteredClassLoader(ClassLoader parent, Collection<String> allowedClasses, Collection<String> allowedPackages) {
super(parent);
this.allowedClasses = new HashSet<>(allowedClasses);
this.allowedPackages = new HashSet<>(allowedPackages);
}
private String getPackageName(String name) {
int lastIndex = name.lastIndexOf(".");
if (lastIndex <= 0) return "";
return name.substring(0, lastIndex);
}
private String getClassName(String name) {
int lastIndex = name.indexOf("$");
if (lastIndex <= 0) return name;
return name.substring(0, lastIndex);
}
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
if (name.startsWith("java.")) return rootClassLoader.loadClass(name);
if (allowedClasses.contains(name) || allowedClasses.contains(getClassName(name)))
return super.loadClass(name, resolve);
if (allowedClasses.contains("!" + name) || allowedClasses.contains("!" + getClassName(name)))
return rootClassLoader.loadClass(name);
if (allowedPackages.contains(getPackageName(name))) return super.loadClass(name, resolve);
return rootClassLoader.loadClass(name);
}
}

View file

@ -0,0 +1,33 @@
/*
* 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 com.google.android.gms.common.ui;
import android.content.Context;
import android.view.View;
import com.google.android.gms.common.internal.ISignInButtonCreator;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;
public class SignInButtonCreatorImpl extends ISignInButtonCreator.Stub {
@Override
public IObjectWrapper createSignInButton(IObjectWrapper contextWrapper, int size, int color) {
Context context = (Context) ObjectWrapper.unwrap(contextWrapper);
// TODO: real view :)
return ObjectWrapper.wrap(new View(context));
}
}

View file

@ -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 com.google.android.gms.dynamite.descriptors.com.google.android.gms.firebase_database;
public class ModuleDescriptor {
public static final String MODULE_ID = "com.google.android.gms.firebase_database";
public static final int MODULE_VERSION = 3;
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (C) 2019 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 com.google.android.gms.dynamite.descriptors.com.google.android.gms.googlecertificates;
public class ModuleDescriptor {
public static final String MODULE_ID = "com.google.android.gms.googlecertificates";
public static final int MODULE_VERSION = 1;
}

View file

@ -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 com.google.android.gms.dynamite.descriptors.com.google.android.gms.measurement.dynamite;
public class ModuleDescriptor {
public static final String MODULE_ID = "com.google.android.gms.measurement.dynamite";
public static final int MODULE_VERSION = 140;
}

View file

@ -0,0 +1,37 @@
/*
* 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 com.google.android.gms.gcm.http;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import com.google.android.gms.http.IGoogleHttpService;
public class GoogleHttpService extends Service {
@Override
public IBinder onBind(Intent intent) {
return new IGoogleHttpService.Stub() {
@Override
public Bundle checkUrl(String url) throws RemoteException {
return null; // allow
}
}.asBinder();
}
}

View file

@ -0,0 +1,40 @@
/*
* 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 com.google.android.gms.plus.plusone;
import android.content.Context;
import android.os.RemoteException;
import com.google.android.gms.dynamic.IObjectWrapper;
import com.google.android.gms.dynamic.ObjectWrapper;
import com.google.android.gms.plus.internal.IPlusOneButtonCreator;
import org.microg.gms.auth.AuthConstants;
import org.microg.gms.plus.PlusOneButtonImpl;
public class PlusOneButtonCreatorImpl extends IPlusOneButtonCreator.Stub {
@Override
public IObjectWrapper create(IObjectWrapper context, int size, int annotation, String url, int activityRequestCode) throws RemoteException {
Context ctx = (Context) ObjectWrapper.unwrap(context);
return ObjectWrapper.wrap(new PlusOneButtonImpl(ctx, size, annotation, url, AuthConstants.DEFAULT_ACCOUNT));
}
@Override
public IObjectWrapper createForAccount(IObjectWrapper context, int size, int annotation, String url, String account) throws RemoteException {
Context ctx = (Context) ObjectWrapper.unwrap(context);
return ObjectWrapper.wrap(new PlusOneButtonImpl(ctx, size, annotation, url, account));
}
}

View file

@ -0,0 +1,32 @@
/*
* 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 com.google.android.gms.recovery;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;
public class RecoveryService extends Service {
private static final String TAG = "GmsRecoverySvc";
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: " + intent);
return null;
}
}

View file

@ -0,0 +1,56 @@
/*
* SPDX-FileCopyrightText: 2024 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package com.google.android.gms.semanticlocation
import android.app.PendingIntent
import android.util.Log
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.Feature
import com.google.android.gms.common.api.internal.IStatusCallback
import com.google.android.gms.common.internal.ConnectionInfo
import com.google.android.gms.common.internal.GetServiceRequest
import com.google.android.gms.common.internal.IGmsCallbacks
import com.google.android.gms.semanticlocation.internal.ISemanticLocationService
import com.google.android.gms.semanticlocation.internal.SemanticLocationParameters
import org.microg.gms.BaseService
import org.microg.gms.common.GmsService
private const val TAG = "SemanticLocationService"
private val FEATURES = arrayOf(
Feature("semanticlocation_events", 1L),
)
class SemanticLocationService : BaseService(TAG, GmsService.SEMANTIC_LOCATION) {
override fun handleServiceRequest(callback: IGmsCallbacks, request: GetServiceRequest, service: GmsService) {
val connectionInfo = ConnectionInfo().apply {
features = FEATURES
}
callback.onPostInitCompleteWithConnectionInfo(
ConnectionResult.SUCCESS,
SemanticLocationServiceImpl().asBinder(),
connectionInfo
)
}
}
class SemanticLocationServiceImpl : ISemanticLocationService.Stub() {
override fun registerSemanticLocationEvents(
params: SemanticLocationParameters,
callback: IStatusCallback,
request: SemanticLocationEventRequest,
pendingIntent: PendingIntent
) {
Log.d(TAG, "registerSemanticLocationEvents: $params")
}
override fun setIncognitoMode(params: SemanticLocationParameters, callback: IStatusCallback, mode: Boolean) {
Log.d(TAG, "setIncognitoMode: $params")
}
override fun unregisterSemanticLocationEvents(params: SemanticLocationParameters, callback: IStatusCallback, pendingIntent: PendingIntent) {
Log.d(TAG, "unregisterSemanticLocationEvents: $params")
}
}

View file

@ -0,0 +1,20 @@
/*
* 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 com.google.android.gms.wallet.dynamite;
public class WalletDynamiteCreatorImpl {
}

View file

@ -0,0 +1,124 @@
/*
* 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 com.google.firebase.database.connection.idl;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.dynamic.IObjectWrapper;
import java.util.List;
public class IPersistentConnectionImpl extends IPersistentConnection.Stub {
private static final String TAG = "GmsFirebaseDbConImpl";
@Override
public void setup(ConnectionConfig var1, IConnectionAuthTokenProvider var2, IObjectWrapper var3, IPersistentConnectionDelegate var4) throws RemoteException {
Log.d(TAG, "unimplemented Method: setup");
}
@Override
public void initialize() throws RemoteException {
Log.d(TAG, "unimplemented Method: initialize");
}
@Override
public void shutdown() throws RemoteException {
Log.d(TAG, "unimplemented Method: shutdown");
}
@Override
public void refreshAuthToken() throws RemoteException {
Log.d(TAG, "unimplemented Method: refreshAuthToken");
}
@Override
public void listen(List<String> var1, IObjectWrapper var2, IListenHashProvider var3, long var4, IRequestResultCallback var6) throws RemoteException {
Log.d(TAG, "unimplemented Method: listen");
}
@Override
public void unlisten(List<String> var1, IObjectWrapper var2) throws RemoteException {
Log.d(TAG, "unimplemented Method: unlisten");
}
@Override
public void purgeOutstandingWrites() throws RemoteException {
Log.d(TAG, "unimplemented Method: purgeOutstandingWrites");
}
@Override
public void put(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
Log.d(TAG, "unimplemented Method: put");
}
@Override
public void compareAndPut(List<String> var1, IObjectWrapper var2, String var3, IRequestResultCallback var4) throws RemoteException {
Log.d(TAG, "unimplemented Method: compareAndPut");
}
@Override
public void merge(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
Log.d(TAG, "unimplemented Method: merge");
}
@Override
public void onDisconnectPut(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
Log.d(TAG, "unimplemented Method: onDisconnectPut");
}
@Override
public void onDisconnectMerge(List<String> var1, IObjectWrapper var2, IRequestResultCallback var3) throws RemoteException {
Log.d(TAG, "unimplemented Method: onDisconnectMerge");
}
@Override
public void onDisconnectCancel(List<String> var1, IRequestResultCallback var2) throws RemoteException {
Log.d(TAG, "unimplemented Method: onDisconnectCancel");
}
@Override
public void interrupt(String var1) throws RemoteException {
Log.d(TAG, "unimplemented Method: interrupt");
}
@Override
public void resume(String var1) throws RemoteException {
Log.d(TAG, "unimplemented Method: resume");
}
@Override
public boolean isInterrupted(String var1) throws RemoteException {
Log.d(TAG, "unimplemented Method: isInterrupted");
return false;
}
}

View file

@ -0,0 +1,39 @@
/*
* 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.gms.ads;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;
import org.microg.gms.BaseService;
import org.microg.gms.common.GmsService;
public class GService extends BaseService {
public GService() {
super("GmsAdsGSvc", GmsService.GSERVICES, GmsService.ADREQUEST);
}
@Override
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
callback.onPostInitComplete(ConnectionResult.API_DISABLED, null, null);
}
}

View file

@ -0,0 +1,134 @@
/*
* 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.gms.auth;
import static android.accounts.AccountManager.VISIBILITY_VISIBLE;
import android.Manifest;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.util.Log;
import androidx.annotation.Nullable;
import org.microg.gms.common.GooglePackagePermission;
import org.microg.gms.common.PackageUtils;
import java.util.Arrays;
import static android.os.Build.VERSION.SDK_INT;
import static org.microg.gms.auth.AuthConstants.DEFAULT_ACCOUNT_TYPE;
import static org.microg.gms.auth.AuthConstants.PROVIDER_EXTRA_ACCOUNTS;
import static org.microg.gms.auth.AuthConstants.PROVIDER_EXTRA_CLEAR_PASSWORD;
import static org.microg.gms.auth.AuthConstants.PROVIDER_METHOD_CLEAR_PASSWORD;
import static org.microg.gms.auth.AuthConstants.PROVIDER_METHOD_GET_ACCOUNTS;
public class AccountContentProvider extends ContentProvider {
private static final String TAG = "GmsAuthProvider";
@Override
public boolean onCreate() {
return true;
}
@Nullable
@Override
public Bundle call(String method, String arg, Bundle extras) {
String suggestedPackageName = null;
if (SDK_INT > 19) {
suggestedPackageName = getCallingPackage();
}
String packageName = PackageUtils.getAndCheckCallingPackage(getContext(), suggestedPackageName);
boolean hasGooglePackagePermission = PackageUtils.callerHasGooglePackagePermission(getContext(), GooglePackagePermission.ACCOUNT);
if (!hasGooglePackagePermission) {
String[] packagesForUid = getContext().getPackageManager().getPackagesForUid(Binder.getCallingUid());
if (packagesForUid != null && packagesForUid.length != 0)
Log.w(TAG, "Not granting extended access to " + Arrays.toString(packagesForUid)
+ ", signature: " + PackageUtils.firstSignatureDigest(getContext(), packagesForUid[0]));
if (getContext().checkCallingPermission(Manifest.permission.GET_ACCOUNTS) != PackageManager.PERMISSION_GRANTED)
throw new SecurityException("Access denied, missing google package permission or GET_ACCOUNTS");
}
long identityToken = Binder.clearCallingIdentity();
try {
if (PROVIDER_METHOD_GET_ACCOUNTS.equals(method)) {
Bundle result = new Bundle();
Account[] accounts = null;
if (arg != null && (arg.equals(DEFAULT_ACCOUNT_TYPE) || arg.startsWith(DEFAULT_ACCOUNT_TYPE + "."))) {
AccountManager am = AccountManager.get(getContext());
accounts = am.getAccountsByTypeForPackage(arg, packageName);
if (SDK_INT >= 26 && accounts != null && arg.equals(DEFAULT_ACCOUNT_TYPE)) {
for (Account account : accounts) {
if (am.getAccountVisibility(account, packageName) == AccountManager.VISIBILITY_UNDEFINED &&
(hasGooglePackagePermission || AuthPrefs.isAuthVisible(getContext()))) {
Log.d(TAG, "Make account " + account + " visible to " + packageName);
am.setAccountVisibility(account, packageName, VISIBILITY_VISIBLE);
}
}
}
}
if (accounts == null) {
accounts = new Account[0];
}
result.putParcelableArray(PROVIDER_EXTRA_ACCOUNTS, accounts);
return result;
} else if (PROVIDER_METHOD_CLEAR_PASSWORD.equals(method) && hasGooglePackagePermission) {
Account a = extras.getParcelable(PROVIDER_EXTRA_CLEAR_PASSWORD);
AccountManager.get(getContext()).clearPassword(a);
return null;
}
throw new UnsupportedOperationException(String.format("Unsupported method call %s(%s).", method, arg));
} finally {
Binder.restoreCallingIdentity(identityToken);
}
}
@Nullable
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
throw new UnsupportedOperationException();
}
@Nullable
@Override
public String getType(Uri uri) {
return "text/plain";
}
@Nullable
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException();
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException();
}
}

View file

@ -0,0 +1,299 @@
/*
* 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.gms.auth;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorActivity;
import android.accounts.AccountManager;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Html;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.google.android.gms.R;
import org.microg.gms.common.PackageUtils;
import org.microg.gms.people.PeopleManager;
import java.io.IOException;
import static android.accounts.AccountManager.KEY_ACCOUNT_NAME;
import static android.accounts.AccountManager.KEY_ACCOUNT_TYPE;
import static android.accounts.AccountManager.KEY_ANDROID_PACKAGE_NAME;
import static android.accounts.AccountManager.KEY_AUTHTOKEN;
import static android.accounts.AccountManager.KEY_CALLER_PID;
import static android.accounts.AccountManager.KEY_CALLER_UID;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
public class AskPermissionActivity extends AccountAuthenticatorActivity {
public static final String EXTRA_FROM_ACCOUNT_MANAGER = "from_account_manager";
public static final String EXTRA_CONSENT_DATA = "consent_data";
private static final String TAG = "GmsAuthAskPermission";
private AuthManager authManager;
private IntentData data;
private static class IntentData {
private String accountName;
private String accountType;
private Account account;
private String packageName;
private String service;
private int callerUid;
private int callerPid;
private ConsentData consentData;
private boolean fromAccountManager = false;
private CharSequence appLabel;
private Drawable appIcon;
private IntentData(Intent intent) {
if (intent != null) {
accountName = intent.getStringExtra(KEY_ACCOUNT_NAME);
accountType = intent.getStringExtra(KEY_ACCOUNT_TYPE);
packageName = intent.getStringExtra(KEY_ANDROID_PACKAGE_NAME);
service = intent.getStringExtra(KEY_AUTHTOKEN);
callerUid = intent.getIntExtra(KEY_CALLER_UID, 0);
callerPid = intent.getIntExtra(KEY_CALLER_PID, 0);
fromAccountManager = intent.hasExtra(EXTRA_FROM_ACCOUNT_MANAGER);
if (intent.hasExtra(EXTRA_CONSENT_DATA)) {
try {
consentData = ConsentData.ADAPTER.decode(intent.getByteArrayExtra(EXTRA_CONSENT_DATA));
} catch (Exception e) {
// Ignore
}
}
}
if (accountName != null && accountType != null) {
account = new Account(accountName, accountType);
}
}
private void verify(Context context) throws Exception {
if (accountName == null || accountType == null || account == null) throw new IllegalArgumentException("Required account information missing");
if (packageName == null || service == null) throw new IllegalArgumentException("Required request information missing");
if (callerUid == 0) throw new IllegalArgumentException("Required caller information missing");
PackageUtils.getAndCheckPackage(context, packageName, callerUid, callerPid);
PackageManager packageManager = context.getPackageManager();
ApplicationInfo applicationInfo = packageManager.getApplicationInfo(packageName, 0);
appLabel = packageManager.getApplicationLabel(applicationInfo);
appIcon = packageManager.getApplicationIcon(applicationInfo);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.ask_permission);
data = new IntentData(getIntent());
try {
data.verify(this);
} catch (Exception e) {
Log.w(TAG, "Verification failed", e);
finish();
return;
}
// This makes the dialog take up the full width
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
getWindow().setAttributes(lp);
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(data.packageName.hashCode());
authManager = new AuthManager(this, data.accountName, data.packageName, data.service);
Bitmap profileIcon = PeopleManager.getOwnerAvatarBitmap(this, data.accountName, false);
// receive profile icon
if (profileIcon != null) {
((ImageView) findViewById(R.id.account_photo)).setImageBitmap(profileIcon);
} else {
new Thread(() -> {
final Bitmap profileIcon1 = PeopleManager.getOwnerAvatarBitmap(AskPermissionActivity.this, data.accountName, true);
runOnUiThread(() -> ((ImageView) findViewById(R.id.account_photo)).setImageBitmap(profileIcon1));
}).start();
}
((ImageView) findViewById(R.id.app_icon)).setImageDrawable(data.appIcon);
if (isOAuth()) {
((TextView) findViewById(R.id.title)).setText(getString(R.string.ask_scope_permission_title, data.appLabel));
} else {
((TextView) findViewById(R.id.title)).setText(getString(R.string.ask_service_permission_title, data.appLabel));
}
findViewById(android.R.id.button1).setOnClickListener(v -> onAllow());
findViewById(android.R.id.button2).setOnClickListener(v -> onDeny());
((ListView) findViewById(R.id.permissions)).setAdapter(new PermissionAdapter());
}
public void onAllow() {
authManager.setPermitted(true);
findViewById(android.R.id.button1).setEnabled(false);
findViewById(android.R.id.button2).setEnabled(false);
findViewById(R.id.progress_bar).setVisibility(VISIBLE);
findViewById(R.id.no_progress_bar).setVisibility(GONE);
new Thread(() -> {
try {
AuthResponse response = authManager.requestAuthWithBackgroundResolution(data.fromAccountManager);
Bundle result = new Bundle();
result.putString(KEY_AUTHTOKEN, response.auth);
result.putString(KEY_ACCOUNT_NAME, data.accountName);
result.putString(KEY_ACCOUNT_TYPE, data.accountType);
result.putString(KEY_ANDROID_PACKAGE_NAME, data.packageName);
result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
setAccountAuthenticatorResult(result);
} catch (IOException e) {
Log.w(TAG, e);
}
finish();
}).start();
}
public void onDeny() {
authManager.setPermitted(false);
finish();
}
@Override
public void finish() {
if (data.packageName != null) {
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(data.packageName.hashCode());
}
super.finish();
}
private boolean isOAuth() {
return data.service.startsWith("oauth2:") || data.service.startsWith("oauth:");
}
private String getScopeLabel(String scope) {
if (data.consentData != null) {
for (ConsentData.ScopeDetails scopeDetails : data.consentData.scopes) {
if (scope.equals(scopeDetails.id)) {
return scopeDetails.title;
}
}
}
String labelResourceId = "permission_scope_";
String escapedScope = scope.replace("/", "_").replace("-", "_");
if (scope.startsWith("https://")) {
labelResourceId += escapedScope.substring(8);
} else {
labelResourceId += escapedScope;
}
int labelResource = getResources().getIdentifier(labelResourceId, "string", getPackageName());
if (labelResource != 0) {
return getString(labelResource);
}
return "unknown";
}
private String getScopeDescription(String scope) {
if (data.consentData != null) {
for (ConsentData.ScopeDetails scopeDetails : data.consentData.scopes) {
if (scope.equals(scopeDetails.id)) {
return scopeDetails.description;
}
}
}
return null;
}
private String getServiceLabel(String service) {
int labelResource = getResources().getIdentifier("permission_service_" + service + "_label", "string", getPackageName());
if (labelResource != 0) {
return getString(labelResource);
}
return "unknown";
}
private class PermissionAdapter extends BaseAdapter {
@Override
public int getCount() {
if (isOAuth()) {
return data.service.split(" ").length;
}
return 1;
}
@Override
public String getItem(int position) {
if (isOAuth()) {
String tokens = data.service.split(":", 2)[1];
return tokens.split(" ")[position];
}
return data.service;
}
@Override
public long getItemId(int position) {
return getItem(position).hashCode();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
String item = getItem(position);
String label;
String description;
if (isOAuth()) {
label = getScopeLabel(item);
description = getScopeDescription(item);
} else {
label = getServiceLabel(item);
description = null;
}
View view = convertView;
if (view == null) {
view = LayoutInflater.from(AskPermissionActivity.this)
.inflate(R.layout.ask_permission_list_entry, parent, false);
}
((TextView) view.findViewById(android.R.id.text1)).setText(label);
TextView textView = (TextView) view.findViewById(android.R.id.text2);
if (description != null && !description.isEmpty()) {
textView.setText(Html.fromHtml(description.trim().replace("\n", "<br>")));
textView.setVisibility(VISIBLE);
} else {
textView.setVisibility(GONE);
}
return view;
}
}
}

View file

@ -0,0 +1,367 @@
/*
* SPDX-FileCopyrightText: 2023 microG Project Team
* SPDX-License-Identifier: Apache-2.0
*/
package org.microg.gms.auth;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.util.Log;
import androidx.annotation.NonNull;
import org.microg.gms.accountaction.ErrorResolverKt;
import org.microg.gms.accountaction.Resolution;
import org.microg.gms.common.NotOkayException;
import org.microg.gms.common.PackageUtils;
import org.microg.gms.settings.SettingsContract;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static android.os.Build.VERSION.SDK_INT;
import static org.microg.gms.auth.AuthPrefs.isTrustGooglePermitted;
public class AuthManager {
private static final String TAG = "GmsAuthManager";
public static final String PERMISSION_TREE_BASE = "com.google.android.googleapps.permission.GOOGLE_AUTH.";
public static final String PREF_AUTH_VISIBLE = SettingsContract.Auth.VISIBLE;
public static final int ONE_HOUR_IN_SECONDS = 60 * 60;
public Map<Object, Object> dynamicFields = new HashMap<>();
private final Context context;
private final String accountName;
private final String packageName;
private final String service;
private AccountManager accountManager;
private Account account;
private String packageSignature;
private String accountType;
private int delegationType;
private String delegateeUserId;
private String oauth2Foreground;
private String oauth2Prompt;
private String itCaveatTypes;
private String tokenRequestOptions;
public String includeEmail;
public String includeProfile;
public boolean isGmsApp;
public boolean ignoreStoredPermission = false;
public boolean forceRefreshToken = false;
public AuthManager(Context context, String accountName, String packageName, String service) {
this.context = context;
this.accountName = accountName;
this.packageName = packageName;
this.service = service;
}
public String getAccountType() {
if (accountType == null)
accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
return accountType;
}
public AccountManager getAccountManager() {
if (accountManager == null)
accountManager = AccountManager.get(context);
return accountManager;
}
public Account getAccount() {
if (account == null)
account = new Account(accountName, getAccountType());
return account;
}
public void setPackageSignature(String packageSignature) {
this.packageSignature = packageSignature;
}
public String getPackageSignature() {
if (packageSignature == null)
packageSignature = PackageUtils.firstSignatureDigest(context, packageName);
return packageSignature;
}
public String buildTokenKey(String service) {
Uri.Builder builder = Uri.EMPTY.buildUpon();
if (delegationType != 0 && delegateeUserId != null)
builder.appendQueryParameter("delegation_type", Integer.toString(delegationType))
.appendQueryParameter("delegatee_user_id", delegateeUserId);
if (tokenRequestOptions != null) builder.appendQueryParameter("token_request_options", tokenRequestOptions);
if (includeEmail != null) builder.appendQueryParameter("include_email", includeEmail);
if (includeProfile != null) builder.appendQueryParameter("include_profile", includeEmail);
String query = builder.build().getEncodedQuery();
return packageName + ":" + getPackageSignature() + ":" + service + (query != null ? ("?" + query) : "");
}
public String buildTokenKey() {
return buildTokenKey(service);
}
public String buildPermKey() {
return "perm." + buildTokenKey();
}
public void setPermitted(boolean value) {
setUserData(buildPermKey(), value ? "1" : "0");
if (SDK_INT >= 26 && value && packageName != null) {
// Make account persistently visible as we already granted access
accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE);
}
}
public boolean isPermitted() {
if (!service.startsWith("oauth")) {
if (context.getPackageManager().checkPermission(PERMISSION_TREE_BASE + service, packageName) == PackageManager.PERMISSION_GRANTED) {
return true;
}
}
String perm = getUserData(buildPermKey());
if (!"1".equals(perm)) {
return false;
}
return true;
}
public void setExpiry(long expiry) {
setUserData(buildExpireKey(), Long.toString(expiry));
}
public String getUserData(String key) {
return getAccountManager().getUserData(getAccount(), key);
}
public void setUserData(String key, String value) {
getAccountManager().setUserData(getAccount(), key, value);
}
public void setDelegation(int delegationType, String delegateeUserId) {
if (delegationType != 0 && delegateeUserId != null) {
this.delegationType = delegationType;
this.delegateeUserId = delegateeUserId;
} else {
this.delegationType = 0;
this.delegateeUserId = null;
}
}
public void setOauth2Foreground(String oauth2Foreground) {
this.oauth2Foreground = oauth2Foreground;
}
public void setOauth2Prompt(String oauth2Prompt) {
this.oauth2Prompt = oauth2Prompt;
}
public void setItCaveatTypes(String itCaveatTypes) {
this.itCaveatTypes = itCaveatTypes;
}
public void setTokenRequestOptions(String tokenRequestOptions) {
this.tokenRequestOptions = tokenRequestOptions;
}
public void putDynamicFiled(Object key, Object value) {
this.dynamicFields.put(key, value);
}
public boolean accountExists() {
for (Account refAccount : getAccountManager().getAccountsByType(accountType)) {
if (refAccount.name.equalsIgnoreCase(accountName)) return true;
}
return false;
}
public String peekAuthToken() {
Log.d(TAG, "peekAuthToken: " + buildTokenKey());
return getAccountManager().peekAuthToken(getAccount(), buildTokenKey());
}
public String getAuthToken() {
if (service.startsWith("weblogin:")) return null;
if (System.currentTimeMillis() / 1000L >= getExpiry() - 300L) {
Log.d(TAG, "token present, but expired");
return null;
}
return peekAuthToken();
}
public String buildExpireKey() {
return "EXP." + buildTokenKey();
}
public long getExpiry() {
String exp = getUserData(buildExpireKey());
if (exp == null) return -1;
return Long.parseLong(exp);
}
public void setAuthToken(String auth) {
setAuthToken(service, auth);
}
public void setAuthToken(String service, String auth) {
getAccountManager().setAuthToken(getAccount(), buildTokenKey(service), auth);
if (SDK_INT >= 26 && packageName != null && auth != null) {
// Make account persistently visible as we already granted access
accountManager.setAccountVisibility(getAccount(), packageName, AccountManager.VISIBILITY_VISIBLE);
}
}
public void invalidateAuthToken() {
String authToken = peekAuthToken();
invalidateAuthToken(authToken);
}
@SuppressLint("MissingPermission")
public void invalidateAuthToken(String auth) {
getAccountManager().invalidateAuthToken(accountType, auth);
}
public void storeResponse(AuthResponse response) {
if (service.startsWith("weblogin:")) return;
if (response.accountId != null)
setUserData("GoogleUserId", response.accountId);
if (response.Sid != null)
setAuthToken("SID", response.Sid);
if (response.LSid != null)
setAuthToken("LSID", response.LSid);
if (response.auth != null && (response.expiry != 0 || response.storeConsentRemotely)) {
setAuthToken(response.auth);
if (response.expiry > 0) {
setExpiry(response.expiry);
} else {
setExpiry(System.currentTimeMillis() / 1000 + ONE_HOUR_IN_SECONDS); // make valid for one hour by default
}
}
}
private boolean isSystemApp() {
try {
int flags = context.getPackageManager().getApplicationInfo(packageName, 0).flags;
return (flags & FLAG_SYSTEM) > 0 || (flags & FLAG_UPDATED_SYSTEM_APP) > 0;
} catch (PackageManager.NameNotFoundException e) {
return false;
}
}
@NonNull
public AuthResponse requestAuthWithBackgroundResolution(boolean legacy) throws IOException {
try {
return requestAuth(legacy);
} catch (NotOkayException e) {
if (e.getMessage() != null) {
Resolution errorResolution = ErrorResolverKt.resolveAuthErrorMessage(context, e.getMessage());
if (errorResolution != null) {
AuthResponse response = ErrorResolverKt.initiateFromBackgroundBlocking(
errorResolution,
context,
getAccount(),
// infinite loop is prevented
() -> requestAuth(legacy)
);
if (response == null) throw new IOException(e);
return response;
} else {
throw new IOException(e);
}
} else {
throw new IOException(e);
}
}
}
@NonNull
public AuthResponse requestAuthWithForegroundResolution(boolean legacy) throws IOException {
try {
return requestAuth(legacy);
} catch (NotOkayException e) {
if (e.getMessage() != null) {
Resolution errorResolution = ErrorResolverKt.resolveAuthErrorMessage(context, e.getMessage());
if (errorResolution != null) {
AuthResponse response = ErrorResolverKt.initiateFromForegroundBlocking(
errorResolution,
context,
getAccount(),
// infinite loop is prevented
() -> requestAuth(legacy)
);
if (response == null) throw new IOException(e);
return response;
} else {
throw new IOException(e);
}
} else {
throw new IOException(e);
}
}
}
@NonNull
public AuthResponse requestAuth(boolean legacy) throws IOException {
if (service.equals(AuthConstants.SCOPE_GET_ACCOUNT_ID)) {
AuthResponse response = new AuthResponse();
response.accountId = response.auth = getAccountManager().getUserData(getAccount(), "GoogleUserId");
return response;
}
if (isPermitted() || isTrustGooglePermitted(context)) {
String token = getAuthToken();
if (token != null && !forceRefreshToken) {
AuthResponse response = new AuthResponse();
response.issueAdvice = "stored";
response.auth = token;
if (service.startsWith("oauth2:")) {
response.grantedScopes = service.substring(7);
}
response.expiry = getExpiry();
return response;
}
}
AuthRequest request = new AuthRequest().fromContext(context)
.source("android")
.app(packageName, getPackageSignature())
.email(accountName)
.token(getAccountManager().getPassword(getAccount()))
.service(service)
.delegation(delegationType, delegateeUserId)
.oauth2Foreground(oauth2Foreground)
.oauth2Prompt(oauth2Prompt)
.oauth2IncludeProfile(includeProfile)
.oauth2IncludeEmail(includeEmail)
.itCaveatTypes(itCaveatTypes)
.tokenRequestOptions(tokenRequestOptions)
.systemPartition(isSystemApp())
.hasPermission(!ignoreStoredPermission && isPermitted())
.putDynamicFiledMap(dynamicFields);
if (isGmsApp) {
request.appIsGms();
}
if (legacy) {
request.callerIsGms().calledFromAccountManager();
} else {
request.callerIsApp();
}
AuthResponse response = request.getResponse();
if (!isPermitted() && !isTrustGooglePermitted(context)) {
response.auth = null;
} else {
storeResponse(response);
}
return response;
}
public String getService() {
return service;
}
}

View file

@ -0,0 +1,279 @@
/*
* 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.gms.auth;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Parcel;
import android.os.RemoteException;
import android.util.Base64;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.PendingIntentCompat;
import com.google.android.auth.IAuthManagerService;
import com.google.android.gms.R;
import com.google.android.gms.auth.AccountChangeEventsRequest;
import com.google.android.gms.auth.AccountChangeEventsResponse;
import com.google.android.gms.auth.GetHubTokenInternalResponse;
import com.google.android.gms.auth.GetHubTokenRequest;
import com.google.android.gms.auth.HasCapabilitiesRequest;
import com.google.android.gms.auth.TokenData;
import com.google.android.gms.common.api.Scope;
import org.microg.gms.common.GooglePackagePermission;
import org.microg.gms.common.PackageUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import static android.accounts.AccountManager.*;
import static android.os.Build.VERSION.SDK_INT;
import static org.microg.gms.auth.AskPermissionActivity.EXTRA_CONSENT_DATA;
public class AuthManagerServiceImpl extends IAuthManagerService.Stub {
private static final String TAG = "GmsAuthManagerSvc";
public static final String KEY_ACCOUNT_FEATURES = "account_features";
public static final String KEY_AUTHORITY = "authority";
public static final String KEY_CALLBACK_INTENT = "callback_intent";
public static final String KEY_CALLER_UID = "callerUid";
public static final String KEY_ANDROID_PACKAGE_NAME = "androidPackageName";
public static final String KEY_CLIENT_PACKAGE_NAME = "clientPackageName";
public static final String KEY_HANDLE_NOTIFICATION = "handle_notification";
public static final String KEY_REQUEST_ACTIONS = "request_visible_actions";
public static final String KEY_REQUEST_VISIBLE_ACTIVITIES = "request_visible_actions";
public static final String KEY_SUPPRESS_PROGRESS_SCREEN = "suppressProgressScreen";
public static final String KEY_SYNC_EXTRAS = "sync_extras";
public static final String KEY_DELEGATION_TYPE = "delegation_type";
public static final String KEY_DELEGATEE_USER_ID = "delegatee_user_id";
public static final String KEY_ERROR = "Error";
public static final String KEY_USER_RECOVERY_INTENT = "userRecoveryIntent";
private final Context context;
public AuthManagerServiceImpl(Context context) {
this.context = context;
}
@Override
public Bundle getToken(String accountName, String scope, Bundle extras) {
return getTokenWithAccount(new Account(accountName, AuthConstants.DEFAULT_ACCOUNT_TYPE), scope, extras);
}
private List<Scope> getScopes(String scope) {
if (!scope.startsWith("oauth2:")) return null;
String[] strings = scope.substring(7).split(" ");
List<Scope> res = new ArrayList<Scope>();
for (String string : strings) {
res.add(new Scope(string));
}
return res;
}
private static CharSequence getPackageLabel(String packageName, PackageManager pm) {
try {
return pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0));
} catch (PackageManager.NameNotFoundException e) {
return packageName;
}
}
@Override
public AccountChangeEventsResponse getChangeEvents(AccountChangeEventsRequest request) {
return new AccountChangeEventsResponse();
}
@Override
public Bundle getTokenWithAccount(Account account, String scope, Bundle extras) {
String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME);
if (packageName == null || packageName.isEmpty())
packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME);
packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0));
boolean notify = extras.getBoolean(KEY_HANDLE_NOTIFICATION, false);
scope = Objects.equals(AuthConstants.SCOPE_OAUTH2, scope) ? AuthConstants.SCOPE_EM_OP_PRO : scope;
if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope))
Log.d(TAG, "getToken: account:" + account.name + " scope:" + scope + " extras:" + extras + ", notify: " + notify);
scope = Objects.equals(AuthConstants.SCOPE_OAUTH2, scope) ? AuthConstants.SCOPE_EM_OP_PRO : scope;
/*
* TODO: This scope seems to be invalid (according to https://developers.google.com/oauthplayground/),
* but is used in some applications anyway. Removing it is unlikely a good solution, but works for now.
*/
scope = scope.replace("https://www.googleapis.com/auth/identity.plus.page.impersonation ", "");
AuthManager authManager = new AuthManager(context, account.name, packageName, scope);
if (extras.containsKey(KEY_DELEGATION_TYPE) && extras.getInt(KEY_DELEGATION_TYPE) != 0 ) {
authManager.setDelegation(extras.getInt(KEY_DELEGATION_TYPE), extras.getString("delegatee_user_id"));
}
authManager.setOauth2Foreground(notify ? "0" : "1");
Bundle result = new Bundle();
result.putString(KEY_ACCOUNT_NAME, account.name);
result.putString(KEY_ACCOUNT_TYPE, authManager.getAccountType());
if (!authManager.accountExists()) {
result.putString(KEY_ERROR, "NetworkError");
return result;
}
try {
AuthResponse res = authManager.requestAuthWithBackgroundResolution(false);
if (res.auth != null) {
if (!AuthConstants.SCOPE_GET_ACCOUNT_ID.equals(scope))
Log.d(TAG, "getToken: " + res);
result.putString(KEY_AUTHTOKEN, res.auth);
Bundle details = new Bundle();
details.putParcelable("TokenData", new TokenData(res.auth, res.expiry, scope.startsWith("oauth2:"), getScopes(res.grantedScopes != null ? res.grantedScopes : scope)));
result.putBundle("tokenDetails", details);
result.putString(KEY_ERROR, "OK");
} else {
result.putString(KEY_ERROR, "NeedPermission");
Intent i = new Intent(context, AskPermissionActivity.class);
i.putExtras(extras);
i.putExtra(KEY_ANDROID_PACKAGE_NAME, packageName);
i.putExtra(KEY_ACCOUNT_TYPE, authManager.getAccountType());
i.putExtra(KEY_ACCOUNT_NAME, account.name);
i.putExtra(KEY_AUTHTOKEN, scope);
i.putExtra(KEY_CALLER_UID, getCallingUid());
i.putExtra(KEY_CALLER_PID, getCallingPid());
try {
if (res.consentDataBase64 != null)
i.putExtra(EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.URL_SAFE));
} catch (Exception e) {
Log.w(TAG, "Can't decode consent data: ", e);
}
if (notify) {
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.notify(packageName.hashCode(), new NotificationCompat.Builder(context)
.setContentIntent(PendingIntentCompat.getActivity(context, 0, i, 0, false))
.setContentTitle(context.getString(R.string.auth_notification_title))
.setContentText(context.getString(R.string.auth_notification_content, getPackageLabel(packageName, context.getPackageManager())))
.setSmallIcon(android.R.drawable.stat_notify_error)
.build());
}
result.putParcelable(KEY_USER_RECOVERY_INTENT, i);
}
} catch (IOException e) {
Log.w(TAG, e);
result.putString(KEY_ERROR, "NetworkError");
}
return result;
}
@Override
public Bundle getAccounts(Bundle extras) {
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.ACCOUNT);
String[] accountFeatures = extras.getStringArray(KEY_ACCOUNT_FEATURES);
String accountType = extras.getString(KEY_ACCOUNT_TYPE);
Account[] accounts;
if (accountFeatures != null) {
try {
accounts = AccountManager.get(context).getAccountsByTypeAndFeatures(accountType, accountFeatures, null, null).getResult(5, TimeUnit.SECONDS);
} catch (Exception e) {
Log.w(TAG, e);
return null;
}
} else {
accounts = AccountManager.get(context).getAccountsByType(accountType);
}
Bundle res = new Bundle();
res.putParcelableArray(KEY_ACCOUNTS, accounts);
return res;
}
@Override
public Bundle removeAccount(Account account) {
Log.w(TAG, "Not implemented: removeAccount(" + account + ")");
return null;
}
@Override
public Bundle requestGoogleAccountsAccess(String packageName) throws RemoteException {
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.ACCOUNT);
if (SDK_INT >= 26) {
Map<Account, Integer> visibilityForPackage = get(context).getAccountsAndVisibilityForPackage(packageName, AuthConstants.DEFAULT_ACCOUNT_TYPE);
for (Account account : get(context).getAccountsByType(AuthConstants.DEFAULT_ACCOUNT_TYPE)) {
Integer visibility = visibilityForPackage.get(account);
if (visibility != null && visibility != VISIBILITY_VISIBLE) {
get(context).setAccountVisibility(account, packageName, VISIBILITY_VISIBLE);
}
}
Bundle res = new Bundle();
res.putString("Error", "Ok");
return res;
} else {
Log.w(TAG, "Not implemented: requestGoogleAccountsAccess(" + packageName + ")");
}
return null;
}
@Override
public int hasCapabilities(HasCapabilitiesRequest request) throws RemoteException {
PackageUtils.assertGooglePackagePermission(context, GooglePackagePermission.ACCOUNT);
List<String> services = Arrays.asList(AccountManager.get(context).getUserData(request.account, "services").split(","));
for (String capability : request.capabilities) {
if (capability.startsWith("service_") && !services.contains(capability.substring(8)) || !services.contains(capability)) {
return 6;
}
}
Log.w(TAG, "Not fully implemented: hasCapabilities(" + request.account + ", " + Arrays.toString(request.capabilities) + ")");
return 1;
}
@Override
public GetHubTokenInternalResponse getHubToken(GetHubTokenRequest request, Bundle extras) throws RemoteException {
Log.w(TAG, "Not implemented: getHubToken()");
return null;
}
@Override
@SuppressLint("MissingPermission") // Workaround bug in Android Linter
public Bundle clearToken(String token, Bundle extras) {
String packageName = extras.getString(KEY_ANDROID_PACKAGE_NAME);
if (packageName == null) packageName = extras.getString(KEY_CLIENT_PACKAGE_NAME);
packageName = PackageUtils.getAndCheckCallingPackage(context, packageName, extras.getInt(KEY_CALLER_UID, 0), extras.getInt(KEY_CALLER_PID, 0));
Log.d(TAG, "clearToken: token:" + token + " extras:" + extras);
AccountManager.get(context).invalidateAuthToken(AuthConstants.DEFAULT_ACCOUNT_TYPE, token);
Bundle res = new Bundle();
res.putString("Error", "Ok");
res.putBoolean("booleanResult", true);
return res;
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
if (super.onTransact(code, data, reply, flags)) return true;
Log.d(TAG, "onTransact [unknown]: " + code + ", " + data + ", " + flags);
return false;
}
}

View file

@ -0,0 +1,118 @@
/*
* 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.gms.auth.login;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.R;
public abstract class AssistantActivity extends AppCompatActivity {
private static final int TITLE_MIN_HEIGHT = 64;
private static final double TITLE_WIDTH_FACTOR = (8.0 / 18.0);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.login_assistant);
formatTitle();
findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onNextButtonClicked();
}
});
findViewById(R.id.back_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onBackButtonClicked();
}
});
}
@SuppressLint("WrongViewCast")
private void formatTitle() {
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
double widthPixels = (double) (getResources().getDisplayMetrics().widthPixels);
findViewById(R.id.title_container).getLayoutParams().height =
(int) (dpToPx(TITLE_MIN_HEIGHT) + (TITLE_WIDTH_FACTOR * widthPixels));
} else {
findViewById(R.id.title_container).getLayoutParams().height = dpToPx(TITLE_MIN_HEIGHT);
}
}
public void setNextButtonText(@StringRes int res) {
setNextButtonText(getText(res));
}
public void setNextButtonText(CharSequence text) {
if (text == null) {
findViewById(R.id.next_button).setVisibility(View.GONE);
} else {
findViewById(R.id.next_button).setVisibility(View.VISIBLE);
((Button) findViewById(R.id.next_button)).setText(text);
}
}
public void setBackButtonText(@StringRes int res) {
setBackButtonText(getText(res));
}
public void setBackButtonText(CharSequence text) {
if (text == null) {
findViewById(R.id.back_button).setVisibility(View.GONE);
} else {
findViewById(R.id.back_button).setVisibility(View.VISIBLE);
((Button) findViewById(R.id.back_button)).setText(text);
}
}
protected void onNextButtonClicked() {
}
protected void onBackButtonClicked() {
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
formatTitle();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
((TextView) findViewById(R.id.title)).setText(title);
}
public int dpToPx(int dp) {
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
return Math.round(dp * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT));
}
}

View file

@ -0,0 +1,727 @@
/*
* 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.gms.auth.login;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.webkit.CookieManager;
import android.webkit.JavascriptInterface;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.StringRes;
import androidx.webkit.WebViewClientCompat;
import com.google.android.gms.R;
import org.json.JSONArray;
import org.microg.gms.accountaction.AccountNotificationKt;
import org.microg.gms.auth.AuthConstants;
import org.microg.gms.auth.AuthManager;
import org.microg.gms.auth.AuthRequest;
import org.microg.gms.auth.AuthResponse;
import org.microg.gms.checkin.CheckinManager;
import org.microg.gms.checkin.LastCheckinInfo;
import org.microg.gms.common.Constants;
import org.microg.gms.common.HttpFormClient;
import org.microg.gms.common.Utils;
import org.microg.gms.people.PeopleManager;
import org.microg.gms.profile.Build;
import org.microg.gms.profile.ProfileManager;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.Locale;
import static android.accounts.AccountManager.PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE;
import static android.accounts.AccountManager.VISIBILITY_USER_MANAGED_VISIBLE;
import static android.os.Build.VERSION.SDK_INT;
import static android.telephony.TelephonyManager.SIM_STATE_UNKNOWN;
import static android.view.KeyEvent.KEYCODE_BACK;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
import static org.microg.gms.auth.AuthPrefs.isAuthVisible;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.common.Constants.GMS_VERSION_CODE;
import static org.microg.gms.common.Constants.VENDING_PACKAGE_NAME;
import static org.microg.gms.gcm.ExtensionsKt.ACTION_GCM_REGISTER_ACCOUNT;
import static org.microg.gms.gcm.ExtensionsKt.KEY_GCM_REGISTER_ACCOUNT_NAME;
public class LoginActivity extends AssistantActivity {
public static final String TMPL_NEW_ACCOUNT = "new_account";
public static final String EXTRA_TMPL = "tmpl";
public static final String EXTRA_EMAIL = "email";
public static final String EXTRA_TOKEN = "masterToken";
public static final String EXTRA_RE_AUTH_ACCOUNT = "re_auth_account";
public static final int STATUS_BAR_DISABLE_BACK = 0x00400000;
private static final String TAG = "GmsAuthLoginBrowser";
private static final String EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup";
private static final String EMBEDDED_RE_AUTH_URL = "https://accounts.google.com/embedded/reauth/v2/android";
private static final String PROGRAMMATIC_AUTH_URL = "https://accounts.google.com/o/oauth2/programmatic_auth";
private static final String GOOGLE_SUITE_URL = "https://accounts.google.com/signin/continue";
private static final String MAGIC_USER_AGENT = " MinuteMaid";
private static final String COOKIE_OAUTH_TOKEN = "oauth_token";
private static final String ACTION_UPDATE_ACCOUNT = "com.google.android.gms.auth.GOOGLE_ACCOUNT_CHANGE";
private static final String PERMISSION_UPDATE_ACCOUNT = "com.google.android.gms.auth.permission.GOOGLE_ACCOUNT_CHANGE";
private final FidoHandler fidoHandler = new FidoHandler(this);
private final DroidGuardHandler dgHandler = new DroidGuardHandler(this);
private WebView webView;
private String accountType;
private AccountManager accountManager;
private AccountAuthenticatorResponse response;
private InputMethodManager inputMethodManager;
private ViewGroup authContent;
private int state = 0;
private boolean isReAuth = false;
private Account reAuthAccount;
@SuppressLint("AddJavascriptInterface")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
accountManager = AccountManager.get(LoginActivity.this);
inputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
webView = createWebView(this);
webView.addJavascriptInterface(new JsBridge(), "mm");
authContent = (ViewGroup) findViewById(R.id.auth_content);
((ViewGroup) findViewById(R.id.auth_root)).addView(webView);
webView.setWebViewClient(new WebViewClientCompat() {
@Override
public void onPageFinished(WebView view, String url) {
Log.d(TAG, "pageFinished: " + view.getUrl());
Uri uri = Uri.parse(view.getUrl());
// Begin login.
// Only required if client code does not invoke showView() via JSBridge
if ("identifier".equals(uri.getFragment()) || uri.getPath().endsWith("/identifier"))
runOnUiThread(() -> webView.setVisibility(VISIBLE));
// Normal login.
if ("close".equals(uri.getFragment()))
closeWeb(false);
// Google Suite login.
if (url.startsWith(GOOGLE_SUITE_URL))
closeWeb(false);
// IDK when this is called.
if (url.startsWith(PROGRAMMATIC_AUTH_URL))
closeWeb(true);
}
});
if(getIntent().hasExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE)){
Object tempObject = getIntent().getExtras().get(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
if (tempObject instanceof AccountAuthenticatorResponse) {
response = (AccountAuthenticatorResponse) tempObject;
}
}
if (getIntent().hasExtra(EXTRA_RE_AUTH_ACCOUNT)) {
reAuthAccount = getIntent().getParcelableExtra(EXTRA_RE_AUTH_ACCOUNT);
isReAuth = reAuthAccount != null;
}
if (getIntent().hasExtra(EXTRA_TOKEN)) {
if (getIntent().hasExtra(EXTRA_EMAIL)) {
AccountManager accountManager = AccountManager.get(this);
Account account = new Account(getIntent().getStringExtra(EXTRA_EMAIL), accountType);
accountManager.addAccountExplicitly(account, getIntent().getStringExtra(EXTRA_TOKEN), null);
if (isAuthVisible(this) && SDK_INT >= 26) {
accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE);
}
retrieveGmsToken(account);
} else {
retrieveRtToken(getIntent().getStringExtra(EXTRA_TOKEN));
}
} else if (android.os.Build.VERSION.SDK_INT < 21 || isReAuth) {
init();
} else {
setMessage(R.string.auth_before_connect);
setBackButtonText(android.R.string.cancel);
setNextButtonText(R.string.auth_sign_in);
}
}
@Override
protected void onNextButtonClicked() {
super.onNextButtonClicked();
state++;
if (state == 1) {
init();
} else if (state == -1) {
loginCanceled();
}
}
@Override
protected void onBackButtonClicked() {
super.onBackButtonClicked();
state--;
if (state == -1) {
loginCanceled();
}
}
public void loginCanceled() {
Log.d(TAG, "loginCanceled: ");
setResult(RESULT_CANCELED);
if (response != null) {
response.onError(AccountManager.ERROR_CODE_CANCELED, "Canceled");
}
if (SDK_INT >= 21) { finishAndRemoveTask(); } else finish();
}
@Override
public void onBackPressed() {
super.onBackPressed();
loginCanceled();
}
private void init() {
setTitle(R.string.just_a_sec);
setBackButtonText(null);
setNextButtonText(null);
View loading = getLayoutInflater().inflate(R.layout.login_assistant_loading, authContent, false);
authContent.removeAllViews();
authContent.addView(loading);
setMessage(R.string.auth_connecting);
CookieManager.getInstance().setAcceptCookie(true);
if (SDK_INT >= 21) {
CookieManager.getInstance().removeAllCookies(value -> start());
} else {
//noinspection deprecation
CookieManager.getInstance().removeAllCookie();
start();
}
}
private static WebView createWebView(Context context) {
WebView webView = new WebView(context);
if (SDK_INT < 21) {
webView.setVisibility(VISIBLE);
} else {
webView.setVisibility(INVISIBLE);
}
webView.setLayoutParams(new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
webView.setBackgroundColor(Color.TRANSPARENT);
prepareWebViewSettings(context, webView.getSettings());
return webView;
}
@SuppressLint("SetJavaScriptEnabled")
private static void prepareWebViewSettings(Context context, WebSettings settings) {
ProfileManager.ensureInitialized(context);
settings.setUserAgentString(Build.INSTANCE.generateWebViewUserAgentString(settings.getUserAgentString()) + MAGIC_USER_AGENT);
settings.setJavaScriptEnabled(true);
settings.setSupportMultipleWindows(false);
settings.setSaveFormData(false);
settings.setAllowFileAccess(false);
settings.setDatabaseEnabled(false);
settings.setNeedInitialFocus(false);
settings.setUseWideViewPort(false);
settings.setSupportZoom(false);
settings.setJavaScriptCanOpenWindowsAutomatically(false);
}
private void start() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
if (LastCheckinInfo.read(this).getAndroidId() == 0) {
new Thread(() -> {
Runnable next;
next = checkin(false) ? this::loadLoginPage : () -> showError(R.string.auth_general_error_desc);
LoginActivity.this.runOnUiThread(next);
}).start();
} else {
loadLoginPage();
}
} else {
showError(R.string.no_network_error_desc);
}
}
private void showError(int errorRes) {
setTitle(R.string.sorry);
findViewById(R.id.progress_bar).setVisibility(View.INVISIBLE);
setMessage(errorRes);
}
private void setMessage(@StringRes int res) {
setMessage(getText(res));
}
private void setMessage(CharSequence text) {
((TextView) findViewById(R.id.description_text)).setText(text);
}
private void loadLoginPage() {
String tmpl = getIntent().hasExtra(EXTRA_TMPL) ? getIntent().getStringExtra(EXTRA_TMPL) : TMPL_NEW_ACCOUNT;
webView.loadUrl(buildUrl(tmpl, Utils.getLocale(this)));
}
protected void runScript(String js) {
runOnUiThread(() -> webView.loadUrl("javascript:" + js));
}
private void closeWeb(boolean programmaticAuth) {
setMessage(R.string.auth_finalize);
runOnUiThread(() -> webView.setVisibility(INVISIBLE));
String cookies = CookieManager.getInstance().getCookie(programmaticAuth ? PROGRAMMATIC_AUTH_URL : EMBEDDED_SETUP_URL);
String[] temp = cookies.split(";");
for (String ar1 : temp) {
if (ar1.trim().startsWith(COOKIE_OAUTH_TOKEN + "=")) {
String[] temp1 = ar1.split("=");
retrieveRtToken(temp1[1]);
return;
}
}
showError(R.string.auth_general_error_desc);
}
private void retrieveRtToken(String oAuthToken) {
new AuthRequest().fromContext(this)
.appIsGms()
.callerIsGms()
.service("ac2dm")
.token(oAuthToken).isAccessToken()
.addAccount()
.getAccountId()
.droidguardResults(null /*TODO*/)
.getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
@Override
public void onResponse(AuthResponse response) {
Account account = new Account(response.email, accountType);
if (isReAuth && reAuthAccount != null && reAuthAccount.name.equals(account.name)) {
accountManager.removeAccount(account, future -> saveAccount(account, response), null);
} else {
saveAccount(account, response);
}
}
@Override
public void onException(Exception exception) {
Log.w(TAG, "onException", exception);
runOnUiThread(() -> {
showError(R.string.auth_general_error_desc);
setNextButtonText(android.R.string.ok);
});
state = -2;
}
});
}
private void saveAccount(Account account, AuthResponse response) {
if (accountManager.addAccountExplicitly(account, response.token, null)) {
accountManager.setAuthToken(account, "SID", response.Sid);
accountManager.setAuthToken(account, "LSID", response.LSid);
accountManager.setUserData(account, "flags", "1");
accountManager.setUserData(account, "services", response.services);
accountManager.setUserData(account, "oauthAccessToken", "1");
accountManager.setUserData(account, "firstName", response.firstName);
accountManager.setUserData(account, "lastName", response.lastName);
if (!TextUtils.isEmpty(response.accountId))
accountManager.setUserData(account, "GoogleUserId", response.accountId);
retrieveGmsToken(account);
setResult(RESULT_OK);
} else {
Log.w(TAG, "Account NOT created!");
runOnUiThread(() -> {
showError(R.string.auth_general_error_desc);
setNextButtonText(android.R.string.ok);
});
state = -2;
}
}
private void returnSuccessResponse(Account account){
if (isReAuth && reAuthAccount != null) {
AccountNotificationKt.cancelAccountNotificationChannel(this, reAuthAccount);
}
if(response != null){
Bundle bd = new Bundle();
bd.putString(AccountManager.KEY_ACCOUNT_NAME,account.name);
bd.putBoolean("new_account_created",false);
bd.putString(AccountManager.KEY_ACCOUNT_TYPE,accountType);
response.onResult(bd);
}
Intent intent = new Intent(ACTION_UPDATE_ACCOUNT);
intent.setPackage(VENDING_PACKAGE_NAME);
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, account.name);
sendBroadcast(intent, PERMISSION_UPDATE_ACCOUNT);
}
private void retrieveGmsToken(final Account account) {
final AuthManager authManager = new AuthManager(this, account.name, GMS_PACKAGE_NAME, "ac2dm");
authManager.setPermitted(true);
new AuthRequest().fromContext(this)
.appIsGms()
.callerIsGms()
.service(authManager.getService())
.email(account.name)
.token(AccountManager.get(this).getPassword(account))
.systemPartition(true)
.hasPermission(true)
.addAccount()
.getAccountId()
.getResponseAsync(new HttpFormClient.Callback<AuthResponse>() {
@Override
public void onResponse(AuthResponse response) {
authManager.storeResponse(response);
String accountId = PeopleManager.loadUserInfo(LoginActivity.this, account);
if (!TextUtils.isEmpty(accountId))
accountManager.setUserData(account, "GoogleUserId", accountId);
if (isAuthVisible(LoginActivity.this) && SDK_INT >= 26) {
accountManager.setAccountVisibility(account, PACKAGE_NAME_KEY_LEGACY_NOT_VISIBLE, VISIBILITY_USER_MANAGED_VISIBLE);
}
checkin(true);
returnSuccessResponse(account);
notifyGcmGroupUpdate(account.name);
if (SDK_INT >= 21) { finishAndRemoveTask(); } else finish();
}
@Override
public void onException(Exception exception) {
Log.w(TAG, "onException", exception);
runOnUiThread(() -> {
showError(R.string.auth_general_error_desc);
setNextButtonText(android.R.string.ok);
});
state = -2;
}
});
}
private void notifyGcmGroupUpdate(String accountName) {
Intent intent = new Intent(ACTION_GCM_REGISTER_ACCOUNT);
intent.setPackage(Constants.GMS_PACKAGE_NAME);
intent.putExtra(KEY_GCM_REGISTER_ACCOUNT_NAME, accountName);
sendBroadcast(intent);
}
private boolean checkin(boolean force) {
try {
CheckinManager.checkin(LoginActivity.this, force);
return true;
} catch (IOException e) {
Log.w(TAG, "Checkin failed", e);
}
return false;
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if ((keyCode == KEYCODE_BACK) && webView.canGoBack() && (webView.getVisibility() == VISIBLE)) {
webView.goBack();
return true;
}
return super.onKeyDown(keyCode, event);
}
private String buildUrl(String tmpl, Locale locale) {
String uriString = isReAuth ? EMBEDDED_RE_AUTH_URL : EMBEDDED_SETUP_URL;
Uri.Builder builder = Uri.parse(uriString).buildUpon()
.appendQueryParameter("source", "android")
.appendQueryParameter("xoauth_display_name", "Android Device")
.appendQueryParameter("lang", locale.getLanguage())
.appendQueryParameter("cc", locale.getCountry().toLowerCase(Locale.US))
.appendQueryParameter("langCountry", locale.toString().toLowerCase(Locale.US))
.appendQueryParameter("hl", locale.toString().replace("_", "-"))
.appendQueryParameter("tmpl", tmpl);
if (isReAuth && reAuthAccount != null) {
builder.appendQueryParameter("Email", reAuthAccount.name);
}
return builder.build().toString();
}
private class JsBridge {
@JavascriptInterface
public final void addAccount(String json) {
Log.d(TAG, "JSBridge: addAccount");
}
@JavascriptInterface
public final void attemptLogin(String accountName, String password) {
Log.d(TAG, "JSBridge: attemptLogin");
}
@JavascriptInterface
public void backupSyncOptIn(String accountName) {
Log.d(TAG, "JSBridge: backupSyncOptIn");
}
@JavascriptInterface
public final void cancelFido2SignRequest() {
Log.d(TAG, "JSBridge: cancelFido2SignRequest");
fidoHandler.cancel();
}
@JavascriptInterface
public void clearOldLoginAttempts() {
Log.d(TAG, "JSBridge: clearOldLoginAttempts");
}
@JavascriptInterface
public final void closeView() {
Log.d(TAG, "JSBridge: closeView");
closeWeb(false);
}
@JavascriptInterface
public void fetchIIDToken(String entity) {
Log.d(TAG, "JSBridge: fetchIIDToken");
}
@JavascriptInterface
public final String fetchVerifiedPhoneNumber() {
Log.d(TAG, "JSBridge: fetchVerifiedPhoneNumber");
return null;
}
@SuppressWarnings("MissingPermission")
@JavascriptInterface
public final String getAccounts() {
Log.d(TAG, "JSBridge: getAccounts");
Account[] accountsByType = accountManager.getAccountsByType(accountType);
JSONArray json = new JSONArray();
for (Account account : accountsByType) {
json.put(account.name);
}
return json.toString();
}
@JavascriptInterface
public final String getAllowedDomains() {
Log.d(TAG, "JSBridge: getAllowedDomains");
return new JSONArray().toString();
}
@JavascriptInterface
public final String getAndroidId() {
long androidId = LastCheckinInfo.read(LoginActivity.this).getAndroidId();
Log.d(TAG, "JSBridge: getAndroidId");
if (androidId == 0 || androidId == -1) return null;
return Long.toHexString(androidId);
}
@JavascriptInterface
public final int getAuthModuleVersionCode() {
return GMS_VERSION_CODE;
}
@JavascriptInterface
public final int getBuildVersionSdk() {
return Build.VERSION.SDK_INT;
}
@JavascriptInterface
public int getDeviceContactsCount() {
return -1;
}
@JavascriptInterface
public final int getDeviceDataVersionInfo() {
return 1;
}
@JavascriptInterface
public final void getDroidGuardResult(String s) {
Log.d(TAG, "JSBridge: getDroidGuardResult");
try {
JSONArray array = new JSONArray(s);
StringBuilder sb = new StringBuilder();
sb.append(getAndroidId()).append(":").append(getBuildVersionSdk()).append(":").append(getPlayServicesVersionCode());
for (int i = 0; i < array.length(); i++) {
sb.append(":").append(array.getString(i));
}
String dg = Base64.encodeToString(MessageDigest.getInstance("SHA1").digest(sb.toString().getBytes()), 0);
dgHandler.start(dg);
} catch (Exception e) {
// Ignore
}
}
@JavascriptInterface
public final String getFactoryResetChallenges() {
return new JSONArray().toString();
}
@JavascriptInterface
public final String getPhoneNumber() {
return null;
}
@JavascriptInterface
public final int getPlayServicesVersionCode() {
return GMS_VERSION_CODE;
}
@JavascriptInterface
public final String getSimSerial() {
return null;
}
@JavascriptInterface
public final int getSimState() {
return SIM_STATE_UNKNOWN;
}
@JavascriptInterface
public final void goBack() {
Log.d(TAG, "JSBridge: goBack");
}
@JavascriptInterface
public final boolean hasPhoneNumber() {
return false;
}
@JavascriptInterface
public final boolean hasTelephony() {
return false;
}
@JavascriptInterface
public final void hideKeyboard() {
inputMethodManager.hideSoftInputFromWindow(webView.getWindowToken(), 0);
}
@JavascriptInterface
public final boolean isUserOwner() {
return true;
}
@JavascriptInterface
public final void launchEmergencyDialer() {
Log.d(TAG, "JSBridge: launchEmergencyDialer");
}
@JavascriptInterface
public final void log(String s) {
Log.d(TAG, "JSBridge: log");
}
@JavascriptInterface
public final void notifyOnTermsOfServiceAccepted() {
Log.d(TAG, "JSBridge: notifyOnTermsOfServiceAccepted");
}
@JavascriptInterface
public final void sendFido2SkUiEvent(String event) {
Log.d(TAG, "JSBridge: sendFido2SkUiEvent");
fidoHandler.onEvent(event);
}
@JavascriptInterface
public final void setAccountIdentifier(String accountName) {
Log.d(TAG, "JSBridge: setAccountIdentifier");
}
@JavascriptInterface
public void setAllActionsEnabled(boolean z) {
Log.d(TAG, "JSBridge: setAllActionsEnabled");
}
@JavascriptInterface
public final void setBackButtonEnabled(boolean backButtonEnabled) {
int visibility = getWindow().getDecorView().getSystemUiVisibility();
if (backButtonEnabled)
visibility &= -STATUS_BAR_DISABLE_BACK;
else
visibility |= STATUS_BAR_DISABLE_BACK;
getWindow().getDecorView().setSystemUiVisibility(visibility);
}
@JavascriptInterface
public final void setNewAccountCreated() {
Log.d(TAG, "JSBridge: setNewAccountCreated");
}
@JavascriptInterface
public void setPrimaryActionEnabled(boolean z) {
Log.d(TAG, "JSBridge: setPrimaryActionEnabled");
}
@JavascriptInterface
public void setPrimaryActionLabel(String str, int i) {
Log.d(TAG, "JSBridge: setPrimaryActionLabel: " + str);
}
@JavascriptInterface
public void setSecondaryActionEnabled(boolean z) {
Log.d(TAG, "JSBridge: setSecondaryActionEnabled");
}
@JavascriptInterface
public void setSecondaryActionLabel(String str, int i) {
Log.d(TAG, "JSBridge: setSecondaryActionLabel: " + str);
}
@JavascriptInterface
public final void showKeyboard() {
inputMethodManager.showSoftInput(webView, SHOW_IMPLICIT);
}
@JavascriptInterface
public final void showView() {
runOnUiThread(() -> webView.setVisibility(VISIBLE));
}
@JavascriptInterface
public final void skipLogin() {
Log.d(TAG, "JSBridge: skipLogin");
loginCanceled();
}
@JavascriptInterface
public final void startAfw() {
Log.d(TAG, "JSBridge: startAfw");
}
@JavascriptInterface
public final void startFido2SignRequest(String request) {
Log.d(TAG, "JSBridge: startFido2SignRequest");
fidoHandler.startSignRequest(request);
}
}
}

View file

@ -0,0 +1,199 @@
/*
* 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.gms.auth.loginservice;
import android.accounts.AbstractAccountAuthenticator;
import android.accounts.Account;
import android.accounts.AccountAuthenticatorResponse;
import android.accounts.AccountManager;
import android.accounts.NetworkErrorException;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import com.google.android.gms.common.internal.CertData;
import org.microg.gms.auth.*;
import org.microg.gms.auth.login.LoginActivity;
import org.microg.gms.common.PackageUtils;
import org.microg.gms.auth.AuthResponse;
import org.microg.gms.utils.PackageManagerUtilsKt;
import java.util.Arrays;
import java.util.List;
import static android.accounts.AccountManager.*;
public class AccountAuthenticator extends AbstractAccountAuthenticator {
private static final String TAG = "GmsAuthenticator";
public static final String KEY_OVERRIDE_PACKAGE = "overridePackage";
public static final String KEY_OVERRIDE_CERTIFICATE = "overrideCertificate";
private final Context context;
private final String accountType;
public AccountAuthenticator(Context context) {
super(context);
this.context = context;
this.accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
}
@Override
public Bundle editProperties(AccountAuthenticatorResponse response, String accountType) {
Log.d(TAG, "editProperties: " + accountType);
return null;
}
@Override
public Bundle addAccount(AccountAuthenticatorResponse response, String accountType, String authTokenType, String[] requiredFeatures, Bundle options) throws NetworkErrorException {
if (accountType.equals(this.accountType)) {
final Intent i = new Intent(context, LoginActivity.class);
i.putExtras(options);
i.putExtra(LoginActivity.EXTRA_TMPL, LoginActivity.TMPL_NEW_ACCOUNT);
i.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
final Bundle result = new Bundle();
result.putParcelable(KEY_INTENT, i);
return result;
}
return null;
}
@Override
public Bundle confirmCredentials(AccountAuthenticatorResponse response, Account account, Bundle options) throws NetworkErrorException {
Log.d(TAG, "confirmCredentials: " + account + ", " + options);
return null;
}
public boolean isPackageOverrideAllowed(Account account, String requestingPackage, String overridePackage, CertData overrideCertificate) {
// Always allow for self package
if (requestingPackage.equals(context.getPackageName())) return true;
// if (requestingPackage.equals("org.microg.example.authwithoverride")) return true;
String requestingDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(PackageManagerUtilsKt.getCertificates(context.getPackageManager(), requestingPackage).get(0), "SHA-256"), "");
String overrideCertificateDigestString = PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCertificate, "SHA-256"), "");
String overrideUserDataKey = "override." + requestingPackage + ":" + requestingDigestString + ":" + overridePackage + ":" + overrideCertificateDigestString;
String hasOverride = AccountManager.get(context).getUserData(account, overrideUserDataKey);
return "1".equals(hasOverride);
}
@Override
public Bundle getAuthToken(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
options.keySet();
Log.d(TAG, "getAuthToken: " + account + ", " + authTokenType + ", " + options);
String app = options.getString(KEY_ANDROID_PACKAGE_NAME);
app = PackageUtils.getAndCheckPackage(context, app, options.getInt(KEY_CALLER_UID), options.getInt(KEY_CALLER_PID));
AuthManager authManager;
if (app == null) {
Bundle result = new Bundle();
result.putInt(KEY_ERROR_CODE, ERROR_CODE_BAD_REQUEST);
return result;
}
if (options.containsKey(KEY_OVERRIDE_PACKAGE) || options.containsKey(KEY_OVERRIDE_CERTIFICATE)) {
String overridePackage = options.getString(KEY_OVERRIDE_PACKAGE, app);
byte[] overrideCertificateBytes = options.getByteArray(KEY_OVERRIDE_CERTIFICATE);
CertData overrideCert;
if (overrideCertificateBytes != null) {
overrideCert = new CertData(overrideCertificateBytes);
} else {
overrideCert = PackageManagerUtilsKt.getCertificates(context.getPackageManager(), app).get(0);
}
if (isPackageOverrideAllowed(account, app, overridePackage, overrideCert)) {
authManager = new AuthManager(context, account.name, overridePackage, authTokenType);
authManager.setPackageSignature(PackageManagerUtilsKt.toHexString(PackageManagerUtilsKt.digest(overrideCert, "SHA1"), ""));
} else {
Bundle result = new Bundle();
Intent i = new Intent(context, AskPackageOverrideActivity.class);
i.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
i.putExtra(KEY_ANDROID_PACKAGE_NAME, app);
i.putExtra(KEY_ACCOUNT_TYPE, account.type);
i.putExtra(KEY_ACCOUNT_NAME, account.name);
i.putExtra(KEY_OVERRIDE_PACKAGE, overridePackage);
i.putExtra(KEY_OVERRIDE_CERTIFICATE, overrideCert.getBytes());
result.putParcelable(KEY_INTENT, i);
return result;
}
} else {
authManager = new AuthManager(context, account.name, app, authTokenType);
}
try {
AuthResponse res = authManager.requestAuthWithBackgroundResolution(true);
if (res.auth != null) {
Log.d(TAG, "getAuthToken: " + res.auth);
Bundle result = new Bundle();
result.putString(KEY_ACCOUNT_TYPE, account.type);
result.putString(KEY_ACCOUNT_NAME, account.name);
result.putString(KEY_AUTHTOKEN, res.auth);
return result;
} else {
Bundle result = new Bundle();
Intent i = new Intent(context, AskPermissionActivity.class);
i.putExtras(options);
i.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
i.putExtra(KEY_ANDROID_PACKAGE_NAME, app);
i.putExtra(KEY_ACCOUNT_TYPE, account.type);
i.putExtra(KEY_ACCOUNT_NAME, account.name);
i.putExtra(KEY_AUTHTOKEN, authTokenType);
try {
if (res.consentDataBase64 != null)
i.putExtra(AskPermissionActivity.EXTRA_CONSENT_DATA, Base64.decode(res.consentDataBase64, Base64.URL_SAFE));
} catch (Exception e) {
Log.w(TAG, "Can't decode consent data: ", e);
}
result.putParcelable(KEY_INTENT, i);
return result;
}
} catch (Exception e) {
Log.w(TAG, e);
return null;
}
}
@Override
public String getAuthTokenLabel(String authTokenType) {
Log.d(TAG, "getAuthTokenLabel: " + authTokenType);
return null;
}
@Override
public Bundle updateCredentials(AccountAuthenticatorResponse response, Account account, String authTokenType, Bundle options) throws NetworkErrorException {
Log.d(TAG, "updateCredentials: " + account + ", " + authTokenType + ", " + options);
return null;
}
@Override
public Bundle hasFeatures(AccountAuthenticatorResponse response, Account account, String[] features) throws NetworkErrorException {
AccountManager accountManager = AccountManager.get(context);
String services = accountManager.getUserData(account, "services");
boolean res = true;
if (services != null) {
List<String> servicesList = Arrays.asList(services.split(","));
for (String feature : features) {
if (feature.startsWith("service_") && !servicesList.contains(feature.substring(8))) {
Log.d(TAG, "Feature " + feature + " not supported");
res = false;
} else if (!feature.startsWith("service_") && !servicesList.contains(feature)) {
Log.d(TAG, "Feature " + feature + " not supported");
res = false;
}
}
} else {
res = false;
}
Bundle result = new Bundle();
result.putBoolean(KEY_BOOLEAN_RESULT, res);
return result;
}
}

View file

@ -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.gms.auth.loginservice;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import static android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT;
public class GoogleLoginService extends Service {
private AccountAuthenticator authenticator;
@Override
public void onCreate() {
super.onCreate();
authenticator = new AccountAuthenticator(this);
}
@Override
public IBinder onBind(Intent intent) {
if (intent.getAction().equals(ACTION_AUTHENTICATOR_INTENT)) {
return authenticator.getIBinder();
}
return null;
}
}

View file

@ -0,0 +1,34 @@
/*
* 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.gms.car;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;
import org.microg.gms.BaseService;
import org.microg.gms.common.GmsService;
public class CarService extends BaseService {
public CarService() {
super("GmsCarSvc", GmsService.CAR);
}
@Override
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) {
// TODO
}
}

View file

@ -0,0 +1,158 @@
/*
* 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.gms.checkin;
import android.content.Context;
import android.util.Log;
import org.microg.gms.common.DeviceConfigProtoKt;
import org.microg.gms.common.DeviceConfiguration;
import org.microg.gms.common.DeviceIdentifier;
import org.microg.gms.common.PhoneInfo;
import org.microg.gms.common.Utils;
import org.microg.gms.profile.Build;
import org.microg.gms.profile.ProfileManager;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Random;
import java.util.TimeZone;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class CheckinClient {
private static final String TAG = "GmsCheckinClient";
private static final Object TODO = null; // TODO
private static final List<String> TODO_LIST_STRING = new ArrayList<>(); // TODO
private static final List<CheckinRequest.Checkin.Statistic> TODO_LIST_CHECKIN = new ArrayList<>(); // TODO
private static final String SERVICE_URL = "https://android.clients.google.com/checkin";
public static CheckinResponse request(CheckinRequest request) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(SERVICE_URL).openConnection();
connection.setRequestMethod("POST");
connection.setDoInput(true);
connection.setDoOutput(true);
connection.setRequestProperty("Content-type", "application/x-protobuffer");
connection.setRequestProperty("Content-Encoding", "gzip");
connection.setRequestProperty("Accept-Encoding", "gzip");
connection.setRequestProperty("User-Agent", "Android-Checkin/2.0 (vbox86p JLS36G); gzip");
Log.d(TAG, "-- Request --\n" + request);
OutputStream os = new GZIPOutputStream(connection.getOutputStream());
os.write(request.encode());
os.close();
if (connection.getResponseCode() != 200) {
try {
throw new IOException(new String(Utils.readStreamToEnd(new GZIPInputStream(connection.getErrorStream()))));
} catch (Exception e) {
throw new IOException(connection.getResponseMessage(), e);
}
}
InputStream is = connection.getInputStream();
CheckinResponse response = CheckinResponse.ADAPTER.decode(new GZIPInputStream(is));
is.close();
return response;
}
public static CheckinRequest makeRequest(Context context, DeviceConfiguration deviceConfiguration,
DeviceIdentifier deviceIdent, PhoneInfo phoneInfo,
LastCheckinInfo checkinInfo, Locale locale,
List<Account> accounts) {
ProfileManager.ensureInitialized(context);
CheckinRequest.Builder builder = new CheckinRequest.Builder()
.accountCookie(new ArrayList<>())
.androidId(checkinInfo.getAndroidId())
.checkin(new CheckinRequest.Checkin.Builder()
.build(new CheckinRequest.Checkin.Build.Builder()
.bootloader(Build.BOOTLOADER)
.brand(Build.BRAND)
.clientId("android-google")
.device(Build.DEVICE)
.fingerprint(Build.FINGERPRINT)
.hardware(Build.HARDWARE)
.manufacturer(Build.MANUFACTURER)
.model(Build.MODEL)
.otaInstalled(false) // TODO?
//.packageVersionCode(Constants.MAX_REFERENCE_VERSION)
.product(Build.PRODUCT)
.radio(Build.RADIO)
.sdkVersion(Build.VERSION.SDK_INT)
.time(Build.TIME / 1000)
.build())
.cellOperator(phoneInfo.cellOperator)
.event(Collections.singletonList(new CheckinRequest.Checkin.Event.Builder()
.tag(checkinInfo.getAndroidId() == 0 ? "event_log_start" : "system_update")
.value_(checkinInfo.getAndroidId() == 0 ? null : "1536,0,-1,NULL")
.timeMs(new Date().getTime())
.build()))
.lastCheckinMs(checkinInfo.getLastCheckin())
.requestedGroup(TODO_LIST_STRING)
.roaming(phoneInfo.roaming)
.simOperator(phoneInfo.simOperator)
.stat(TODO_LIST_CHECKIN)
.userNumber(0)
.build())
.deviceConfiguration(DeviceConfigProtoKt.asProto(deviceConfiguration))
.digest(checkinInfo.getDigest())
.esn(deviceIdent.esn)
.fragment(0)
.locale(locale.toString())
.loggingId(new Random().nextLong()) // TODO: static
.meid(deviceIdent.meid)
.otaCert(Collections.singletonList("71Q6Rn2DDZl1zPDVaaeEHItd"))
.serial(Build.SERIAL != null && !Build.SERIAL.isEmpty() ? Build.SERIAL : null)
.timeZone(TimeZone.getDefault().getID())
.userName((String) TODO)
.userSerialNumber((Integer) TODO)
.version(3);
for (Account account : accounts) {
builder.accountCookie.add("[" + account.name + "]");
builder.accountCookie.add(account.authToken);
}
if (builder.accountCookie.isEmpty()) builder.accountCookie.add("");
if (deviceIdent.wifiMac != null) {
builder.macAddress(Arrays.asList(deviceIdent.wifiMac))
.macAddressType(Arrays.asList("wifi"));
}
if (checkinInfo.getSecurityToken() != 0) {
builder.securityToken(checkinInfo.getSecurityToken())
.fragment(1);
}
return builder.build();
}
public static class Account {
public final String name;
public final String authToken;
public Account(String accountName, String authToken) {
this.name = accountName;
this.authToken = authToken;
}
}
}

View file

@ -0,0 +1,75 @@
/*
* 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.gms.checkin;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import org.microg.gms.auth.AuthConstants;
import org.microg.gms.auth.AuthRequest;
import org.microg.gms.common.Constants;
import org.microg.gms.common.DeviceConfiguration;
import org.microg.gms.common.Utils;
import org.microg.gms.gservices.GServices;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
public class CheckinManager {
private static final long MIN_CHECKIN_INTERVAL = 3 * 60 * 60 * 1000; // 3 hours
@SuppressWarnings("MissingPermission")
public static synchronized LastCheckinInfo checkin(Context context, boolean force) throws IOException {
LastCheckinInfo info = LastCheckinInfo.read(context);
if (!force && info.getLastCheckin() > System.currentTimeMillis() - MIN_CHECKIN_INTERVAL)
return null;
if (!CheckinPreferences.isEnabled(context))
return null;
List<CheckinClient.Account> accounts = new ArrayList<CheckinClient.Account>();
AccountManager accountManager = AccountManager.get(context);
String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
for (Account account : accountManager.getAccountsByType(accountType)) {
String token = new AuthRequest()
.email(account.name).token(accountManager.getPassword(account))
.hasPermission(true).service("ac2dm")
.app("com.google.android.gsf", Constants.GMS_PACKAGE_SIGNATURE_SHA1)
.getResponse().LSid;
if (token != null) {
accounts.add(new CheckinClient.Account(account.name, token));
}
}
CheckinRequest request = CheckinClient.makeRequest(context,
new DeviceConfiguration(context), Utils.getDeviceIdentifier(context),
Utils.getPhoneInfo(context), info, Utils.getLocale(context), accounts);
return handleResponse(context, CheckinClient.request(request));
}
private static LastCheckinInfo handleResponse(Context context, CheckinResponse response) {
LastCheckinInfo info = new LastCheckinInfo(response);
info.write(context);
ContentResolver resolver = context.getContentResolver();
for (CheckinResponse.GservicesSetting setting : response.setting) {
GServices.setString(resolver, setting.name.utf8(), setting.value_.utf8());
}
return info;
}
}

View file

@ -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.gms.checkin;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.util.Log;
import androidx.core.app.PendingIntentCompat;
import androidx.legacy.content.WakefulBroadcastReceiver;
import com.google.android.gms.checkin.internal.ICheckinService;
import org.microg.gms.auth.AuthConstants;
import org.microg.gms.common.ForegroundServiceInfo;
import org.microg.gms.common.ForegroundServiceContext;
import org.microg.gms.gcm.McsService;
import org.microg.gms.people.PeopleManager;
@ForegroundServiceInfo(value = "Google device registration", resName = "service_name_checkin", resPackage = "com.google.android.gms")
public class CheckinService extends IntentService {
private static final String TAG = "GmsCheckinSvc";
public static final long MAX_VALID_CHECKIN_AGE = 24 * 60 * 60 * 1000; // 12 hours
public static final long REGULAR_CHECKIN_INTERVAL = 12 * 60 * 60 * 1000; // 12 hours
public static final long BACKUP_CHECKIN_DELAY = 3 * 60 * 60 * 1000; // 3 hours
public static final String BIND_ACTION = "com.google.android.gms.checkin.BIND_TO_SERVICE";
public static final String EXTRA_FORCE_CHECKIN = "force";
@Deprecated
public static final String EXTRA_CALLBACK_INTENT = "callback";
public static final String EXTRA_RESULT_RECEIVER = "receiver";
public static final String EXTRA_NEW_CHECKIN_TIME = "checkin_time";
private ICheckinService iface = new ICheckinService.Stub() {
@Override
public String getDeviceDataVersionInfo() throws RemoteException {
return LastCheckinInfo.read(CheckinService.this).getDeviceDataVersionInfo();
}
@Override
public long getLastCheckinSuccessTime() throws RemoteException {
return LastCheckinInfo.read(CheckinService.this).getLastCheckin();
}
@Override
public String getLastSimOperator() throws RemoteException {
return null;
}
};
public CheckinService() {
super(TAG);
}
@SuppressWarnings("MissingPermission")
@Override
protected void onHandleIntent(Intent intent) {
try {
ForegroundServiceContext.completeForegroundService(this, intent, TAG);
if (CheckinPreferences.isEnabled(this)) {
LastCheckinInfo info = CheckinManager.checkin(this, intent.getBooleanExtra(EXTRA_FORCE_CHECKIN, false));
if (info != null) {
Log.d(TAG, "Checked in as " + Long.toHexString(info.getAndroidId()));
String accountType = AuthConstants.DEFAULT_ACCOUNT_TYPE;
for (Account account : AccountManager.get(this).getAccountsByType(accountType)) {
PeopleManager.loadUserInfo(this, account);
}
McsService.scheduleReconnect(this);
if (intent.hasExtra(EXTRA_CALLBACK_INTENT)) {
startService((Intent) intent.getParcelableExtra(EXTRA_CALLBACK_INTENT));
}
if (intent.hasExtra(EXTRA_RESULT_RECEIVER)) {
ResultReceiver receiver = intent.getParcelableExtra(EXTRA_RESULT_RECEIVER);
if (receiver != null) {
Bundle bundle = new Bundle();
bundle.putLong(EXTRA_NEW_CHECKIN_TIME, info.getLastCheckin());
receiver.send(Activity.RESULT_OK, bundle);
}
}
}
}
} catch (Exception e) {
Log.w(TAG, e);
} finally {
if (intent != null) {
WakefulBroadcastReceiver.completeWakefulIntent(intent);
}
schedule(this);
stopSelf();
}
}
@Override
public IBinder onBind(Intent intent) {
if (BIND_ACTION.equals(intent.getAction())) {
return iface.asBinder();
} else {
return super.onBind(intent);
}
}
static void schedule(Context context) {
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
PendingIntent pendingIntent = PendingIntentCompat.getService(context, TriggerReceiver.class.getName().hashCode(), new Intent(context, TriggerReceiver.class), PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT, false);
alarmManager.set(AlarmManager.RTC, Math.max(LastCheckinInfo.read(context).getLastCheckin() + REGULAR_CHECKIN_INTERVAL, System.currentTimeMillis() + BACKUP_CHECKIN_DELAY), pendingIntent);
}
}

View file

@ -0,0 +1,75 @@
/*
* 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.gms.checkin;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.util.Log;
import androidx.core.app.PendingIntentCompat;
import androidx.legacy.content.WakefulBroadcastReceiver;
import org.microg.gms.common.ForegroundServiceContext;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
import static android.os.Build.VERSION.SDK_INT;
import static org.microg.gms.checkin.CheckinService.EXTRA_FORCE_CHECKIN;
import static org.microg.gms.checkin.CheckinService.REGULAR_CHECKIN_INTERVAL;
public class TriggerReceiver extends WakefulBroadcastReceiver {
private static final String TAG = "GmsCheckinTrigger";
private static boolean registered = false;
@Override
public void onReceive(Context context, Intent intent) {
try {
boolean force = "android.provider.Telephony.SECRET_CODE".equals(intent.getAction());
if (CheckinPreferences.isEnabled(context) || force) {
if (LastCheckinInfo.read(context).getLastCheckin() > System.currentTimeMillis() - REGULAR_CHECKIN_INTERVAL && !force) {
CheckinService.schedule(context);
return;
}
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected() || force) {
Intent subIntent = new Intent(context, CheckinService.class);
subIntent.putExtra(EXTRA_FORCE_CHECKIN, force);
startWakefulService(new ForegroundServiceContext(context), subIntent);
} else if (SDK_INT >= 23) {
// no network, register a network callback to retry when we have internet
NetworkRequest networkRequest = new NetworkRequest.Builder()
.addCapability(NET_CAPABILITY_INTERNET)
.build();
Intent i = new Intent(context, TriggerReceiver.class);
PendingIntent pendingIntent = PendingIntentCompat.getBroadcast(context, 0, i, FLAG_UPDATE_CURRENT, true);
cm.registerNetworkCallback(networkRequest, pendingIntent);
}
} else {
Log.d(TAG, "Ignoring " + intent + ": checkin is disabled");
}
} catch (Exception e) {
Log.w(TAG, e);
}
}
}

View file

@ -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.gms.drive.api;
import android.os.RemoteException;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;
import org.microg.gms.BaseService;
import org.microg.gms.common.GmsService;
public class DriveApiService extends BaseService {
private DriveServiceImpl impl = new DriveServiceImpl();
public DriveApiService() {
super("GmsDriveApiSvc", GmsService.DRIVE);
}
@Override
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
callback.onPostInitComplete(0, impl.asBinder(), null);
}
}

View file

@ -0,0 +1,318 @@
/*
* 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.gms.drive.api;
import android.content.IntentSender;
import android.os.RemoteException;
import android.util.Log;
import com.google.android.gms.drive.internal.AddEventListenerRequest;
import com.google.android.gms.drive.internal.AddPermissionRequest;
import com.google.android.gms.drive.internal.AuthorizeAccessRequest;
import com.google.android.gms.drive.internal.CancelPendingActionsRequest;
import com.google.android.gms.drive.internal.ChangeResourceParentsRequest;
import com.google.android.gms.drive.internal.CheckResourceIdsExistRequest;
import com.google.android.gms.drive.internal.CloseContentsAndUpdateMetadataRequest;
import com.google.android.gms.drive.internal.CloseContentsRequest;
import com.google.android.gms.drive.internal.ControlProgressRequest;
import com.google.android.gms.drive.internal.CreateContentsRequest;
import com.google.android.gms.drive.internal.CreateFileIntentSenderRequest;
import com.google.android.gms.drive.internal.CreateFileRequest;
import com.google.android.gms.drive.internal.CreateFolderRequest;
import com.google.android.gms.drive.internal.DeleteResourceRequest;
import com.google.android.gms.drive.internal.DisconnectRequest;
import com.google.android.gms.drive.internal.DriveServiceResponse;
import com.google.android.gms.drive.internal.FetchThumbnailRequest;
import com.google.android.gms.drive.internal.GetChangesRequest;
import com.google.android.gms.drive.internal.GetDriveIdFromUniqueIdRequest;
import com.google.android.gms.drive.internal.GetMetadataRequest;
import com.google.android.gms.drive.internal.GetPermissionsRequest;
import com.google.android.gms.drive.internal.IDriveService;
import com.google.android.gms.drive.internal.IDriveServiceCallbacks;
import com.google.android.gms.drive.internal.IEventCallback;
import com.google.android.gms.drive.internal.ListParentsRequest;
import com.google.android.gms.drive.internal.LoadRealtimeRequest;
import com.google.android.gms.drive.internal.OpenContentsRequest;
import com.google.android.gms.drive.internal.OpenFileIntentSenderRequest;
import com.google.android.gms.drive.internal.RealtimeDocumentSyncRequest;
import com.google.android.gms.drive.internal.RemoveEventListenerRequest;
import com.google.android.gms.drive.internal.RemovePermissionRequest;
import com.google.android.gms.drive.internal.SetDrivePreferencesRequest;
import com.google.android.gms.drive.internal.SetFileUploadPreferencesRequest;
import com.google.android.gms.drive.internal.SetResourceParentsRequest;
import com.google.android.gms.drive.internal.StreamContentsRequest;
import com.google.android.gms.drive.internal.TrashResourceRequest;
import com.google.android.gms.drive.internal.UnsubscribeResourceRequest;
import com.google.android.gms.drive.internal.UntrashResourceRequest;
import com.google.android.gms.drive.internal.UpdateMetadataRequest;
import com.google.android.gms.drive.internal.UpdatePermissionRequest;
public class DriveServiceImpl extends IDriveService.Stub {
private static final String TAG = "GmsDriveSvcImpl";
@Override
public void getMetadata(GetMetadataRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getMetadata");
}
@Override
public void updateMetadata(UpdateMetadataRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: updateMetadata");
}
@Override
public void createContents(CreateContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: createContents");
}
@Override
public void createFile(CreateFileRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: createFile");
}
@Override
public void createFolder(CreateFolderRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: createFolder");
}
@Override
public DriveServiceResponse openContents(OpenContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: openContents");
return null;
}
@Override
public void closeContents(CloseContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: closeContents");
}
@Override
public void requestSync(IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: requestSync");
}
@Override
public IntentSender openFileIntentSender(OpenFileIntentSenderRequest request) throws RemoteException {
Log.d(TAG, "unimplemented Method: openFileIntentSender");
return null;
}
@Override
public IntentSender createFileIntentSender(CreateFileIntentSenderRequest request) throws RemoteException {
Log.d(TAG, "unimplemented Method: createFileIntentSender");
return null;
}
@Override
public void authorizeAccess(AuthorizeAccessRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: authorizeAccess");
}
@Override
public void listParents(ListParentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: listParents");
}
@Override
public void addEventListener(AddEventListenerRequest request, IEventCallback callback, String unused, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: addEventListener");
}
@Override
public void removeEventListener(RemoveEventListenerRequest request, IEventCallback callback, String unused, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: removeEventListener");
}
@Override
public void disconnect(DisconnectRequest request) throws RemoteException {
Log.d(TAG, "unimplemented Method: disconnect");
}
@Override
public void trashResource(TrashResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: trashResource");
}
@Override
public void closeContentsAndUpdateMetadata(CloseContentsAndUpdateMetadataRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: closeContentsAndUpdateMetadata");
}
@Override
public void deleteResource(DeleteResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: deleteResource");
}
@Override
public void loadRealtime(LoadRealtimeRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: loadRealtime");
}
@Override
public void setResourceParents(SetResourceParentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: setResourceParents");
}
@Override
public void getDriveIdFromUniqueId(GetDriveIdFromUniqueIdRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getDriveIdFromUniqueId");
}
@Override
public void checkResourceIdsExist(CheckResourceIdsExistRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: checkResourceIdsExist");
}
@Override
public void completePendingAction(IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: completePendingAction");
}
@Override
public void getDrivePreferences(IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getDrivePreferences");
}
@Override
public void setDrivePreferences(SetDrivePreferencesRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: setDrivePreferences");
}
@Override
public void realtimeDocumentSync(RealtimeDocumentSyncRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: realtimeDocumentSync");
}
@Override
public void getDeviceUsagePreferences(IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getDeviceUsagePreferences");
}
@Override
public void setFileUploadPreferences(SetFileUploadPreferencesRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: setFileUploadPreferences");
}
@Override
public void cancelPendingActions(CancelPendingActionsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: cancelPendingActions");
}
@Override
public void untrashResource(UntrashResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: untrashResource");
}
@Override
public void isAutoBackupEnabled(IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: isAutoBackupEnabled");
}
@Override
public void fetchThumbnail(FetchThumbnailRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: fetchThumbnail");
}
@Override
public void getChanges(GetChangesRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getChanges");
}
@Override
public void unsubscribeResource(UnsubscribeResourceRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: unsubscribeResource");
}
@Override
public void getPermissions(GetPermissionsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: getPermissions");
}
@Override
public void addPermission(AddPermissionRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: addPermission");
}
@Override
public void updatePermission(UpdatePermissionRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: updatePermission");
}
@Override
public void removePermission(RemovePermissionRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: removePermission");
}
@Override
public void removeQueryResultListener(IEventCallback callback, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: removeQueryResultListener");
}
@Override
public void controlProgress(ControlProgressRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: controlProgress");
}
@Override
public void changeResourceParents(ChangeResourceParentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: changeResourceParents");
}
@Override
public DriveServiceResponse streamContents(StreamContentsRequest request, IDriveServiceCallbacks callbacks) throws RemoteException {
Log.d(TAG, "unimplemented Method: streamContents");
return null;
}
}

View file

@ -0,0 +1,56 @@
/*
* 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.gms.feeds;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
public class SubscribedFeedsProvider extends ContentProvider{
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return new MatrixCursor(new String[0]);
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
}

View file

@ -0,0 +1,59 @@
/*
* 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.gms.games;
import android.app.PendingIntent;
import android.content.Intent;
import android.os.Bundle;
import android.os.RemoteException;
import androidx.core.app.PendingIntentCompat;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.internal.GetServiceRequest;
import com.google.android.gms.common.internal.IGmsCallbacks;
import org.microg.gms.BaseService;
import org.microg.gms.common.GmsService;
import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
import static org.microg.gms.common.Constants.GMS_PACKAGE_NAME;
import static org.microg.gms.games.UpgradeActivity.ACTION_PLAY_GAMES_UPGRADE;
import static org.microg.gms.games.UpgradeActivity.EXTRA_GAME_PACACKE_NAME;
public class GamesStubService extends BaseService {
public static final String PARAM_GAME_PACKAGE_NAME = "com.google.android.gms.games.key.gamePackageName";
public GamesStubService() {
super("GmsGamesSvc", GmsService.GAMES);
}
@Override
public void handleServiceRequest(IGmsCallbacks callback, GetServiceRequest request, GmsService service) throws RemoteException {
String packageName = null;
if (request.extras != null) {
packageName = request.extras.getString(PARAM_GAME_PACKAGE_NAME);
}
if (packageName == null) packageName = GMS_PACKAGE_NAME;
Intent intent = new Intent(ACTION_PLAY_GAMES_UPGRADE);
intent.setPackage(GMS_PACKAGE_NAME);
intent.putExtra(EXTRA_GAME_PACACKE_NAME, packageName);
Bundle bundle = new Bundle();
bundle.putParcelable("pendingIntent", PendingIntentCompat.getActivity(this, packageName.hashCode(), intent, FLAG_UPDATE_CURRENT, false));
callback.onPostInitComplete(CommonStatusCodes.RESOLUTION_REQUIRED, null, bundle);
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.gms.games;
import android.app.Activity;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.android.gms.R;
public class UpgradeActivity extends Activity {
public static final String ACTION_PLAY_GAMES_UPGRADE = "com.google.android.gms.games.PLAY_GAMES_UPGRADE";
public static final String EXTRA_GAME_PACACKE_NAME = "com.google.android.gms.games.GAME_PACKAGE_NAME";
private static final String TAG = "GmsUpgActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.games_info);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.copyFrom(getWindow().getAttributes());
lp.width = WindowManager.LayoutParams.MATCH_PARENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
getWindow().setAttributes(lp);
String packageName = getIntent().getStringExtra(EXTRA_GAME_PACACKE_NAME);
// receive package info
PackageManager packageManager = getPackageManager();
ApplicationInfo applicationInfo;
try {
applicationInfo = packageManager.getApplicationInfo(packageName, 0);
} catch (PackageManager.NameNotFoundException e) {
Log.w(TAG, e);
finish();
return;
}
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
((ImageView) findViewById(R.id.app_icon)).setImageDrawable(appIcon);
((TextView) findViewById(R.id.title)).setText(getString(R.string.games_info_title, appLabel));
findViewById(android.R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
}
}

Some files were not shown because too many files have changed in this diff Show more