From f34d87bcc2bb44de1a8efd29b40c311dbea67049 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 20 Jan 2016 15:08:34 +0100 Subject: [PATCH 01/39] Initial commit --- .gitignore | 4 + Android.mk | 7 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 49896 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 +++++++++ microg-ui-tools/build.gradle | 52 +++ microg-ui-tools/src/main/AndroidManifest.xml | 24 ++ .../v4/preference/PreferenceFragment.java | 313 ++++++++++++++++++ .../selfcheck/AbstractSelfCheckActivity.java | 65 ++++ .../tools/selfcheck/SelfCheckGroup.java | 33 ++ .../src/main/res/layout/self_check.xml | 37 +++ .../src/main/res/layout/self_check_entry.xml | 46 +++ .../src/main/res/layout/toolbar.xml | 24 ++ .../src/main/res/values/colors.xml | 21 ++ .../src/main/res/values/themes.xml | 26 ++ settings.gradle | 1 + 16 files changed, 823 insertions(+) create mode 100644 .gitignore create mode 100644 Android.mk create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100755 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 microg-ui-tools/build.gradle create mode 100644 microg-ui-tools/src/main/AndroidManifest.xml create mode 100644 microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java create mode 100644 microg-ui-tools/src/main/res/layout/self_check.xml create mode 100644 microg-ui-tools/src/main/res/layout/self_check_entry.xml create mode 100644 microg-ui-tools/src/main/res/layout/toolbar.xml create mode 100644 microg-ui-tools/src/main/res/values/colors.xml create mode 100644 microg-ui-tools/src/main/res/values/themes.xml create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..31ed2bf6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/ +.gradle/ +local.properties +*.iml diff --git a/Android.mk b/Android.mk new file mode 100644 index 00000000..8b660b6b --- /dev/null +++ b/Android.mk @@ -0,0 +1,7 @@ +LOCAL_PATH := $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_MODULE := MicroGUiTools +LOCAL_SRC_FILES := $(call all-java-files-under, microg-ui-tools/src/main/java) + +include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..8c0fb64a8698b08ecc4158d828ca593c4928e9dd GIT binary patch literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z 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 \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle new file mode 100644 index 00000000..4cd39d2e --- /dev/null +++ b/microg-ui-tools/build.gradle @@ -0,0 +1,52 @@ +/* + * 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. + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.0' + } +} +apply plugin: 'com.android.library' + +String getMyVersionName() { + def stdout = new ByteArrayOutputStream() + exec { + commandLine 'git', 'describe', '--tags', '--always' + standardOutput = stdout + } + return stdout.toString().trim() +} + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.2" + + defaultConfig { + versionName getMyVersionName() + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_6 + } +} + +dependencies { + compile 'com.android.support:support-v4:23.1.1' + compile 'com.android.support:appcompat-v7:23.1.1' +} diff --git a/microg-ui-tools/src/main/AndroidManifest.xml b/microg-ui-tools/src/main/AndroidManifest.xml new file mode 100644 index 00000000..958a9998 --- /dev/null +++ b/microg-ui-tools/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java b/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java new file mode 100644 index 00000000..24070962 --- /dev/null +++ b/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java @@ -0,0 +1,313 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.support.v4.preference; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.preference.Preference; +import android.preference.PreferenceManager; +import android.preference.PreferenceScreen; +import android.support.v4.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ListView; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +public abstract class PreferenceFragment extends Fragment { + + private static final int FIRST_REQUEST_CODE = 100; + private static final int MSG_BIND_PREFERENCES = 1; + private static final String PREFERENCES_TAG = "android:preferences"; + private boolean mHavePrefs; + private boolean mInitDone; + private ListView mList; + private PreferenceManager mPreferenceManager; + + private Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + + case MSG_BIND_PREFERENCES: + bindPreferences(); + break; + } + } + }; + + final private Runnable mRequestFocus = new Runnable() { + public void run() { + mList.focusableViewAvailable(mList); + } + }; + + private void bindPreferences() { + PreferenceScreen localPreferenceScreen = getPreferenceScreen(); + if (localPreferenceScreen != null) { + ListView localListView = getListView(); + localPreferenceScreen.bind(localListView); + } + } + + private void ensureList() { + if (mList == null) { + View view = getView(); + if (view == null) { + throw new IllegalStateException("Content view not yet created"); + } + + View listView = view.findViewById(android.R.id.list); + if (!(listView instanceof ListView)) { + throw new RuntimeException( + "Content has view with id attribute 'android.R.id.list' that is not a ListView class"); + } + + mList = (ListView) listView; + if (mList == null) { + throw new RuntimeException( + "Your content must have a ListView whose id attribute is 'android.R.id.list'"); + } + + mHandler.post(mRequestFocus); + } + } + + private void postBindPreferences() { + if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) { + mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); + } + } + + private void requirePreferenceManager() { + if (this.mPreferenceManager == null) { + throw new RuntimeException("This should be called after super.onCreate."); + } + } + + public void addPreferencesFromIntent(Intent intent) { + requirePreferenceManager(); + PreferenceScreen screen = inflateFromIntent(intent, getPreferenceScreen()); + setPreferenceScreen(screen); + } + + public void addPreferencesFromResource(int resId) { + requirePreferenceManager(); + PreferenceScreen screen = inflateFromResource(getActivity(), resId, getPreferenceScreen()); + setPreferenceScreen(screen); + } + + public Preference findPreference(CharSequence key) { + if (mPreferenceManager == null) { + return null; + } + return mPreferenceManager.findPreference(key); + } + + public ListView getListView() { + ensureList(); + return mList; + } + + public PreferenceManager getPreferenceManager() { + return mPreferenceManager; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getListView().setScrollBarStyle(0); + if (mHavePrefs) { + bindPreferences(); + } + mInitDone = true; + if (savedInstanceState != null) { + Bundle localBundle = savedInstanceState.getBundle(PREFERENCES_TAG); + if (localBundle != null) { + PreferenceScreen screen = getPreferenceScreen(); + if (screen != null) { + screen.restoreHierarchyState(localBundle); + } + } + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + dispatchActivityResult(requestCode, resultCode, data); + } + + @Override + public void onCreate(Bundle paramBundle) { + super.onCreate(paramBundle); + mPreferenceManager = createPreferenceManager(); + } + + @Override + public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, + Bundle paramBundle) { + ListView listView = new ListView(paramLayoutInflater.getContext()); + listView.setId(android.R.id.list); + listView.setDrawSelectorOnTop(false); + listView.setPadding(12, 6, 12, 0); + //listView.setSelector(null); + return listView; + } + + @Override + public void onDestroy() { + super.onDestroy(); + dispatchActivityDestroy(); + } + + @Override + public void onDestroyView() { + mList = null; + mHandler.removeCallbacks(mRequestFocus); + mHandler.removeMessages(MSG_BIND_PREFERENCES); + super.onDestroyView(); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + super.onSaveInstanceState(bundle); + PreferenceScreen screen = getPreferenceScreen(); + if (screen != null) { + Bundle localBundle = new Bundle(); + screen.saveHierarchyState(localBundle); + bundle.putBundle(PREFERENCES_TAG, localBundle); + } + } + + @Override + public void onStop() { + super.onStop(); + dispatchActivityStop(); + } + + /** + * Access methods with visibility private * + */ + + private PreferenceManager createPreferenceManager() { + try { + Constructor c = PreferenceManager.class + .getDeclaredConstructor(Activity.class, int.class); + c.setAccessible(true); + return c.newInstance(this.getActivity(), FIRST_REQUEST_CODE); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private PreferenceScreen getPreferenceScreen() { + try { + Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen"); + m.setAccessible(true); + return (PreferenceScreen) m.invoke(mPreferenceManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void setPreferenceScreen(PreferenceScreen preferenceScreen) { + try { + Method m = PreferenceManager.class + .getDeclaredMethod("setPreferences", PreferenceScreen.class); + m.setAccessible(true); + boolean result = (Boolean) m.invoke(mPreferenceManager, preferenceScreen); + if (result && preferenceScreen != null) { + mHavePrefs = true; + if (mInitDone) { + postBindPreferences(); + } + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void dispatchActivityResult(int requestCode, int resultCode, Intent data) { + try { + Method m = PreferenceManager.class + .getDeclaredMethod("dispatchActivityResult", int.class, int.class, + Intent.class); + m.setAccessible(true); + m.invoke(mPreferenceManager, requestCode, resultCode, data); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void dispatchActivityDestroy() { + try { + Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy"); + m.setAccessible(true); + m.invoke(mPreferenceManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void dispatchActivityStop() { + try { + Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop"); + m.setAccessible(true); + m.invoke(mPreferenceManager); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public PreferenceScreen inflateFromResource(Context context, int resId, + PreferenceScreen rootPreferences) { + PreferenceScreen preferenceScreen; + try { + Method m = PreferenceManager.class + .getDeclaredMethod("inflateFromResource", Context.class, int.class, + PreferenceScreen.class); + m.setAccessible(true); + preferenceScreen = (PreferenceScreen) m + .invoke(mPreferenceManager, context, resId, rootPreferences); + } catch (Exception e) { + throw new RuntimeException(e); + } + return preferenceScreen; + } + + public PreferenceScreen inflateFromIntent(Intent queryIntent, + PreferenceScreen rootPreferences) { + PreferenceScreen preferenceScreen; + try { + Method m = PreferenceManager.class + .getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class); + m.setAccessible(true); + preferenceScreen = (PreferenceScreen) m + .invoke(mPreferenceManager, queryIntent, rootPreferences); + } catch (Exception e) { + throw new RuntimeException(e); + } + return preferenceScreen; + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java new file mode 100644 index 00000000..885acd36 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java @@ -0,0 +1,65 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.selfcheck; + +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import org.microg.tools.ui.R; + +import java.util.ArrayList; +import java.util.List; + +public abstract class AbstractSelfCheckActivity extends AppCompatActivity implements SelfCheckGroup.ResultCollector { + + protected abstract void prepareSelfCheckList(List checks); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.self_check); + setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); + + List selfCheckGroupList = new ArrayList(); + prepareSelfCheckList(selfCheckGroupList); + + for (SelfCheckGroup group : selfCheckGroupList) { + group.doChecks(this, this); + } + } + + public void addResult(String name, SelfCheckGroup.Result result, String resolution) { + if (result == null) return; + ViewGroup root = (ViewGroup) findViewById(R.id.self_check_root); + View resultEntry = LayoutInflater.from(this).inflate(R.layout.self_check_entry, root, false); + ((TextView) resultEntry.findViewById(R.id.self_check_name)).setText(name); + if (result == SelfCheckGroup.Result.Positive) { + ((ImageView) resultEntry.findViewById(R.id.self_check_result)).setImageResource(android.R.drawable.presence_online); + } else { + ((TextView) resultEntry.findViewById(R.id.self_check_resolution)).setText(resolution); + ((ImageView) resultEntry.findViewById(R.id.self_check_result)) + .setImageResource(result == SelfCheckGroup.Result.Negative ? android.R.drawable.presence_busy : android.R.drawable.presence_invisible); + } + root.addView(resultEntry); + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java new file mode 100644 index 00000000..cc832597 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java @@ -0,0 +1,33 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.selfcheck; + +import android.content.Context; + +public interface SelfCheckGroup { + String getGroupName(Context context); + + void doChecks(Context context, ResultCollector collector); + + interface ResultCollector { + void addResult(String name, Result value, String resolution); + } + + enum Result { + Positive, Negative, Unknown + } +} diff --git a/microg-ui-tools/src/main/res/layout/self_check.xml b/microg-ui-tools/src/main/res/layout/self_check.xml new file mode 100644 index 00000000..b8594924 --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/self_check.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/layout/self_check_entry.xml b/microg-ui-tools/src/main/res/layout/self_check_entry.xml new file mode 100644 index 00000000..b371debb --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/self_check_entry.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/layout/toolbar.xml b/microg-ui-tools/src/main/res/layout/toolbar.xml new file mode 100644 index 00000000..67ac66ef --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/toolbar.xml @@ -0,0 +1,24 @@ + + + diff --git a/microg-ui-tools/src/main/res/values/colors.xml b/microg-ui-tools/src/main/res/values/colors.xml new file mode 100644 index 00000000..a9410025 --- /dev/null +++ b/microg-ui-tools/src/main/res/values/colors.xml @@ -0,0 +1,21 @@ + + + + #ff263238 + #ff21272b + #ff009688 + diff --git a/microg-ui-tools/src/main/res/values/themes.xml b/microg-ui-tools/src/main/res/values/themes.xml new file mode 100644 index 00000000..80726729 --- /dev/null +++ b/microg-ui-tools/src/main/res/values/themes.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..05430179 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +include ':microg-ui-tools' From f0e7d6b1c806effee3f5040e5e8595e128c575e4 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 22 Jan 2016 04:33:10 +0100 Subject: [PATCH 02/39] Update selfcheck, add about fragment --- microg-ui-tools/build.gradle | 2 +- .../selfcheck/AbstractSelfCheckActivity.java | 65 --------- .../tools/ui/AbstractAboutFragment.java | 130 ++++++++++++++++++ .../tools/ui/AbstractSelfCheckFragment.java | 97 +++++++++++++ .../src/main/res/layout/about_root.xml | 67 +++++++++ .../src/main/res/layout/self_check.xml | 29 ++-- .../src/main/res/layout/self_check_entry.xml | 25 ++-- .../src/main/res/layout/self_check_group.xml | 40 ++++++ 8 files changed, 362 insertions(+), 93 deletions(-) delete mode 100644 microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java create mode 100644 microg-ui-tools/src/main/res/layout/about_root.xml create mode 100644 microg-ui-tools/src/main/res/layout/self_check_group.xml diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index 4cd39d2e..167a335e 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -27,7 +27,7 @@ apply plugin: 'com.android.library' String getMyVersionName() { def stdout = new ByteArrayOutputStream() exec { - commandLine 'git', 'describe', '--tags', '--always' + commandLine 'git', 'describe', '--tags', '--always', '--dirty' standardOutput = stdout } return stdout.toString().trim() diff --git a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java deleted file mode 100644 index 885acd36..00000000 --- a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/AbstractSelfCheckActivity.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2013-2016 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.microg.tools.selfcheck; - -import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.microg.tools.ui.R; - -import java.util.ArrayList; -import java.util.List; - -public abstract class AbstractSelfCheckActivity extends AppCompatActivity implements SelfCheckGroup.ResultCollector { - - protected abstract void prepareSelfCheckList(List checks); - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.self_check); - setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); - - List selfCheckGroupList = new ArrayList(); - prepareSelfCheckList(selfCheckGroupList); - - for (SelfCheckGroup group : selfCheckGroupList) { - group.doChecks(this, this); - } - } - - public void addResult(String name, SelfCheckGroup.Result result, String resolution) { - if (result == null) return; - ViewGroup root = (ViewGroup) findViewById(R.id.self_check_root); - View resultEntry = LayoutInflater.from(this).inflate(R.layout.self_check_entry, root, false); - ((TextView) resultEntry.findViewById(R.id.self_check_name)).setText(name); - if (result == SelfCheckGroup.Result.Positive) { - ((ImageView) resultEntry.findViewById(R.id.self_check_result)).setImageResource(android.R.drawable.presence_online); - } else { - ((TextView) resultEntry.findViewById(R.id.self_check_resolution)).setText(resolution); - ((ImageView) resultEntry.findViewById(R.id.self_check_result)) - .setImageResource(result == SelfCheckGroup.Result.Negative ? android.R.drawable.presence_busy : android.R.drawable.presence_invisible); - } - root.addView(resultEntry); - } -} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java new file mode 100644 index 00000000..a149dee1 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -0,0 +1,130 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.*; +import android.support.v4.app.Fragment; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public abstract class AbstractAboutFragment extends Fragment { + + protected abstract void collectLibraries(List libraries); + + protected Drawable getIcon() { + try { + PackageManager pm = getContext().getPackageManager(); + Drawable icon = pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadIcon(pm); + if (icon == null) return getContext().getDrawable(android.R.drawable.ic_dialog_alert); + return icon; + } catch (PackageManager.NameNotFoundException e) { + // Never happens, self package always exists! + throw new RuntimeException(e); + } + } + + protected String getAppName() { + try { + PackageManager pm = getContext().getPackageManager(); + CharSequence label = pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadLabel(pm); + if (TextUtils.isEmpty(label)) return getContext().getPackageName(); + return label.toString(); + } catch (PackageManager.NameNotFoundException e) { + // Never happens, self package always exists! + throw new RuntimeException(e); + } + } + + protected String getLibVersion(String packageName) { + try { + String versionName = (String) Class.forName(packageName + ".BuildConfig").getField("VERSION_NAME").get(null); + if (TextUtils.isEmpty(versionName)) return ""; + return versionName; + } catch (Exception e) { + return ""; + } + } + + @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()); + ((TextView) aboutRoot.findViewById(android.R.id.title)).setText(getAppName()); + ((TextView) aboutRoot.findViewById(R.id.about_version)).setText("Version " + getLibVersion(getContext().getPackageName())); + + List libraries = new ArrayList(); + libraries.add(new Library("org.microg.tools.ui", "microG UI Tools", "Apache License 2.0, Copyright (c) microG Team")); + collectLibraries(libraries); + Collections.sort(libraries); + ((ListView) aboutRoot.findViewById(android.R.id.list)).setAdapter(new LibraryAdapter(getContext(), libraries.toArray(new Library[libraries.size()]))); + + return aboutRoot; + } + + private class LibraryAdapter extends ArrayAdapter { + + public LibraryAdapter(Context context, Library[] libraries) { + super(context, android.R.layout.simple_list_item_2, android.R.id.text1, libraries); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View v = super.getView(position, convertView, parent); + ((TextView) v.findViewById(android.R.id.text1)).setText(getItem(position).name + " " + getLibVersion(getItem(position).packageName)); + ((TextView) v.findViewById(android.R.id.text2)).setText(getItem(position).copyright); + return v; + } + } + + protected static class Library implements Comparable { + private final String packageName; + private final String name; + private final String copyright; + + public Library(String packageName, String name, String copyright) { + this.packageName = packageName; + this.name = name; + this.copyright = copyright; + } + + @Override + public String toString() { + return name + ", " + copyright; + } + + @Override + public int compareTo(Library another) { + return name.toLowerCase().compareTo(another.name.toLowerCase()); + } + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java new file mode 100644 index 00000000..56bf9863 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java @@ -0,0 +1,97 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +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 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.Positive; +import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Unknown; + +public abstract class AbstractSelfCheckFragment extends Fragment { + + protected abstract void prepareSelfCheckList(List checks); + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View scrollRoot = inflater.inflate(R.layout.self_check, container, false); + List selfCheckGroupList = new ArrayList(); + prepareSelfCheckList(selfCheckGroupList); + + ViewGroup root = (ViewGroup) scrollRoot.findViewById(R.id.self_check_root); + 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); + group.doChecks(getContext(), new GroupResultCollector(viewGroup)); + root.addView(groupView); + } + return scrollRoot; + } + + 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) { + 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); + } + } + viewGroup.addView(resultEntry); + } + }); + + } + } +} diff --git a/microg-ui-tools/src/main/res/layout/about_root.xml b/microg-ui-tools/src/main/res/layout/about_root.xml new file mode 100644 index 00000000..1fe0c283 --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/about_root.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/layout/self_check.xml b/microg-ui-tools/src/main/res/layout/self_check.xml index b8594924..47092f97 100644 --- a/microg-ui-tools/src/main/res/layout/self_check.xml +++ b/microg-ui-tools/src/main/res/layout/self_check.xml @@ -15,23 +15,16 @@ ~ limitations under the License. --> - + - + - - - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/layout/self_check_entry.xml b/microg-ui-tools/src/main/res/layout/self_check_entry.xml index b371debb..ec5ad8d2 100644 --- a/microg-ui-tools/src/main/res/layout/self_check_entry.xml +++ b/microg-ui-tools/src/main/res/layout/self_check_entry.xml @@ -16,31 +16,38 @@ --> + android:gravity="center_vertical" + android:paddingBottom="5dp" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:paddingLeft="?attr/listPreferredItemPaddingLeft" + android:paddingRight="?attr/listPreferredItemPaddingRight" + android:paddingStart="?android:attr/listPreferredItemPaddingStart"> + android:textAppearance="?attr/textAppearanceListItem" + android:textColor="?android:textColorPrimary"/> - + android:layout_centerVertical="true" + android:focusable="false"/> + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + android:textColor="?android:textColorSecondary"/> \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/layout/self_check_group.xml b/microg-ui-tools/src/main/res/layout/self_check_group.xml new file mode 100644 index 00000000..62af9c7e --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/self_check_group.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file From b2f08accd9b8b75d9b1b3f7aecbb06d2d12c9658 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 25 Jan 2016 21:03:17 +0100 Subject: [PATCH 03/39] Various fixes, proper strings --- .../tools/ui/AbstractAboutFragment.java | 13 ++++---- .../tools/ui/AbstractSelfCheckFragment.java | 11 ++++++- .../src/main/res/values-sr/strings.xml | 23 ++++++++++++++ .../src/main/res/values/strings.xml | 30 +++++++++++++++++++ 4 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 microg-ui-tools/src/main/res/values-sr/strings.xml create mode 100644 microg-ui-tools/src/main/res/values/strings.xml diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index a149dee1..5e9771aa 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -21,7 +21,6 @@ import android.content.pm.PackageManager; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; -import android.support.v4.*; import android.support.v4.app.Fragment; import android.text.TextUtils; import android.view.LayoutInflater; @@ -57,7 +56,7 @@ public abstract class AbstractAboutFragment extends Fragment { PackageManager pm = getContext().getPackageManager(); CharSequence label = pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadLabel(pm); if (TextUtils.isEmpty(label)) return getContext().getPackageName(); - return label.toString(); + return label.toString().trim(); } catch (PackageManager.NameNotFoundException e) { // Never happens, self package always exists! throw new RuntimeException(e); @@ -68,7 +67,7 @@ public abstract class AbstractAboutFragment extends Fragment { try { String versionName = (String) Class.forName(packageName + ".BuildConfig").getField("VERSION_NAME").get(null); if (TextUtils.isEmpty(versionName)) return ""; - return versionName; + return versionName.trim(); } catch (Exception e) { return ""; } @@ -80,10 +79,10 @@ public abstract class AbstractAboutFragment extends Fragment { View aboutRoot = inflater.inflate(R.layout.about_root, container, false); ((ImageView) aboutRoot.findViewById(android.R.id.icon)).setImageDrawable(getIcon()); ((TextView) aboutRoot.findViewById(android.R.id.title)).setText(getAppName()); - ((TextView) aboutRoot.findViewById(R.id.about_version)).setText("Version " + getLibVersion(getContext().getPackageName())); + ((TextView) aboutRoot.findViewById(R.id.about_version)).setText(getString(R.string.about_version_str, getLibVersion(getContext().getPackageName()))); List libraries = new ArrayList(); - libraries.add(new Library("org.microg.tools.ui", "microG UI Tools", "Apache License 2.0, Copyright (c) microG Team")); + libraries.add(new Library(BuildConfig.APPLICATION_ID, getString(R.string.lib_name), getString(R.string.lib_license))); collectLibraries(libraries); Collections.sort(libraries); ((ListView) aboutRoot.findViewById(android.R.id.list)).setAdapter(new LibraryAdapter(getContext(), libraries.toArray(new Library[libraries.size()]))); @@ -100,8 +99,8 @@ public abstract class AbstractAboutFragment extends Fragment { @Override public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); - ((TextView) v.findViewById(android.R.id.text1)).setText(getItem(position).name + " " + getLibVersion(getItem(position).packageName)); - ((TextView) v.findViewById(android.R.id.text2)).setText(getItem(position).copyright); + ((TextView) v.findViewById(android.R.id.text1)).setText(getString(R.string.about_name_version_str, getItem(position).name, getLibVersion(getItem(position).packageName))); + ((TextView) v.findViewById(android.R.id.text2)).setText(getItem(position).copyright != null ? getItem(position).copyright : getString(R.string.about_default_license)); return v; } } diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java index 56bf9863..bb00cf3f 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java @@ -19,6 +19,7 @@ package org.microg.tools.ui; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -33,10 +34,12 @@ 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"; protected abstract void prepareSelfCheckList(List checks); @@ -52,7 +55,13 @@ public abstract class AbstractSelfCheckFragment extends Fragment { 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); - group.doChecks(getContext(), new GroupResultCollector(viewGroup)); + 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); } return scrollRoot; diff --git a/microg-ui-tools/src/main/res/values-sr/strings.xml b/microg-ui-tools/src/main/res/values-sr/strings.xml new file mode 100644 index 00000000..78d4753a --- /dev/null +++ b/microg-ui-tools/src/main/res/values-sr/strings.xml @@ -0,0 +1,23 @@ + + + + + Поставка + + микроГ самопровера + Провера исправности подешавања система за коришћење микроГ услуга. + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/values/strings.xml b/microg-ui-tools/src/main/res/values/strings.xml new file mode 100644 index 00000000..186ef926 --- /dev/null +++ b/microg-ui-tools/src/main/res/values/strings.xml @@ -0,0 +1,30 @@ + + + + + microG UI Tools + Apache License 2.0, Copyright © microG Team + + Version %1$s + %1$s %2$s + All rights reserved. + + Setup + + Self-Check + Check if the system is correctly set up to use microG. + \ No newline at end of file From d7210d466aec8901f6bd08461134097ce34edad7 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Tue, 26 Jan 2016 23:32:17 +0100 Subject: [PATCH 04/39] Fix layout on small screens / long titles, add ability to define resolver for self-check --- .../tools/selfcheck/SelfCheckGroup.java | 7 +++ .../tools/ui/AbstractSelfCheckFragment.java | 29 +++++++-- .../src/main/res/layout/self_check_entry.xml | 63 ++++++++++--------- 3 files changed, 66 insertions(+), 33 deletions(-) diff --git a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java index cc832597..028e6533 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/SelfCheckGroup.java @@ -17,6 +17,7 @@ package org.microg.tools.selfcheck; import android.content.Context; +import android.support.v4.app.Fragment; public interface SelfCheckGroup { String getGroupName(Context context); @@ -25,6 +26,12 @@ public interface SelfCheckGroup { 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 { diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java index bb00cf3f..1a3f25b3 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java @@ -41,16 +41,24 @@ import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Unknown; public abstract class AbstractSelfCheckFragment extends Fragment { private static final String TAG = "SelfCheck"; - protected abstract void prepareSelfCheckList(List checks); + private ViewGroup root; @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View scrollRoot = inflater.inflate(R.layout.self_check, container, false); + root = (ViewGroup) scrollRoot.findViewById(R.id.self_check_root); + reset(inflater); + return scrollRoot; + } + + protected abstract void prepareSelfCheckList(List checks); + + protected void reset(LayoutInflater inflater) { List selfCheckGroupList = new ArrayList(); prepareSelfCheckList(selfCheckGroupList); - ViewGroup root = (ViewGroup) scrollRoot.findViewById(R.id.self_check_root); + 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())); @@ -64,7 +72,6 @@ public abstract class AbstractSelfCheckFragment extends Fragment { } root.addView(groupView); } - return scrollRoot; } private class GroupResultCollector implements SelfCheckGroup.ResultCollector { @@ -76,6 +83,12 @@ public abstract class AbstractSelfCheckFragment extends Fragment { @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 @@ -96,11 +109,19 @@ public abstract class AbstractSelfCheckFragment extends Fragment { if (result == Unknown) { resultEntry.findViewById(R.id.self_check_result).setVisibility(INVISIBLE); } + if (resolver != null) { + resultEntry.setClickable(true); + resultEntry.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + resolver.tryResolve(AbstractSelfCheckFragment.this); + } + }); + } } viewGroup.addView(resultEntry); } }); - } } } diff --git a/microg-ui-tools/src/main/res/layout/self_check_entry.xml b/microg-ui-tools/src/main/res/layout/self_check_entry.xml index ec5ad8d2..e24a7e1c 100644 --- a/microg-ui-tools/src/main/res/layout/self_check_entry.xml +++ b/microg-ui-tools/src/main/res/layout/self_check_entry.xml @@ -15,39 +15,44 @@ ~ limitations under the License. --> - + - + android:layout_weight="1" + android:orientation="vertical"> + + + + + - - - \ No newline at end of file + android:focusable="false" + android:gravity="right|center_vertical" + android:paddingTop="5dp"/> + \ No newline at end of file From 6186c987124146d2deb14febcf5cf698829a12d8 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Wed, 27 Jan 2016 01:03:00 +0100 Subject: [PATCH 05/39] Remove broken getDrawable() call, add summary line to about --- .../tools/ui/AbstractAboutFragment.java | 20 +++++++++++++++---- .../src/main/res/layout/about_root.xml | 14 +++++++++++-- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index 5e9771aa..42e0c4fc 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -22,6 +22,7 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -42,9 +43,7 @@ public abstract class AbstractAboutFragment extends Fragment { protected Drawable getIcon() { try { PackageManager pm = getContext().getPackageManager(); - Drawable icon = pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadIcon(pm); - if (icon == null) return getContext().getDrawable(android.R.drawable.ic_dialog_alert); - return icon; + return pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadIcon(pm); } catch (PackageManager.NameNotFoundException e) { // Never happens, self package always exists! throw new RuntimeException(e); @@ -73,13 +72,26 @@ public abstract class AbstractAboutFragment extends Fragment { } } + protected String getSelfVersion() { + return getLibVersion(getContext().getPackageName()); + } + + 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()); ((TextView) aboutRoot.findViewById(android.R.id.title)).setText(getAppName()); - ((TextView) aboutRoot.findViewById(R.id.about_version)).setText(getString(R.string.about_version_str, getLibVersion(getContext().getPackageName()))); + ((TextView) aboutRoot.findViewById(R.id.about_version)).setText(getString(R.string.about_version_str, getSelfVersion())); + String summary = getSummary(); + if (summary != null) { + ((TextView) aboutRoot.findViewById(android.R.id.summary)).setText(summary); + aboutRoot.findViewById(android.R.id.summary).setVisibility(View.VISIBLE); + } List libraries = new ArrayList(); libraries.add(new Library(BuildConfig.APPLICATION_ID, getString(R.string.lib_name), getString(R.string.lib_license))); diff --git a/microg-ui-tools/src/main/res/layout/about_root.xml b/microg-ui-tools/src/main/res/layout/about_root.xml index 1fe0c283..2dceaa79 100644 --- a/microg-ui-tools/src/main/res/layout/about_root.xml +++ b/microg-ui-tools/src/main/res/layout/about_root.xml @@ -31,13 +31,23 @@ + + Date: Sat, 6 Feb 2016 20:02:05 +0100 Subject: [PATCH 06/39] Fixed some lint errors --- .../support/v4/preference/PreferenceFragment.java | 2 +- microg-ui-tools/src/main/res/values-sr/strings.xml | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java b/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java index 24070962..8cb10aa6 100644 --- a/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java +++ b/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java @@ -136,7 +136,7 @@ public abstract class PreferenceFragment extends Fragment { @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - getListView().setScrollBarStyle(0); + getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); if (mHavePrefs) { bindPreferences(); } diff --git a/microg-ui-tools/src/main/res/values-sr/strings.xml b/microg-ui-tools/src/main/res/values-sr/strings.xml index 78d4753a..f5578ae1 100644 --- a/microg-ui-tools/src/main/res/values-sr/strings.xml +++ b/microg-ui-tools/src/main/res/values-sr/strings.xml @@ -16,8 +16,15 @@ --> + microG UI Tools + Apache License 2.0, Copyright © microG Team + + Version %1$s + %1$s %2$s + All rights reserved. + Поставка микроГ самопровера Провера исправности подешавања система за коришћење микроГ услуга. - \ No newline at end of file + From 47f885d1ef6173afa6a0b754c1678e95fc8ee89b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20V=C3=A1gner?= Date: Sun, 7 Feb 2016 14:09:50 +0100 Subject: [PATCH 07/39] use disable lint check "MissingTranslation" for now rather than inserting wrong language strings. --- microg-ui-tools/build.gradle | 5 +++++ microg-ui-tools/src/main/res/values-sr/strings.xml | 7 ------- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index 167a335e..0b439ad9 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -44,9 +44,14 @@ android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_6 } + lintOptions { + // TODO: Remove MissingTranslation once we have stable strings and proper translations. + disable 'MissingTranslation' + } } dependencies { compile 'com.android.support:support-v4:23.1.1' compile 'com.android.support:appcompat-v7:23.1.1' } + diff --git a/microg-ui-tools/src/main/res/values-sr/strings.xml b/microg-ui-tools/src/main/res/values-sr/strings.xml index f5578ae1..0d00f669 100644 --- a/microg-ui-tools/src/main/res/values-sr/strings.xml +++ b/microg-ui-tools/src/main/res/values-sr/strings.xml @@ -16,13 +16,6 @@ --> - microG UI Tools - Apache License 2.0, Copyright © microG Team - - Version %1$s - %1$s %2$s - All rights reserved. - Поставка микроГ самопровера From 75cafdbd4b5e6f58885017a15c3b8fc02bc46dfe Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 7 Feb 2016 16:39:25 +0100 Subject: [PATCH 08/39] Add Travis CI support --- .travis.yml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..5cf5f787 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: android +sudo: false +before_script: + - echo sdk.dir $ANDROID_HOME > local.properties +script: + - export TERM=dumb + - export JAVA_OPTS="-XX:MaxPermSize=1024m -XX:+CMSClassUnloadingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx2048m" + - ./gradlew build +android: + components: + - platform-tools + - tools + - build-tools-23.0.2 + - android-23 + - extra-android-m2repository +before_cache: + - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock +cache: + directories: + - $HOME/.gradle/caches/ + - $HOME/.gradle/wrapper/ From 18fb8cb8a189d675f8bfbddcfac3f4396c2aba0a Mon Sep 17 00:00:00 2001 From: Marvin W Date: Mon, 8 Feb 2016 03:02:34 +0100 Subject: [PATCH 09/39] Add README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..38244329 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +microG UI Tools +====== +[![Build Status](https://travis-ci.org/microg/android_external_MicroGUiTools.svg?branch=master)](https://travis-ci.org/microg/android_external_MicroGUiTools) +This repository holds a library providing various UI features used in microG apps (GmsCore and UnifiedNlp). From fd8a7a6272e4d3515f0fadee407f266ddecf770b Mon Sep 17 00:00:00 2001 From: Felix Knecht Date: Thu, 11 Feb 2016 23:11:04 +0800 Subject: [PATCH 10/39] Fixed compile in CM13 --- Android.mk | 2 ++ microg-ui-tools/src/main/res/layout/about_root.xml | 10 +++++----- microg-ui-tools/src/main/res/values/strings.xml | 7 ++++++- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/Android.mk b/Android.mk index 8b660b6b..6f9596ad 100644 --- a/Android.mk +++ b/Android.mk @@ -4,4 +4,6 @@ include $(CLEAR_VARS) LOCAL_MODULE := MicroGUiTools LOCAL_SRC_FILES := $(call all-java-files-under, microg-ui-tools/src/main/java) +LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v7-appcompat + include $(BUILD_STATIC_JAVA_LIBRARY) diff --git a/microg-ui-tools/src/main/res/layout/about_root.xml b/microg-ui-tools/src/main/res/layout/about_root.xml index 2dceaa79..fbbbd8b1 100644 --- a/microg-ui-tools/src/main/res/layout/about_root.xml +++ b/microg-ui-tools/src/main/res/layout/about_root.xml @@ -34,7 +34,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" - android:text="microG UI Demo" + android:text="@+id/about_root_title" android:textAppearance="@style/TextAppearance.AppCompat.Headline" android:textColor="?attr/colorAccent"/> @@ -44,7 +44,7 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:visibility="gone" - android:text="Summary" + android:text="@+id/about_root_summary" android:textAppearance="@style/TextAppearance.AppCompat.Subhead" android:textColor="?attr/colorAccent"/> @@ -53,7 +53,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:text="Version v0.1.0" + android:text="@+id/about_root_version" android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/> - \ No newline at end of file + diff --git a/microg-ui-tools/src/main/res/values/strings.xml b/microg-ui-tools/src/main/res/values/strings.xml index 186ef926..47baddc5 100644 --- a/microg-ui-tools/src/main/res/values/strings.xml +++ b/microg-ui-tools/src/main/res/values/strings.xml @@ -27,4 +27,9 @@ Self-Check Check if the system is correctly set up to use microG. - \ No newline at end of file + + microG UI Demo + Summary + Version v0.1.0 + Included libraries + From c6a81f4d7d978418352648cecd2545ea575a8caf Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 11 Feb 2016 18:13:10 +0100 Subject: [PATCH 11/39] Add references to resources and AndroidManifest.xml to Android.mk related to microg/android_packages_apps_UnifiedNlp#57 --- Android.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Android.mk b/Android.mk index 6f9596ad..b34fceba 100644 --- a/Android.mk +++ b/Android.mk @@ -3,7 +3,8 @@ include $(CLEAR_VARS) LOCAL_MODULE := MicroGUiTools LOCAL_SRC_FILES := $(call all-java-files-under, microg-ui-tools/src/main/java) - +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/microg-ui-tools/src/main/res +LOCAL_MANIFEST_FILE := $(LOCAL_PATH)/microg-ui-tools/src/main/AndroidManifest.xml LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v7-appcompat include $(BUILD_STATIC_JAVA_LIBRARY) From a80fbe235c4ba26735ce79547c18edfd6dfde823 Mon Sep 17 00:00:00 2001 From: Julien Bolard Date: Tue, 1 Mar 2016 13:49:19 +0100 Subject: [PATCH 12/39] Generate Gradle BuildConfig.java file during AOSP build This fix compilation problem using AOSP source tree since it does not handle (yet?) Graddle generated files. Ignore also Android Studio generated files. Signed-off-by: Julien Bolard --- .gitignore | 3 +++ Android.mk | 27 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 31ed2bf6..7ee5ca07 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ build/ .gradle/ local.properties *.iml +.idea/ +build.gradle +BuildConfig.java diff --git a/Android.mk b/Android.mk index b34fceba..840ad077 100644 --- a/Android.mk +++ b/Android.mk @@ -1,10 +1,35 @@ LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +# Generate Gradle BuildConfig.mk file since AOSP does not handle that +# Remove the generated file if you want it to be regenerated with new values + +UITOOLS_BUILDCONFIG_CLASS := microg-ui-tools/src/main/java/org/microg/tools/ui/BuildConfig.java +UITOOLS_BC_PATH := $(LOCAL_PATH)/$(UITOOLS_BUILDCONFIG_CLASS) +UITOOLS_BC_APPLICATION_ID := "org.microg.tools.ui" +UITOOLS_BC_VERSION_CODE := -1 + +$(UITOOLS_BC_PATH): + echo "/**" > $(UITOOLS_BC_PATH) + echo "* Automatically generated file. DO NOT MODIFY" >> $(UITOOLS_BC_PATH) + echo "*/" >> $(UITOOLS_BC_PATH) + echo "package "$(UITOOLS_BC_APPLICATION_ID)";" >> $(UITOOLS_BC_PATH) + echo "public final class BuildConfig {" >> $(UITOOLS_BC_PATH) + echo " public static final String APPLICATION_ID = \""$(UITOOLS_BC_APPLICATION_ID)"\";" >> $(UITOOLS_BC_PATH) + echo " public static final int VERSION_CODE = "$(UITOOLS_BC_VERSION_CODE)";" >> $(UITOOLS_BC_PATH) + echo " private BuildConfig() {}" >> $(UITOOLS_BC_PATH) + echo "}" >> $(UITOOLS_BC_PATH) + LOCAL_MODULE := MicroGUiTools LOCAL_SRC_FILES := $(call all-java-files-under, microg-ui-tools/src/main/java) +LOCAL_SRC_FILES += $(UITOOLS_BUILDCONFIG_CLASS) + LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/microg-ui-tools/src/main/res -LOCAL_MANIFEST_FILE := $(LOCAL_PATH)/microg-ui-tools/src/main/AndroidManifest.xml +LOCAL_RESOURCE_DIR += frameworks/support/v7/appcompat/res +LOCAL_MANIFEST_FILE := microg-ui-tools/src/main/AndroidManifest.xml LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4 android-support-v7-appcompat +LOCAL_AAPT_FLAGS := --auto-add-overlay +LOCAL_AAPT_FLAGS += --extra-packages android.support.v7.appcompat + include $(BUILD_STATIC_JAVA_LIBRARY) From 1e8926edd95dc155d57c1306d7b13448cd895740 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Fri, 4 Mar 2016 15:54:36 +0100 Subject: [PATCH 13/39] Add permission self check --- .../tools/selfcheck/PermissionCheckGroup.java | 76 +++++++++++++++++++ .../src/main/res/layout/about_root.xml | 2 +- .../src/main/res/values-sr/strings.xml | 4 + .../src/main/res/values/strings.xml | 4 + 4 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java diff --git a/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java new file mode 100644 index 00000000..266cf64d --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/selfcheck/PermissionCheckGroup.java @@ -0,0 +1,76 @@ +/* + * Copyright 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.selfcheck; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.PermissionGroupInfo; +import android.content.pm.PermissionInfo; +import android.support.v4.app.Fragment; +import android.util.Log; + +import org.microg.tools.ui.R; + +import static android.os.Build.VERSION_CODES.M; +import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Negative; +import static org.microg.tools.selfcheck.SelfCheckGroup.Result.Positive; + +@TargetApi(M) +public class PermissionCheckGroup implements SelfCheckGroup { + private static final String TAG = "SelfCheckPerms"; + + private String[] permissions; + + public PermissionCheckGroup(String... permissions) { + this.permissions = permissions; + } + + @Override + public String getGroupName(Context context) { + return context.getString(R.string.self_check_cat_permissions); + } + + @Override + public void doChecks(Context context, ResultCollector collector) { + for (String permission : permissions) { + doPermissionCheck(context, collector, permission); + } + } + + private void doPermissionCheck(Context context, ResultCollector collector, final String permission) { + PackageManager pm = context.getPackageManager(); + try { + PermissionInfo info = pm.getPermissionInfo(permission, 0); + PermissionGroupInfo groupInfo = info.group != null ? pm.getPermissionGroupInfo(info.group, 0) : null; + CharSequence permLabel = info.loadLabel(pm); + CharSequence groupLabel = groupInfo != null ? groupInfo.loadLabel(pm) : permLabel; + 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, groupLabel), + new SelfCheckGroup.CheckResolver() { + + @Override + public void tryResolve(Fragment fragment) { + fragment.requestPermissions(new String[]{permission}, 0); + } + }); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, e); + } + } +} diff --git a/microg-ui-tools/src/main/res/layout/about_root.xml b/microg-ui-tools/src/main/res/layout/about_root.xml index fbbbd8b1..c731c4a7 100644 --- a/microg-ui-tools/src/main/res/layout/about_root.xml +++ b/microg-ui-tools/src/main/res/layout/about_root.xml @@ -65,7 +65,7 @@ android:paddingRight="?attr/listPreferredItemPaddingRight" android:paddingTop="16dip" android:textAppearance="@style/TextAppearance.AppCompat.Body2" - android:text="@+id/about_root_libraries" + android:text="@string/about_root_libraries" android:textColor="?attr/colorAccent"/> микроГ самопровера Провера исправности подешавања система за коришћење микроГ услуга. + + Дозволе одобрене + Дозволе за %1$s: + Тапните овде да одобрите дозволе за %1$s. Не одобравање дозвола може да резултира чудним понашањем апликација. diff --git a/microg-ui-tools/src/main/res/values/strings.xml b/microg-ui-tools/src/main/res/values/strings.xml index 47baddc5..010cb604 100644 --- a/microg-ui-tools/src/main/res/values/strings.xml +++ b/microg-ui-tools/src/main/res/values/strings.xml @@ -28,6 +28,10 @@ Self-Check Check if the system is correctly set up to use microG. + Permissions granted + Permission to %1$s: + Touch here to grant permission to %1$s. Not granting the permission can result in misbehaving applications. + microG UI Demo Summary Version v0.1.0 From e73d7fae9a613fff8f3f7b0f2f03011b751b30a4 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 14 Apr 2016 21:41:46 +0200 Subject: [PATCH 14/39] Update Gradle --- gradle/wrapper/gradle-wrapper.properties | 2 +- microg-ui-tools/build.gradle | 16 ++++++++-------- .../microg/tools/ui/AbstractAboutFragment.java | 2 ++ microg-ui-tools/src/main/res/values/strings.xml | 6 +++++- 4 files changed, 16 insertions(+), 10 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 03cfe798..aa76e3eb 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index 0b439ad9..bff3323e 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -19,18 +19,18 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:1.5.0' + classpath 'com.android.tools.build:gradle:2.0.0' } } apply plugin: 'com.android.library' String getMyVersionName() { def stdout = new ByteArrayOutputStream() - exec { - commandLine 'git', 'describe', '--tags', '--always', '--dirty' - standardOutput = stdout - } - return stdout.toString().trim() + if (rootProject.file("gradlew").exists()) + exec { commandLine 'git', 'describe', '--tags', '--always', '--dirty'; standardOutput = stdout } + else // automatic build system, don't tag dirty + exec { commandLine 'git', 'describe', '--tags', '--always'; standardOutput = stdout } + return stdout.toString().trim().substring(1) } android { @@ -51,7 +51,7 @@ android { } dependencies { - compile 'com.android.support:support-v4:23.1.1' - compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:support-v4:23.3.0' + compile 'com.android.support:appcompat-v7:23.3.0' } diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index 42e0c4fc..7e2a915b 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -95,6 +95,8 @@ public abstract class AbstractAboutFragment extends Fragment { List libraries = new ArrayList(); libraries.add(new Library(BuildConfig.APPLICATION_ID, getString(R.string.lib_name), getString(R.string.lib_license))); + libraries.add(new Library("android.support.v4", getString(R.string.about_android_support_v4), getString(R.string.about_android_support_license))); + libraries.add(new Library("android.support.v7.appcompat", getString(R.string.about_android_support_v7_appcompat), getString(R.string.about_android_support_license))); collectLibraries(libraries); Collections.sort(libraries); ((ListView) aboutRoot.findViewById(android.R.id.list)).setAdapter(new LibraryAdapter(getContext(), libraries.toArray(new Library[libraries.size()]))); diff --git a/microg-ui-tools/src/main/res/values/strings.xml b/microg-ui-tools/src/main/res/values/strings.xml index 010cb604..05af915f 100644 --- a/microg-ui-tools/src/main/res/values/strings.xml +++ b/microg-ui-tools/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ microG UI Tools - Apache License 2.0, Copyright © microG Team + Apache License 2.0 by microG Team Version %1$s %1$s %2$s @@ -36,4 +36,8 @@ Summary Version v0.1.0 Included libraries + + v4 Support Library + v7 appcompat Support Library + Apache License 2.0 by The Android Open Source Project From 8003a0ac898cb5ba658c255cfbb09885014bcf39 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Thu, 2 Jun 2016 22:58:37 +0200 Subject: [PATCH 15/39] Update build tools and support lib --- microg-ui-tools/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index bff3323e..bd68afb5 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'com.android.tools.build:gradle:2.1.0' } } apply plugin: 'com.android.library' @@ -51,7 +51,7 @@ android { } dependencies { - compile 'com.android.support:support-v4:23.3.0' - compile 'com.android.support:appcompat-v7:23.3.0' + compile 'com.android.support:support-v4:23.4.0' + compile 'com.android.support:appcompat-v7:23.4.0' } From 3532954c3e232b5ca0ca83c4db83fd0acb42845a Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 2 Jul 2016 14:21:28 +0200 Subject: [PATCH 16/39] Update build tools and add LICENSE --- .travis.yml | 2 +- LICENSE | 177 +++++++++++++++++++++++++++++++++++ microg-ui-tools/build.gradle | 4 +- 3 files changed, 180 insertions(+), 3 deletions(-) create mode 100644 LICENSE diff --git a/.travis.yml b/.travis.yml index 5cf5f787..846cd7b8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ android: components: - platform-tools - tools - - build-tools-23.0.2 + - build-tools-23.0.3 - android-23 - extra-android-m2repository before_cache: diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..f433b1a5 --- /dev/null +++ b/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index bd68afb5..ea1ec07b 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -19,7 +19,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.0' + classpath 'com.android.tools.build:gradle:2.1.2' } } apply plugin: 'com.android.library' @@ -35,7 +35,7 @@ String getMyVersionName() { android { compileSdkVersion 23 - buildToolsVersion "23.0.2" + buildToolsVersion "23.0.3" defaultConfig { versionName getMyVersionName() From 4d8fb82ae557562e557a4984583b0d32f55b2b34 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sat, 27 Aug 2016 12:35:01 +0200 Subject: [PATCH 17/39] Update Gradle --- .gitignore | 1 - build.gradle | 38 ++++++++++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- microg-ui-tools/build.gradle | 12 ++------ 4 files changed, 41 insertions(+), 12 deletions(-) create mode 100644 build.gradle diff --git a/.gitignore b/.gitignore index 7ee5ca07..d84bc0b7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,5 +3,4 @@ build/ local.properties *.iml .idea/ -build.gradle BuildConfig.java diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..4b3b586b --- /dev/null +++ b/build.gradle @@ -0,0 +1,38 @@ +/* + * 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. + */ + +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.1.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' + } +} + +allprojects { + apply plugin: 'idea' + ext.androidBuildVersionTools = "23.0.3" +} + +def androidCompileSdk() { return 23 } + +subprojects { + repositories { + jcenter() + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa76e3eb..06df1f78 100755 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index ea1ec07b..c3cac8ab 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -14,14 +14,6 @@ * limitations under the License. */ -buildscript { - repositories { - jcenter() - } - dependencies { - classpath 'com.android.tools.build:gradle:2.1.2' - } -} apply plugin: 'com.android.library' String getMyVersionName() { @@ -34,8 +26,8 @@ String getMyVersionName() { } android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion androidCompileSdk() + buildToolsVersion "$androidBuildVersionTools" defaultConfig { versionName getMyVersionName() From 4f3d77f9be908150956f247a69015fa2a740b682 Mon Sep 17 00:00:00 2001 From: Marvin W Date: Sun, 13 Nov 2016 15:49:54 +0100 Subject: [PATCH 18/39] Major UI overhaul --- microg-ui-tools/build.gradle | 5 +- .../v4/preference/PreferenceFragment.java | 313 ------------------ .../tools/ui/AbstractAboutFragment.java | 32 +- .../tools/ui/AbstractSelfCheckFragment.java | 2 +- .../tools/ui/AbstractSettingsActivity.java | 79 +++++ .../tools/ui/AbstractSettingsFragment.java | 40 +++ .../org/microg/tools/ui/DialogPreference.java | 113 +++++++ .../tools/ui/DimmableIconPreference.java | 80 +++++ .../tools/ui/ResourceSettingsFragment.java | 38 +++ .../java/org/microg/tools/ui/SwitchBar.java | 270 +++++++++++++++ .../microg/tools/ui/TintIconPreference.java | 44 +++ .../org/microg/tools/ui/ToggleSwitch.java | 60 ++++ .../res/drawable-v21/switchbar_background.xml | 6 + .../src/main/res/drawable/self_check.xml | 11 + .../res/drawable/switchbar_background.xml | 4 + .../res/layout-v21/preference_material.xml | 81 +++++ .../src/main/res/layout/about_root.xml | 6 +- .../src/main/res/layout/app_bar.xml | 49 +++ .../src/main/res/layout/settings_activity.xml | 42 +++ .../src/main/res/layout/switch_bar.xml | 46 +++ .../src/main/res/values/colors.xml | 3 + .../src/main/res/values/strings.xml | 5 +- .../src/main/res/values/themes.xml | 2 +- 23 files changed, 997 insertions(+), 334 deletions(-) delete mode 100644 microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/DimmableIconPreference.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/ResourceSettingsFragment.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/TintIconPreference.java create mode 100644 microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java create mode 100644 microg-ui-tools/src/main/res/drawable-v21/switchbar_background.xml create mode 100644 microg-ui-tools/src/main/res/drawable/self_check.xml create mode 100644 microg-ui-tools/src/main/res/drawable/switchbar_background.xml create mode 100644 microg-ui-tools/src/main/res/layout-v21/preference_material.xml create mode 100644 microg-ui-tools/src/main/res/layout/app_bar.xml create mode 100644 microg-ui-tools/src/main/res/layout/settings_activity.xml create mode 100644 microg-ui-tools/src/main/res/layout/switch_bar.xml diff --git a/microg-ui-tools/build.gradle b/microg-ui-tools/build.gradle index c3cac8ab..cae89799 100644 --- a/microg-ui-tools/build.gradle +++ b/microg-ui-tools/build.gradle @@ -43,7 +43,8 @@ android { } dependencies { - compile 'com.android.support:support-v4:23.4.0' - compile 'com.android.support:appcompat-v7:23.4.0' + compile 'com.android.support:support-v4:25.0.0' + compile 'com.android.support:appcompat-v7:25.0.0' + compile 'com.takisoft.fix:preference-v7:25.0.0.1' } diff --git a/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java b/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java deleted file mode 100644 index 8cb10aa6..00000000 --- a/microg-ui-tools/src/main/java/android/support/v4/preference/PreferenceFragment.java +++ /dev/null @@ -1,313 +0,0 @@ -/* - * Copyright 2013-2016 microG Project Team - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.support.v4.preference; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.preference.Preference; -import android.preference.PreferenceManager; -import android.preference.PreferenceScreen; -import android.support.v4.app.Fragment; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ListView; - -import java.lang.reflect.Constructor; -import java.lang.reflect.Method; - -public abstract class PreferenceFragment extends Fragment { - - private static final int FIRST_REQUEST_CODE = 100; - private static final int MSG_BIND_PREFERENCES = 1; - private static final String PREFERENCES_TAG = "android:preferences"; - private boolean mHavePrefs; - private boolean mInitDone; - private ListView mList; - private PreferenceManager mPreferenceManager; - - private Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - - case MSG_BIND_PREFERENCES: - bindPreferences(); - break; - } - } - }; - - final private Runnable mRequestFocus = new Runnable() { - public void run() { - mList.focusableViewAvailable(mList); - } - }; - - private void bindPreferences() { - PreferenceScreen localPreferenceScreen = getPreferenceScreen(); - if (localPreferenceScreen != null) { - ListView localListView = getListView(); - localPreferenceScreen.bind(localListView); - } - } - - private void ensureList() { - if (mList == null) { - View view = getView(); - if (view == null) { - throw new IllegalStateException("Content view not yet created"); - } - - View listView = view.findViewById(android.R.id.list); - if (!(listView instanceof ListView)) { - throw new RuntimeException( - "Content has view with id attribute 'android.R.id.list' that is not a ListView class"); - } - - mList = (ListView) listView; - if (mList == null) { - throw new RuntimeException( - "Your content must have a ListView whose id attribute is 'android.R.id.list'"); - } - - mHandler.post(mRequestFocus); - } - } - - private void postBindPreferences() { - if (mHandler.hasMessages(MSG_BIND_PREFERENCES)) { - mHandler.obtainMessage(MSG_BIND_PREFERENCES).sendToTarget(); - } - } - - private void requirePreferenceManager() { - if (this.mPreferenceManager == null) { - throw new RuntimeException("This should be called after super.onCreate."); - } - } - - public void addPreferencesFromIntent(Intent intent) { - requirePreferenceManager(); - PreferenceScreen screen = inflateFromIntent(intent, getPreferenceScreen()); - setPreferenceScreen(screen); - } - - public void addPreferencesFromResource(int resId) { - requirePreferenceManager(); - PreferenceScreen screen = inflateFromResource(getActivity(), resId, getPreferenceScreen()); - setPreferenceScreen(screen); - } - - public Preference findPreference(CharSequence key) { - if (mPreferenceManager == null) { - return null; - } - return mPreferenceManager.findPreference(key); - } - - public ListView getListView() { - ensureList(); - return mList; - } - - public PreferenceManager getPreferenceManager() { - return mPreferenceManager; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - getListView().setScrollBarStyle(View.SCROLLBARS_INSIDE_OVERLAY); - if (mHavePrefs) { - bindPreferences(); - } - mInitDone = true; - if (savedInstanceState != null) { - Bundle localBundle = savedInstanceState.getBundle(PREFERENCES_TAG); - if (localBundle != null) { - PreferenceScreen screen = getPreferenceScreen(); - if (screen != null) { - screen.restoreHierarchyState(localBundle); - } - } - } - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - dispatchActivityResult(requestCode, resultCode, data); - } - - @Override - public void onCreate(Bundle paramBundle) { - super.onCreate(paramBundle); - mPreferenceManager = createPreferenceManager(); - } - - @Override - public View onCreateView(LayoutInflater paramLayoutInflater, ViewGroup paramViewGroup, - Bundle paramBundle) { - ListView listView = new ListView(paramLayoutInflater.getContext()); - listView.setId(android.R.id.list); - listView.setDrawSelectorOnTop(false); - listView.setPadding(12, 6, 12, 0); - //listView.setSelector(null); - return listView; - } - - @Override - public void onDestroy() { - super.onDestroy(); - dispatchActivityDestroy(); - } - - @Override - public void onDestroyView() { - mList = null; - mHandler.removeCallbacks(mRequestFocus); - mHandler.removeMessages(MSG_BIND_PREFERENCES); - super.onDestroyView(); - } - - @Override - public void onSaveInstanceState(Bundle bundle) { - super.onSaveInstanceState(bundle); - PreferenceScreen screen = getPreferenceScreen(); - if (screen != null) { - Bundle localBundle = new Bundle(); - screen.saveHierarchyState(localBundle); - bundle.putBundle(PREFERENCES_TAG, localBundle); - } - } - - @Override - public void onStop() { - super.onStop(); - dispatchActivityStop(); - } - - /** - * Access methods with visibility private * - */ - - private PreferenceManager createPreferenceManager() { - try { - Constructor c = PreferenceManager.class - .getDeclaredConstructor(Activity.class, int.class); - c.setAccessible(true); - return c.newInstance(this.getActivity(), FIRST_REQUEST_CODE); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private PreferenceScreen getPreferenceScreen() { - try { - Method m = PreferenceManager.class.getDeclaredMethod("getPreferenceScreen"); - m.setAccessible(true); - return (PreferenceScreen) m.invoke(mPreferenceManager); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void setPreferenceScreen(PreferenceScreen preferenceScreen) { - try { - Method m = PreferenceManager.class - .getDeclaredMethod("setPreferences", PreferenceScreen.class); - m.setAccessible(true); - boolean result = (Boolean) m.invoke(mPreferenceManager, preferenceScreen); - if (result && preferenceScreen != null) { - mHavePrefs = true; - if (mInitDone) { - postBindPreferences(); - } - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void dispatchActivityResult(int requestCode, int resultCode, Intent data) { - try { - Method m = PreferenceManager.class - .getDeclaredMethod("dispatchActivityResult", int.class, int.class, - Intent.class); - m.setAccessible(true); - m.invoke(mPreferenceManager, requestCode, resultCode, data); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void dispatchActivityDestroy() { - try { - Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityDestroy"); - m.setAccessible(true); - m.invoke(mPreferenceManager); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - private void dispatchActivityStop() { - try { - Method m = PreferenceManager.class.getDeclaredMethod("dispatchActivityStop"); - m.setAccessible(true); - m.invoke(mPreferenceManager); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - - public PreferenceScreen inflateFromResource(Context context, int resId, - PreferenceScreen rootPreferences) { - PreferenceScreen preferenceScreen; - try { - Method m = PreferenceManager.class - .getDeclaredMethod("inflateFromResource", Context.class, int.class, - PreferenceScreen.class); - m.setAccessible(true); - preferenceScreen = (PreferenceScreen) m - .invoke(mPreferenceManager, context, resId, rootPreferences); - } catch (Exception e) { - throw new RuntimeException(e); - } - return preferenceScreen; - } - - public PreferenceScreen inflateFromIntent(Intent queryIntent, - PreferenceScreen rootPreferences) { - PreferenceScreen preferenceScreen; - try { - Method m = PreferenceManager.class - .getDeclaredMethod("inflateFromIntent", Intent.class, PreferenceScreen.class); - m.setAccessible(true); - preferenceScreen = (PreferenceScreen) m - .invoke(mPreferenceManager, queryIntent, rootPreferences); - } catch (Exception e) { - throw new RuntimeException(e); - } - return preferenceScreen; - } -} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java index 7e2a915b..9a728fa0 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractAboutFragment.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 microG Project Team + * Copyright (C) 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. @@ -22,7 +22,6 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v4.app.Fragment; -import android.support.v4.content.ContextCompat; import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; @@ -40,21 +39,21 @@ public abstract class AbstractAboutFragment extends Fragment { protected abstract void collectLibraries(List libraries); - protected Drawable getIcon() { + public static Drawable getIcon(Context context) { try { - PackageManager pm = getContext().getPackageManager(); - return pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadIcon(pm); + 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); } } - protected String getAppName() { + public static String getAppName(Context context) { try { - PackageManager pm = getContext().getPackageManager(); - CharSequence label = pm.getPackageInfo(getContext().getPackageName(), 0).applicationInfo.loadLabel(pm); - if (TextUtils.isEmpty(label)) return getContext().getPackageName(); + 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! @@ -62,7 +61,11 @@ public abstract class AbstractAboutFragment extends Fragment { } } - protected String getLibVersion(String packageName) { + 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 ""; @@ -72,8 +75,12 @@ public abstract class AbstractAboutFragment extends Fragment { } } + public static String getSelfVersion(Context context) { + return getLibVersion(context.getPackageName()); + } + protected String getSelfVersion() { - return getLibVersion(getContext().getPackageName()); + return getSelfVersion(getContext()); } protected String getSummary() { @@ -84,7 +91,7 @@ public abstract class AbstractAboutFragment extends Fragment { @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()); + ((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(); @@ -97,6 +104,7 @@ public abstract class AbstractAboutFragment extends Fragment { libraries.add(new Library(BuildConfig.APPLICATION_ID, getString(R.string.lib_name), getString(R.string.lib_license))); libraries.add(new Library("android.support.v4", getString(R.string.about_android_support_v4), getString(R.string.about_android_support_license))); libraries.add(new Library("android.support.v7.appcompat", getString(R.string.about_android_support_v7_appcompat), getString(R.string.about_android_support_license))); + libraries.add(new Library("android.support.v7.preference#hide_version", getString(R.string.about_android_support_v7_preference), getString(R.string.about_android_support_license))); collectLibraries(libraries); Collections.sort(libraries); ((ListView) aboutRoot.findViewById(android.R.id.list)).setAdapter(new LibraryAdapter(getContext(), libraries.toArray(new Library[libraries.size()]))); diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java index 1a3f25b3..9ef9fd92 100644 --- a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSelfCheckFragment.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2016 microG Project Team + * Copyright (C) 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. diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java new file mode 100644 index 00000000..ea5b9427 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsActivity.java @@ -0,0 +1,79 @@ +package org.microg.tools.ui; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.view.ViewGroup; + +public 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; + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java new file mode 100644 index 00000000..89422e48 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/AbstractSettingsFragment.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.support.v4.app.DialogFragment; +import android.support.v7.preference.Preference; + +import com.takisoft.fix.support.v7.preference.PreferenceFragmentCompat; + +public abstract class AbstractSettingsFragment extends PreferenceFragmentCompat { + private static final String TAG = AbstractSettingsFragment.class.getSimpleName(); + + private static final String DIALOG_FRAGMENT_TAG = + "android.support.v7.preference.PreferenceFragment.DIALOG"; + + @Override + public void onDisplayPreferenceDialog(Preference preference) { + if (preference instanceof DialogPreference) { + DialogFragment f = DialogPreference.DialogPreferenceCompatDialogFragment.newInstance(preference.getKey()); + f.setTargetFragment(this, 0); + f.show(getFragmentManager(), DIALOG_FRAGMENT_TAG); + } else { + super.onDisplayPreferenceDialog(preference); + } + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java new file mode 100644 index 00000000..b592db48 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/DialogPreference.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceDialogFragmentCompat; +import android.support.v7.preference.PreferenceFragmentCompat; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +public class DialogPreference extends android.support.v7.preference.DialogPreference implements PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback { + + private static final String DIALOG_FRAGMENT_TAG = + "android.support.v7.preference.PreferenceFragment.DIALOG"; + + public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + public DialogPreference(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public DialogPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public DialogPreference(Context context) { + super(context); + } + + protected View onCreateDialogView() { + return null; + } + + /** + * Called when the dialog is dismissed and should be used to save data to + * the {@link SharedPreferences}. + * + * @param positiveResult Whether the positive button was clicked (true), or + * the negative button was clicked or the dialog was canceled (false). + */ + protected void onDialogClosed(boolean positiveResult) { + } + + @Override + public boolean onPreferenceDisplayDialog(PreferenceFragmentCompat caller, Preference pref) { + DialogPreferenceCompatDialogFragment fragment = new DialogPreferenceCompatDialogFragment(); + fragment.setTargetFragment(caller, 0); + fragment.show(caller.getFragmentManager(), DIALOG_FRAGMENT_TAG); + return true; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + + ViewGroup.LayoutParams layoutParams = view.findViewById(R.id.icon_frame).getLayoutParams(); + if (layoutParams instanceof LinearLayout.LayoutParams) { + if (((LinearLayout.LayoutParams) layoutParams).leftMargin < 0) { + ((LinearLayout.LayoutParams) layoutParams).leftMargin = 0; + } + } + } + + public static class DialogPreferenceCompatDialogFragment extends PreferenceDialogFragmentCompat { + + @Override + protected View onCreateDialogView(Context context) { + if (getPreference() instanceof DialogPreference) { + View view = ((DialogPreference) getPreference()).onCreateDialogView(); + if (view != null) return view; + } + return super.onCreateDialogView(context); + } + + @Override + public void onDialogClosed(boolean positiveResult) { + if (getPreference() instanceof DialogPreference) { + ((DialogPreference) getPreference()).onDialogClosed(positiveResult); + } + } + + public static DialogFragment newInstance(String key) { + final DialogPreferenceCompatDialogFragment fragment = new DialogPreferenceCompatDialogFragment(); + final Bundle b = new Bundle(1); + b.putString(ARG_KEY, key); + fragment.setArguments(b); + return fragment; + } + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/DimmableIconPreference.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/DimmableIconPreference.java new file mode 100644 index 00000000..c86f7813 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/DimmableIconPreference.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * 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.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceViewHolder; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.TextView; + +/** + * A preference item that can dim the icon when it's disabled, either directly or because its parent + * is disabled. + */ +public class DimmableIconPreference extends Preference { + private static final int ICON_ALPHA_ENABLED = 255; + private static final int ICON_ALPHA_DISABLED = 102; + + private final CharSequence mContentDescription; + + public DimmableIconPreference(Context context) { + this(context, (AttributeSet) null); + } + + public DimmableIconPreference(Context context, AttributeSet attrs) { + super(context, attrs); + mContentDescription = null; + } + + public DimmableIconPreference(Context context, CharSequence contentDescription) { + super(context); + mContentDescription = contentDescription; + } + + protected boolean shouldDimIcon() { + return !isEnabled(); + } + + private void dimIcon(boolean dimmed) { + Drawable icon = getIcon(); + if (icon != null) { + icon.mutate().setAlpha(dimmed ? ICON_ALPHA_DISABLED : ICON_ALPHA_ENABLED); + setIcon(icon); + } + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + if (!TextUtils.isEmpty(mContentDescription)) { + final TextView titleView = (TextView) view.findViewById(android.R.id.title); + titleView.setContentDescription(mContentDescription); + } + 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; + } + } + dimIcon(shouldDimIcon()); + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/ResourceSettingsFragment.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/ResourceSettingsFragment.java new file mode 100644 index 00000000..85dd9b1f --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/ResourceSettingsFragment.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.os.Bundle; +import android.support.annotation.Nullable; + +public class ResourceSettingsFragment extends AbstractSettingsFragment { + + public static final String EXTRA_PREFERENCE_RESOURCE = "preferencesResource"; + + protected int preferencesResource; + + @Override + public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) { + Bundle b = getArguments(); + if (b != null) { + preferencesResource = b.getInt(EXTRA_PREFERENCE_RESOURCE, preferencesResource); + } + if (preferencesResource != 0) { + addPreferencesFromResource(preferencesResource); + } + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java new file mode 100644 index 00000000..b086b748 --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/SwitchBar.java @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2014-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.os.Build; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.v7.widget.SwitchCompat; +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.Switch; +import android.widget.TextView; + +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 mSwitchChangeListeners = + new ArrayList(); + + public SwitchBar(Context context) { + this(context, null); + } + + public SwitchBar(Context context, AttributeSet attrs) { + super(context, attrs); + + LayoutInflater.from(context).inflate(R.layout.switch_bar, this); + + mTextView = (TextView) findViewById(R.id.switch_text); + if (SDK_INT > Build.VERSION_CODES.JELLY_BEAN) { + mTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + } + mLabel = getResources().getString(R.string.v7_preference_off); + mSummarySpan = new TextAppearanceSpan(context, android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Widget_Switch); + updateText(); + + mSwitch = (ToggleSwitch) findViewById(R.id.switch_widget); + // Prevent onSaveInstanceState() to be called as we are managing the state of the Switch + // on our own + mSwitch.setSaveEnabled(false); + if (SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mSwitch.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + } + + addOnSwitchChangeListener(new OnSwitchChangeListener() { + @Override + public void onSwitchChanged(SwitchCompat switchView, boolean isChecked) { + setTextViewLabel(isChecked); + } + }); + + setOnClickListener(this); + + // Default is hide + setVisibility(View.GONE); + } + + public void setTextViewLabel(boolean isChecked) { + mLabel = getResources() + .getString(isChecked ? R.string.v7_preference_on : R.string.v7_preference_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(null); + visible = (Boolean) in.readValue(null); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeValue(checked); + out.writeValue(visible); + } + + @Override + public String toString() { + return "SwitchBar.SavedState{" + + Integer.toHexString(System.identityHashCode(this)) + + " checked=" + checked + + " visible=" + visible + "}"; + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + + SavedState ss = new SavedState(superState); + ss.checked = mSwitch.isChecked(); + ss.visible = isShowing(); + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + + super.onRestoreInstanceState(ss.getSuperState()); + + mSwitch.setCheckedInternal(ss.checked); + setTextViewLabel(ss.checked); + setVisibility(ss.visible ? View.VISIBLE : View.GONE); + mSwitch.setOnCheckedChangeListener(ss.visible ? this : null); + + requestLayout(); + } + + @Override + public CharSequence getAccessibilityClassName() { + return Switch.class.getName(); + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/TintIconPreference.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/TintIconPreference.java new file mode 100644 index 00000000..ae81350a --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/TintIconPreference.java @@ -0,0 +1,44 @@ +package org.microg.tools.ui; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.support.v4.graphics.drawable.DrawableCompat; +import android.support.v7.preference.PreferenceViewHolder; +import android.util.AttributeSet; +import android.util.TypedValue; + +import static android.os.Build.VERSION.SDK_INT; +import static android.os.Build.VERSION_CODES.LOLLIPOP; + +public class TintIconPreference extends DimmableIconPreference { + + public TintIconPreference(Context context) { + this(context, (AttributeSet) null); + } + + public TintIconPreference(Context context, AttributeSet attrs) { + super(context, attrs); + } + + private static int getThemeAccentColor(Context context) { + int colorAttr; + if (SDK_INT >= LOLLIPOP) { + colorAttr = android.R.attr.colorAccent; + } else { + //Get colorAccent defined for AppCompat + colorAttr = context.getResources().getIdentifier("colorAccent", "attr", context.getPackageName()); + } + TypedValue outValue = new TypedValue(); + context.getTheme().resolveAttribute(colorAttr, outValue, true); + return outValue.data; + } + + @Override + public void onBindViewHolder(PreferenceViewHolder view) { + super.onBindViewHolder(view); + Drawable icon = getIcon(); + if (icon != null) { + DrawableCompat.setTint(icon, getThemeAccentColor(getContext())); + } + } +} diff --git a/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java b/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java new file mode 100644 index 00000000..44f0a09a --- /dev/null +++ b/microg-ui-tools/src/main/java/org/microg/tools/ui/ToggleSwitch.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * Copyright (C) 2014-2016 microG Project Team + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.microg.tools.ui; + +import android.content.Context; +import android.support.v7.widget.SwitchCompat; +import android.util.AttributeSet; + +public class ToggleSwitch extends SwitchCompat { + + private ToggleSwitch.OnBeforeCheckedChangeListener mOnBeforeListener; + + public interface OnBeforeCheckedChangeListener { + boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked); + } + + public ToggleSwitch(Context context) { + super(context); + } + + public ToggleSwitch(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ToggleSwitch(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setOnBeforeCheckedChangeListener(OnBeforeCheckedChangeListener listener) { + mOnBeforeListener = listener; + } + + @Override + public void setChecked(boolean checked) { + if (mOnBeforeListener != null + && mOnBeforeListener.onBeforeCheckedChanged(this, checked)) { + return; + } + super.setChecked(checked); + } + + public void setCheckedInternal(boolean checked) { + super.setChecked(checked); + } +} diff --git a/microg-ui-tools/src/main/res/drawable-v21/switchbar_background.xml b/microg-ui-tools/src/main/res/drawable-v21/switchbar_background.xml new file mode 100644 index 00000000..20909c3a --- /dev/null +++ b/microg-ui-tools/src/main/res/drawable-v21/switchbar_background.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/microg-ui-tools/src/main/res/drawable/self_check.xml b/microg-ui-tools/src/main/res/drawable/self_check.xml new file mode 100644 index 00000000..5c28f0c3 --- /dev/null +++ b/microg-ui-tools/src/main/res/drawable/self_check.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/drawable/switchbar_background.xml b/microg-ui-tools/src/main/res/drawable/switchbar_background.xml new file mode 100644 index 00000000..643d428b --- /dev/null +++ b/microg-ui-tools/src/main/res/drawable/switchbar_background.xml @@ -0,0 +1,4 @@ + + + diff --git a/microg-ui-tools/src/main/res/layout-v21/preference_material.xml b/microg-ui-tools/src/main/res/layout-v21/preference_material.xml new file mode 100644 index 00000000..2c93ad42 --- /dev/null +++ b/microg-ui-tools/src/main/res/layout-v21/preference_material.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/microg-ui-tools/src/main/res/layout/about_root.xml b/microg-ui-tools/src/main/res/layout/about_root.xml index c731c4a7..4488ae32 100644 --- a/microg-ui-tools/src/main/res/layout/about_root.xml +++ b/microg-ui-tools/src/main/res/layout/about_root.xml @@ -34,7 +34,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center_horizontal" - android:text="@+id/about_root_title" + android:text="@string/about_root_title" android:textAppearance="@style/TextAppearance.AppCompat.Headline" android:textColor="?attr/colorAccent"/> @@ -44,7 +44,7 @@ android:layout_height="wrap_content" android:gravity="center_horizontal" android:visibility="gone" - android:text="@+id/about_root_summary" + android:text="@string/about_root_summary" android:textAppearance="@style/TextAppearance.AppCompat.Subhead" android:textColor="?attr/colorAccent"/> @@ -53,7 +53,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" - android:text="@+id/about_root_version" + android:text="@string/about_root_version" android:textAppearance="@style/TextAppearance.AppCompat.Subhead"/> + + + + + + + + + diff --git a/microg-ui-tools/src/main/res/layout/settings_activity.xml b/microg-ui-tools/src/main/res/layout/settings_activity.xml new file mode 100644 index 00000000..4571b19c --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/settings_activity.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + diff --git a/microg-ui-tools/src/main/res/layout/switch_bar.xml b/microg-ui-tools/src/main/res/layout/switch_bar.xml new file mode 100644 index 00000000..b320e3c9 --- /dev/null +++ b/microg-ui-tools/src/main/res/layout/switch_bar.xml @@ -0,0 +1,46 @@ + + + + + + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/values/colors.xml b/microg-ui-tools/src/main/res/values/colors.xml index a9410025..237df0fc 100644 --- a/microg-ui-tools/src/main/res/values/colors.xml +++ b/microg-ui-tools/src/main/res/values/colors.xml @@ -18,4 +18,7 @@ #ff263238 #ff21272b #ff009688 + + #ff37474f + #ff7fcac3 diff --git a/microg-ui-tools/src/main/res/values/strings.xml b/microg-ui-tools/src/main/res/values/strings.xml index 05af915f..8a1de419 100644 --- a/microg-ui-tools/src/main/res/values/strings.xml +++ b/microg-ui-tools/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ microG UI Tools - Apache License 2.0 by microG Team + Apache License 2.0, microG Team Version %1$s %1$s %2$s @@ -39,5 +39,6 @@ v4 Support Library v7 appcompat Support Library - Apache License 2.0 by The Android Open Source Project + v7 preference Support Library + Apache License 2.0, The Android Open Source Project diff --git a/microg-ui-tools/src/main/res/values/themes.xml b/microg-ui-tools/src/main/res/values/themes.xml index 80726729..225a7b5f 100644 --- a/microg-ui-tools/src/main/res/values/themes.xml +++ b/microg-ui-tools/src/main/res/values/themes.xml @@ -16,7 +16,7 @@ - + + + + + + + \ No newline at end of file diff --git a/microg-ui-tools/src/main/res/values/themes.xml b/microg-ui-tools/src/main/res/values/themes.xml index 225a7b5f..578f3291 100644 --- a/microg-ui-tools/src/main/res/values/themes.xml +++ b/microg-ui-tools/src/main/res/values/themes.xml @@ -16,11 +16,19 @@ - + + + +