From 315265fbb4ff7b7e54780bae40a2d0c8602bfaf8 Mon Sep 17 00:00:00 2001 From: Nico Alt Date: Tue, 21 Apr 2015 21:55:07 +0200 Subject: [PATCH 01/78] fix #91: Material Design - use 'com.android.support:appcompat-v7:22.0.0' instead of version 20.0.0 - ActionBar color: "F-Droid Blue" (also option for "F-Droid Green") - fix invisible swap button with Material Design - remove "Light + dark action bar" theme (as of Action Bar is always blue/green) --- F-Droid/build.gradle | 2 +- F-Droid/res/values-v11/styles.xml | 17 +++-------- F-Droid/res/values/array.xml | 1 - F-Droid/res/values/donottranslate.xml | 1 - F-Droid/res/values/styles.xml | 31 ++++++++++++-------- F-Droid/src/org/fdroid/fdroid/FDroidApp.java | 9 ++---- 6 files changed, 26 insertions(+), 35 deletions(-) diff --git a/F-Droid/build.gradle b/F-Droid/build.gradle index b9e2a67a0..8744895f3 100644 --- a/F-Droid/build.gradle +++ b/F-Droid/build.gradle @@ -17,7 +17,7 @@ if ( !hasProperty( 'sourceDeps' ) ) { dependencies { compile 'com.android.support:support-v4:20.0.0', - 'com.android.support:appcompat-v7:20.0.0', + 'com.android.support:appcompat-v7:22.0.0', 'com.android.support:support-annotations:20.0.0', 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0', diff --git a/F-Droid/res/values-v11/styles.xml b/F-Droid/res/values-v11/styles.xml index 0d7bd9897..bcb6bc118 100644 --- a/F-Droid/res/values-v11/styles.xml +++ b/F-Droid/res/values-v11/styles.xml @@ -1,15 +1,6 @@ + - - - - - - - - + \ No newline at end of file diff --git a/F-Droid/res/values/array.xml b/F-Droid/res/values/array.xml index 3deb4a5d4..38e4465c7 100644 --- a/F-Droid/res/values/array.xml +++ b/F-Droid/res/values/array.xml @@ -13,6 +13,5 @@ Dark Light - Light (with dark action bar) diff --git a/F-Droid/res/values/donottranslate.xml b/F-Droid/res/values/donottranslate.xml index e677bf1e0..32f9793bd 100644 --- a/F-Droid/res/values/donottranslate.xml +++ b/F-Droid/res/values/donottranslate.xml @@ -27,7 +27,6 @@ dark light - lightWithDarkActionBar diff --git a/F-Droid/res/values/styles.xml b/F-Droid/res/values/styles.xml index a0522db1a..e74c6cc05 100644 --- a/F-Droid/res/values/styles.xml +++ b/F-Droid/res/values/styles.xml @@ -2,14 +2,24 @@ - - #FF000000 @@ -29,24 +39,20 @@ - - - - - - + \ No newline at end of file diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java index bad481729..fd32107d6 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java @@ -90,12 +90,12 @@ public class FDroidApp extends Application { dark, light, lightWithDarkActionBar } - private static Theme curTheme = Theme.dark; + private static Theme curTheme = Theme.light; public void reloadTheme() { curTheme = Theme.valueOf(PreferenceManager .getDefaultSharedPreferences(getBaseContext()) - .getString(Preferences.PREF_THEME, "dark")); + .getString(Preferences.PREF_THEME, Preferences.DEFAULT_THEME)); } public void applyTheme(Activity activity) { @@ -103,12 +103,9 @@ public class FDroidApp extends Application { case dark: activity.setTheme(R.style.AppThemeDark); break; - case light: + default: activity.setTheme(R.style.AppThemeLight); break; - case lightWithDarkActionBar: - activity.setTheme(R.style.AppThemeLightWithDarkActionBar); - break; } } From b57a122ac1aa140a2499d8a031b5f94c30dc6906 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 21 Apr 2015 23:30:06 +1000 Subject: [PATCH 02/78] Made category Spinner style correctly. Signed-off-by: Nico Alt --- F-Droid/res/layout/available_app_list.xml | 21 ++++++++ F-Droid/res/layout/swap_blank.xml | 1 + F-Droid/res/values/ids.xml | 2 +- .../fragments/AvailableAppsFragment.java | 49 ++++++------------- 4 files changed, 39 insertions(+), 34 deletions(-) create mode 100644 F-Droid/res/layout/available_app_list.xml diff --git a/F-Droid/res/layout/available_app_list.xml b/F-Droid/res/layout/available_app_list.xml new file mode 100644 index 000000000..f4c147f8b --- /dev/null +++ b/F-Droid/res/layout/available_app_list.xml @@ -0,0 +1,21 @@ + + + + + + + + + + \ No newline at end of file diff --git a/F-Droid/res/layout/swap_blank.xml b/F-Droid/res/layout/swap_blank.xml index ff4591a89..5825668d0 100644 --- a/F-Droid/res/layout/swap_blank.xml +++ b/F-Droid/res/layout/swap_blank.xml @@ -18,6 +18,7 @@ android:text="@string/swap_start" style="@style/SwapTheme.StartSwap.StartButton" android:layout_width="match_parent" + android:layout_height="wrap_content" android:layout_below="@+id/text_description" android:layout_centerHorizontal="true"/> diff --git a/F-Droid/res/values/ids.xml b/F-Droid/res/values/ids.xml index 2d82ad59c..9b031b54e 100644 --- a/F-Droid/res/values/ids.xml +++ b/F-Droid/res/values/ids.xml @@ -1,5 +1,5 @@ - + \ No newline at end of file diff --git a/F-Droid/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java b/F-Droid/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java index e71988ae4..ad6a5f8ab 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java +++ b/F-Droid/src/org/fdroid/fdroid/views/fragments/AvailableAppsFragment.java @@ -17,7 +17,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.Spinner; @@ -124,28 +123,25 @@ public class AvailableAppsFragment extends AppListFragment implements */ @SuppressWarnings("deprecation") private void styleSpinner(Spinner spinner) { - if (Build.VERSION.SDK_INT >= 14) { - Drawable menuButton = getResources().getDrawable(android.R.drawable.btn_dropdown); - if (FDroidApp.getCurTheme() == FDroidApp.Theme.dark) { - menuButton.setAlpha(32); // make it darker via alpha - } - if (Build.VERSION.SDK_INT >= 16) { - spinner.setBackground(menuButton); - } else { - spinner.setBackgroundDrawable(menuButton); - } + + Drawable menuButton = getResources().getDrawable(android.R.drawable.btn_dropdown); + if (FDroidApp.getCurTheme() == FDroidApp.Theme.dark) { + menuButton.setAlpha(32); // make it darker via alpha + } + if (Build.VERSION.SDK_INT >= 16) { + spinner.setBackground(menuButton); + } else { + spinner.setBackgroundDrawable(menuButton); } } - private Spinner createCategorySpinner() { + private Spinner setupCategorySpinner(Spinner spinner) { + + categorySpinner = spinner; + categorySpinner.setId(R.id.category_spinner); categories = AppProvider.Helper.categories(getActivity()); - categorySpinner = new Spinner(getActivity()); - - // Giving it an ID lets the default save/restore state functionality do its stuff. - categorySpinner.setId(R.id.categorySpinner); - styleSpinner(categorySpinner); ArrayAdapter adapter = new ArrayAdapter<>( @@ -173,24 +169,11 @@ public class AvailableAppsFragment extends AppListFragment implements @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - LinearLayout view = new LinearLayout(getActivity()); - view.setOrientation(LinearLayout.VERTICAL); + View view = inflater.inflate(R.layout.available_app_list, container, false); - view.addView( - createCategorySpinner(), - new ViewGroup.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); + setupCategorySpinner((Spinner)view.findViewById(R.id.category_spinner)); - ListView list = new ListView(getActivity()); - list.setId(android.R.id.list); - list.setFastScrollEnabled(true); - list.setOnItemClickListener(this); - view.addView( - list, - new ViewGroup.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT)); + ((ListView)view.findViewById(android.R.id.list)).setOnItemClickListener(this); // R.string.category_whatsnew is the default set in AppListManager DEFAULT_CATEGORY = getActivity().getString(R.string.category_whatsnew); From ec67059128bb02e4f6004e662c0fd424474441cf Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 22 Apr 2015 00:03:13 +1000 Subject: [PATCH 03/78] More colours beyond colorPrimary. Signed-off-by: Nico Alt --- F-Droid/res/values-v21/styles.xml | 16 ++++++++++++++++ F-Droid/res/values/colors.xml | 4 ++++ F-Droid/res/values/styles.xml | 15 ++++----------- 3 files changed, 24 insertions(+), 11 deletions(-) create mode 100644 F-Droid/res/values-v21/styles.xml diff --git a/F-Droid/res/values-v21/styles.xml b/F-Droid/res/values-v21/styles.xml new file mode 100644 index 000000000..76b75bab0 --- /dev/null +++ b/F-Droid/res/values-v21/styles.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/F-Droid/res/values/colors.xml b/F-Droid/res/values/colors.xml index 5157887e3..6709ce46c 100644 --- a/F-Droid/res/values/colors.xml +++ b/F-Droid/res/values/colors.xml @@ -4,6 +4,10 @@ #ffCC0000 #ff999999 + #FF6097C5 + #ff4b7195 + #FFAAD024 + #27aae1 #ff98cce1 #1c6bbc diff --git a/F-Droid/res/values/styles.xml b/F-Droid/res/values/styles.xml index e74c6cc05..83e3396bb 100644 --- a/F-Droid/res/values/styles.xml +++ b/F-Droid/res/values/styles.xml @@ -3,23 +3,16 @@ #FF000000 From 96bffff8ace5c95402a20ac5daed2a9db5df04d5 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 11 May 2015 15:54:19 +1000 Subject: [PATCH 04/78] Updated support libraries to 22.1.0 --- F-Droid/build.gradle | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/F-Droid/build.gradle b/F-Droid/build.gradle index 8744895f3..6758c189c 100644 --- a/F-Droid/build.gradle +++ b/F-Droid/build.gradle @@ -16,9 +16,9 @@ if ( !hasProperty( 'sourceDeps' ) ) { dependencies { - compile 'com.android.support:support-v4:20.0.0', - 'com.android.support:appcompat-v7:22.0.0', - 'com.android.support:support-annotations:20.0.0', + compile 'com.android.support:support-v4:22.1.0', + 'com.android.support:appcompat-v7:22.1.0', + 'com.android.support:support-annotations:22.1.0', 'org.thoughtcrime.ssl.pinning:AndroidPinning:1.0.0', 'com.nostra13.universalimageloader:universal-image-loader:1.9.3', @@ -80,9 +80,9 @@ if ( !hasProperty( 'sourceDeps' ) ) { // then you can find the relevant portions of the ../build.gradle file that // include magic required to make it work at around about the v0.78 git tag. // They have since been removed to clean up the build file. - compile 'com.android.support:support-v4:20.0.0', - 'com.android.support:appcompat-v7:20.0.0', - 'com.android.support:support-annotations:20.0.0' + compile 'com.android.support:support-v4:22.1.0', + 'com.android.support:appcompat-v7:22.1.0', + 'com.android.support:support-annotations:22.1.0' androidTestCompile 'commons-io:commons-io:2.2' } From 1203cacbe4f7e68c60b33bf95983cce55e06158b Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 11 May 2015 16:27:41 +1000 Subject: [PATCH 05/78] Made swap button text white. --- F-Droid/res/values-v11/styles.xml | 6 ------ F-Droid/res/values/styles.xml | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) delete mode 100644 F-Droid/res/values-v11/styles.xml diff --git a/F-Droid/res/values-v11/styles.xml b/F-Droid/res/values-v11/styles.xml deleted file mode 100644 index bcb6bc118..000000000 --- a/F-Droid/res/values-v11/styles.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/F-Droid/res/values/styles.xml b/F-Droid/res/values/styles.xml index 83e3396bb..7c783a394 100644 --- a/F-Droid/res/values/styles.xml +++ b/F-Droid/res/values/styles.xml @@ -53,6 +53,7 @@ 9dp 63.3dp match_parent + @android:color/white 18.5sp @drawable/swap_start_button_skin From 92e4d99c1cb044538a852681b87d71c915aa855b Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 11 May 2015 23:04:33 +1000 Subject: [PATCH 06/78] Added material icons from CC licensed "Google material icons" set. Fixes #240. To make this easier, I added a script to aid in downloading icons. Checkout F-Droid/tools/download-material-icon.sh for more details. The icons are licensed under the CCv4 attribution license, which I added a shout out to under "License" in the README.md. --- F-Droid/res/drawable-hdpi/ic_add_white.png | Bin 0 -> 222 bytes .../drawable-hdpi/ic_attach_money_white.png | Bin 0 -> 761 bytes .../res/drawable-hdpi/ic_bluetooth_white.png | Bin 0 -> 663 bytes F-Droid/res/drawable-hdpi/ic_cancel_white.png | Bin 0 -> 893 bytes F-Droid/res/drawable-hdpi/ic_delete_white.png | Bin 0 -> 338 bytes .../drawable-hdpi/ic_do_not_disturb_white.png | Bin 0 -> 1171 bytes F-Droid/res/drawable-hdpi/ic_edit_white.png | Bin 0 -> 490 bytes F-Droid/res/drawable-hdpi/ic_help_white.png | Bin 0 -> 1000 bytes F-Droid/res/drawable-hdpi/ic_info_white.png | Bin 0 -> 736 bytes F-Droid/res/drawable-hdpi/ic_nfc_white.png | Bin 0 -> 565 bytes .../res/drawable-hdpi/ic_play_arrow_white.png | Bin 0 -> 399 bytes .../res/drawable-hdpi/ic_refresh_white.png | Bin 0 -> 875 bytes F-Droid/res/drawable-hdpi/ic_search_white.png | Bin 0 -> 871 bytes .../res/drawable-hdpi/ic_settings_white.png | Bin 0 -> 974 bytes F-Droid/res/drawable-hdpi/ic_share_white.png | Bin 0 -> 857 bytes .../drawable-hdpi/ic_view_headline_white.png | Bin 0 -> 227 bytes F-Droid/res/drawable-mdpi/ic_add_white.png | Bin 0 -> 198 bytes .../drawable-mdpi/ic_attach_money_white.png | Bin 0 -> 565 bytes .../res/drawable-mdpi/ic_bluetooth_white.png | Bin 0 -> 470 bytes F-Droid/res/drawable-mdpi/ic_cancel_white.png | Bin 0 -> 645 bytes F-Droid/res/drawable-mdpi/ic_delete_white.png | Bin 0 -> 270 bytes .../drawable-mdpi/ic_do_not_disturb_white.png | Bin 0 -> 815 bytes F-Droid/res/drawable-mdpi/ic_edit_white.png | Bin 0 -> 378 bytes F-Droid/res/drawable-mdpi/ic_help_white.png | Bin 0 -> 703 bytes F-Droid/res/drawable-mdpi/ic_info_white.png | Bin 0 -> 530 bytes F-Droid/res/drawable-mdpi/ic_nfc_white.png | Bin 0 -> 406 bytes .../res/drawable-mdpi/ic_play_arrow_white.png | Bin 0 -> 318 bytes .../res/drawable-mdpi/ic_refresh_white.png | Bin 0 -> 637 bytes F-Droid/res/drawable-mdpi/ic_search_white.png | Bin 0 -> 591 bytes .../res/drawable-mdpi/ic_settings_white.png | Bin 0 -> 737 bytes F-Droid/res/drawable-mdpi/ic_share_white.png | Bin 0 -> 625 bytes .../drawable-mdpi/ic_view_headline_white.png | Bin 0 -> 202 bytes F-Droid/res/drawable-xhdpi/ic_add_white.png | Bin 0 -> 269 bytes .../drawable-xhdpi/ic_attach_money_white.png | Bin 0 -> 986 bytes .../res/drawable-xhdpi/ic_bluetooth_white.png | Bin 0 -> 771 bytes .../res/drawable-xhdpi/ic_cancel_white.png | Bin 0 -> 1179 bytes .../res/drawable-xhdpi/ic_delete_white.png | 0 .../ic_do_not_disturb_white.png | Bin 0 -> 1542 bytes F-Droid/res/drawable-xhdpi/ic_edit_white.png | Bin 0 -> 632 bytes F-Droid/res/drawable-xhdpi/ic_help_white.png | Bin 0 -> 1363 bytes F-Droid/res/drawable-xhdpi/ic_info_white.png | Bin 0 -> 967 bytes F-Droid/res/drawable-xhdpi/ic_nfc_white.png | Bin 0 -> 774 bytes .../drawable-xhdpi/ic_play_arrow_white.png | Bin 0 -> 477 bytes .../res/drawable-xhdpi/ic_refresh_white.png | Bin 0 -> 1148 bytes .../res/drawable-xhdpi/ic_search_white.png | Bin 0 -> 1090 bytes .../res/drawable-xhdpi/ic_settings_white.png | Bin 0 -> 1273 bytes F-Droid/res/drawable-xhdpi/ic_share_white.png | Bin 0 -> 1115 bytes .../drawable-xhdpi/ic_view_headline_white.png | Bin 0 -> 263 bytes F-Droid/res/drawable-xxhdpi/ic_add_white.png | Bin 0 -> 356 bytes .../drawable-xxhdpi/ic_attach_money_white.png | Bin 0 -> 1445 bytes .../drawable-xxhdpi/ic_bluetooth_white.png | Bin 0 -> 1254 bytes .../res/drawable-xxhdpi/ic_cancel_white.png | Bin 0 -> 1742 bytes .../res/drawable-xxhdpi/ic_delete_white.png | Bin 0 -> 574 bytes .../ic_do_not_disturb_white.png | Bin 0 -> 2319 bytes F-Droid/res/drawable-xxhdpi/ic_edit_white.png | Bin 0 -> 843 bytes F-Droid/res/drawable-xxhdpi/ic_help_white.png | Bin 0 -> 2003 bytes F-Droid/res/drawable-xxhdpi/ic_info_white.png | Bin 0 -> 1434 bytes F-Droid/res/drawable-xxhdpi/ic_nfc_white.png | Bin 0 -> 1126 bytes .../drawable-xxhdpi/ic_play_arrow_white.png | Bin 0 -> 666 bytes .../res/drawable-xxhdpi/ic_refresh_white.png | Bin 0 -> 1704 bytes .../res/drawable-xxhdpi/ic_search_white.png | Bin 0 -> 1671 bytes .../res/drawable-xxhdpi/ic_settings_white.png | Bin 0 -> 1933 bytes .../res/drawable-xxhdpi/ic_share_white.png | Bin 0 -> 1631 bytes .../ic_view_headline_white.png | Bin 0 -> 374 bytes F-Droid/res/drawable-xxxhdpi/ic_add_white.png | Bin 0 -> 470 bytes .../ic_attach_money_white.png | Bin 0 -> 1915 bytes .../drawable-xxxhdpi/ic_bluetooth_white.png | Bin 0 -> 1528 bytes .../res/drawable-xxxhdpi/ic_cancel_white.png | Bin 0 -> 2357 bytes .../res/drawable-xxxhdpi/ic_delete_white.png | Bin 0 -> 719 bytes .../ic_do_not_disturb_white.png | Bin 0 -> 3134 bytes .../res/drawable-xxxhdpi/ic_edit_white.png | Bin 0 -> 1098 bytes .../res/drawable-xxxhdpi/ic_help_white.png | Bin 0 -> 2735 bytes .../res/drawable-xxxhdpi/ic_info_white.png | Bin 0 -> 1993 bytes F-Droid/res/drawable-xxxhdpi/ic_nfc_white.png | Bin 0 -> 1370 bytes .../drawable-xxxhdpi/ic_play_arrow_white.png | Bin 0 -> 835 bytes .../res/drawable-xxxhdpi/ic_refresh_white.png | Bin 0 -> 2312 bytes .../res/drawable-xxxhdpi/ic_search_white.png | Bin 0 -> 2279 bytes .../drawable-xxxhdpi/ic_settings_white.png | Bin 0 -> 2607 bytes .../res/drawable-xxxhdpi/ic_share_white.png | Bin 0 -> 2298 bytes .../ic_view_headline_white.png | Bin 0 -> 460 bytes F-Droid/res/drawable/ic_add_white.png | Bin 0 -> 198 bytes .../res/drawable/ic_attach_money_white.png | Bin 0 -> 565 bytes F-Droid/res/drawable/ic_bluetooth_white.png | Bin 0 -> 470 bytes F-Droid/res/drawable/ic_cancel_white.png | Bin 0 -> 645 bytes F-Droid/res/drawable/ic_delete_white.png | Bin 0 -> 270 bytes .../res/drawable/ic_do_not_disturb_white.png | Bin 0 -> 815 bytes F-Droid/res/drawable/ic_edit_white.png | Bin 0 -> 378 bytes F-Droid/res/drawable/ic_help_white.png | Bin 0 -> 703 bytes F-Droid/res/drawable/ic_info_white.png | Bin 0 -> 530 bytes F-Droid/res/drawable/ic_nfc_white.png | Bin 0 -> 406 bytes F-Droid/res/drawable/ic_play_arrow_white.png | Bin 0 -> 318 bytes F-Droid/res/drawable/ic_refresh_white.png | Bin 0 -> 637 bytes F-Droid/res/drawable/ic_search_white.png | Bin 0 -> 591 bytes F-Droid/res/drawable/ic_settings_white.png | Bin 0 -> 737 bytes F-Droid/res/drawable/ic_share_white.png | Bin 0 -> 625 bytes .../res/drawable/ic_view_headline_white.png | Bin 0 -> 202 bytes F-Droid/res/menu/main.xml | 12 +-- F-Droid/res/menu/manage_repo_context.xml | 4 +- F-Droid/res/menu/manage_repos.xml | 4 +- F-Droid/res/menu/swap_next_search.xml | 2 +- F-Droid/res/values/styles.xml | 1 - F-Droid/src/org/fdroid/fdroid/AppDetails.java | 24 +++--- .../views/fragments/RepoDetailsFragment.java | 6 +- F-Droid/tools/download-material-icon.sh | 74 ++++++++++++++++++ README.md | 5 ++ 105 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 F-Droid/res/drawable-hdpi/ic_add_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_attach_money_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_bluetooth_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_cancel_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_delete_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_do_not_disturb_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_edit_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_help_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_info_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_nfc_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_play_arrow_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_refresh_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_search_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_settings_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_share_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_view_headline_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_add_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_attach_money_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_bluetooth_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_cancel_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_delete_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_do_not_disturb_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_edit_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_help_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_info_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_nfc_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_play_arrow_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_refresh_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_search_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_settings_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_share_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_view_headline_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_add_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_attach_money_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_bluetooth_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_cancel_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_delete_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_do_not_disturb_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_edit_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_help_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_info_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_nfc_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_play_arrow_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_refresh_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_search_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_settings_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_share_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_view_headline_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_add_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_attach_money_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_bluetooth_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_cancel_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_delete_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_do_not_disturb_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_edit_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_help_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_info_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_nfc_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_play_arrow_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_refresh_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_search_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_settings_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_share_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_view_headline_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_add_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_attach_money_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_bluetooth_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_cancel_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_delete_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_do_not_disturb_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_edit_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_help_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_info_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_nfc_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_play_arrow_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_refresh_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_search_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_settings_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_share_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_view_headline_white.png create mode 100644 F-Droid/res/drawable/ic_add_white.png create mode 100644 F-Droid/res/drawable/ic_attach_money_white.png create mode 100644 F-Droid/res/drawable/ic_bluetooth_white.png create mode 100644 F-Droid/res/drawable/ic_cancel_white.png create mode 100644 F-Droid/res/drawable/ic_delete_white.png create mode 100644 F-Droid/res/drawable/ic_do_not_disturb_white.png create mode 100644 F-Droid/res/drawable/ic_edit_white.png create mode 100644 F-Droid/res/drawable/ic_help_white.png create mode 100644 F-Droid/res/drawable/ic_info_white.png create mode 100644 F-Droid/res/drawable/ic_nfc_white.png create mode 100644 F-Droid/res/drawable/ic_play_arrow_white.png create mode 100644 F-Droid/res/drawable/ic_refresh_white.png create mode 100644 F-Droid/res/drawable/ic_search_white.png create mode 100644 F-Droid/res/drawable/ic_settings_white.png create mode 100644 F-Droid/res/drawable/ic_share_white.png create mode 100644 F-Droid/res/drawable/ic_view_headline_white.png create mode 100755 F-Droid/tools/download-material-icon.sh diff --git a/F-Droid/res/drawable-hdpi/ic_add_white.png b/F-Droid/res/drawable-hdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..72cedcad4f1bcedad5e0b69728dd893c52edf322 GIT binary patch literal 222 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6b$opaSX}0_x9RGP6h=5W{1uHFWkCy^H1}O)P{mkk7qly8JQr!Ds4T702EwEU(nYs qt)QS_%>n}004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00MDIL_t(&-tF06h)iJ^$MM-UJ2U=GDGIe@DdNI~B%;>e zXfNDIQA=)^c0-8`7t00NE6Fy6;)0^RAmq<%nUa!}HW^#wPhs04B?+@@cfW4udCxiT z*_q}&FWdW`%lXyw=}hN+pYxm;v487|IgkT6kOTe4pg8Ipav%qCpelmq(dq#W(?lwI zP?FvB5VXG2Nqv-{Eqo+w{i2Q9C_o7=G1_&rvOljp&V9;RcNk|Dbk3(}kr(8+MK`_t z@%-9u613V=?gkr3mL$^b;=M==FxMRDq4F$k@=Z-9FLD=5f$EjJvpz66h0i6qAL1TX z5wuNky0zJMkzHp7bVL+6sLd`B*)}tvymqR3hMEo_qjePg<9P?|wO z>S3nsCjXl9;D4osgiX+V20VLpKPOpe?=@(Dz#;mY(`;mdjrXz+&DmZgM}}!px3GKo zt!+65xlUuWE$jjwgmyxYSsrbZzlIzmTG9wv^BtqS8aPI;7WI^=(N90-kfDn&0i@?l zvNMjUVJRK-`H(K#oDap>&RY*@mED0=it`HSw*3iOR%!Ipwm~WS1!=AQ8DpmawNxKe z%TP&yi=p|w0(8{G9V&eRd=;8LBtVBvfUcJHDBl(79VS3qMTvrT2VSY1bBj!X5_}Rx zo-ivQxk5Qe-k92lnw1UjCudkD1}=3pa)n_f*J^HK?DCzJ707dsJTEEw{Qiu%Ina1z zvJt-em~HlOO_njzg8~gv^h29@HwsLVQ_*#dbu{xx)53n>MCI?bm7iBL*y+004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Iw5L_t(&-tF33PQx$|1<**eB(Ms^f(q;>z=A4K0!dmb z76Z$m0)~$wT6@ONYex}E{5y$`D#vqOr=Vh7qnJPwXaebDM*FKC8z>PeB`66gEhqsg zHHheTY#@-70|b%sfB;f15Yb&u(1Mn98VHIc`4nBzj))oq$qb4jxl68T_xpI6bPW2U zHh!NI0)jnK@0k@a&!>L@tH#h zQX_Cw&^OBQd&~ADnPdN3xX#t>7qm2RY@mAhkZ%Lk!-squs2)D#+d#m%Vh#{=%$Xhp xp1S_?L*S7elaK5}g3)|xF?}5~fhLfB`~XTO;e}4YGcW)E002ovPDHLkV1ko~2*3aU literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_cancel_white.png b/F-Droid/res/drawable-hdpi/ic_cancel_white.png new file mode 100644 index 0000000000000000000000000000000000000000..faa409f8023efb648d5feccecab9b63047906b44 GIT binary patch literal 893 zcmV-@1A_dCP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Q+%L_t(&-tF4WPFg_}!122(RH}AWkoYXMN>GGFtzEG2 zAvC%%`Wh}+m=H~q*2JBHT{KaV5LUM70~q(lMAHqR$-fKJ51@1A!di9Xi}{O_M#gSRgS-&=ecg=^#ai3)V?S56V#G z5z~02!fbS)d78LHgN%RBG>3$V1JXW0EBqjA+*9-einEKaC=m+_dPxnRamqv(P>S#P zj<0eukpCV3oF~$R3n=dSVf`Iv>bax_?Gj~_Y(XnT9Yq_^w1>Q-&r4}@&>@3}kEWn` z@ixaS(=z(O7E62a#JfnI1zcOA)|18v6Cpqx>5QIT9Y09iF?+c4qvl5(`1Ik2e)?HeZj{vXdd zbD*pSnYTIpBv%eJ)SzpNrBNc;aNtS}y0u!ZRU|tO+^9hv2u-r(K>I%e4c#O2HL3Hy zCbhrUKJVwX-*e^^`YM6EkHom;%xM`GCzeacf-}dgII&(_69(mnv}U=G+B}yEw2KfN zH`2LWF|LQ=sEZSItjeI2P~^WgOo09HC85ZFrE7!KEz3z)8&oiEgW^K}dZh2mW`)1E z`kPcRZWQBUJN??Em2L<(s93hrGN)$N+h+#0*)U_e9dj^{?U)bd`$K8&Bj?M3mfA!GR_yCxon(I@+}h1CF8C~3&{vbm%WRH-Qmk5XPzI{c-Z5q zn8)e8qnI(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6g=hW;uw-~@9kAvKE_0m)`#V*IHLnDaQC)^<#UTI5V8(japl{BYyD1%j3(P0Op@83 zy*KXJvwZK_$+L@#7f-qGrQ$I`!I78stHp&{zqK>4$;7 OV(@hJb6Mw<&;$URHE|CB literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_do_not_disturb_white.png b/F-Droid/res/drawable-hdpi/ic_do_not_disturb_white.png new file mode 100644 index 0000000000000000000000000000000000000000..bb07825f13bec9d177c50cac784f2e5cd0722f5d GIT binary patch literal 1171 zcmV;E1Z?|>P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00aw3L_t(&-tC&rOH@%5z(*xV9i2sJMi2$I&V+DbqM@0z z3UblEP>JTpqHrxy5eX{f${b3mMPDQm6ig+Fh_ucYHlP-%gS5*j`n9RJ_q})DyYIeh zxS0EPGv}Q5n|tp$_uO+PMr?OIu{=;7C=c}aNzl#!Ddx$LB}a}d8Rkhbz-h|%;uRUjxj-WaDIrE7Nessa8r`-sk|GlwLjfCj{6-x*^$V31F2rn|4ykIs9g|TQ69R{kqY=K=(nkX1_ZH`PaY5*NzBl%s)161 zSf_7V8x^TX(7me$nis^H12XMsNM{7iIW0{6|4LBIqrvfwF>FLgPMSj#Med zJ0+0l^o7KB5GgLz0knXMH4U2~73l$qOSV^Y^39NvdP^p5>Kg40YJyawx2Z+9Zx7+b z;gMSP+&-l~g)Jj>3zFk{N5@pi!yp{elpuFS4Rl%%`x5#L*GDSgiy(DM4OAvM{_!0{ z(oR2Oyw$nxMn)PIzEAsSjAFjKX;j<|MoO?HeA^Y2KCK}h}5y{|Ip8uVPc z1|_T`C8f&R)8KnqlVs?+;dM}rG$ei6pmUX;m8P`TDpG@V?@R05?VBCNhH`62GklUt z$tQHnF_V&+$6C3O9+EgF^y@?WCC7v@<06$BDeGO0RT=7VsE=8nrL$p+o}&%}PyC9x z0Z210oT2bl*kWEfjC|yn5m1A)l>uI`$E0vx!J?~helujKOfux^uPyl52x=`Xx+eQG zeVS)nf_{&F$Aa^)2-ke6;FZUQ++>t`3VezQ^$a^a_3vUOqPC}s8BsXyS02*C8IDj% zoFbCc&`LK`Qu-y1+v>d&u7TI#`D??Ctjf{NTAbC~j{x(F3IC>)SrfR4H%2&Vck$O= zo4k={QVQokay<1qKyCk#po(r@yV`Q{_)eNGe+ReySBp~GxXNATSZ0H5w%K5rImWp{ ltD~Tt|94CtC=axYp5Gf0Jz!aR6~_Po002ovPDHLkV1mx56|MjP literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_edit_white.png b/F-Droid/res/drawable-hdpi/ic_edit_white.png new file mode 100644 index 0000000000000000000000000000000000000000..34ec7092f142ac50ca97d3f18f1486582a12d2f6 GIT binary patch literal 490 zcmV004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Cl2L_t(&-tF44Q3Npz1<<{O+ie6w+F_`LA_y1)>M(F@ zO#~1?`U#kuIaph^bo=EEBsK76Y)e1zfMHub1SmiO3J|Mz*lhZa0*QNMcNNm!|5D4O2OrRct47kN2WY~KcU(7- zKKP*H%W5Otvq7g1y-rHF;DY|o9hsE!i;V+3LDM_hlo%l72 z)~p-k2OM zh|bZ0bev}kM+0)Dtspl}8_1E<0&?ZlK_d>HoEpfTBZG9Dmo+#Ns0*hI>clC5 zx^eQLj+{KGBPS2)$T<&0;G6@ZaO|L04#x_jacm$W#{!~q^dK@v3!-y$AP0^H>P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Ur2L_t(&-tC&tPZL2D#~TdgM}d>IP=6#G^-oYz2o^YE zM8$aW9{`~j2q!NXOpGRAqVd2lCD9WQIFLY6prIjHOjHiUXsI`orXgOmEy~xE$&7U7 z&CJejJoN2x-}~mXyYuF~nOPrv|JUU!1C@cwK!2|Qx-fznY$JmliYOw747M?Y5p<%~ zOP~;9NT3Lr6|jjxoN*th4a@iiRXf5GBJKgTV+$shK&ySAFy6q4&7sjIP%pm2sU4!n z4p0@I!a;1v-)t_##l2#%52`G`=st^*V%=ABuk*=vrN zmqO}5uZgjvR?F+~RjTt+6{wwf**7`wYQ(UMJn~3k09D+*+k{d?4QPuAfxk3J|Tkn(Z)(&7^l8 zHDaJHD-A{;qj*Qbze5Z(VgYBD8)fqf5yN7j8OJ#LTr+=BX~{Gt2HJLr^9f-_?G!eZ zqLN~uj3b=ST-oqpTrn;!2Ff|WIlu)@>!EUFRt#jG^xx3TDUE8x7VHBZqm9$KuN7Ue z|H)o(Dg&C)S=)oU$PMOJ{ML+5+fMs=F4gz7)%S-Qap*cwuJmrKXtSxub)atD zP0y#U0zJ{+h4o|AHJ}w#nrxicBjpOv4g%&!3n}Z#zVmOf&^vso$C_1~71`TSJD&1m z!WzzF@z~Kj!0XYPmqhbUx6LEY5P5nbFA~%8Ln*UE;0QJhOK6LG$K)+o)M(*~SSVFM zZU0CRL_gM1AX@i0`s4|2`&Ww^bYK`$NFt3a3Me3pG?JLa5U!%S{4+`!s0{SK0{sC` WqUEXTJDWEE0000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00LM^L_t(&-tF4YN*hrW$MI|$t7%-BMtqc3qS&g-LbLEG z6m+BHAzG+~cBO_^Xm^S(3JRkJLy*u%2-$~0+&Potw~JB`>z%p(Cx!XVI>U#|+&gE^ znF58;Itn=`2j!r@M~P(`H0jVMBqk=LPlqNAmYDDfs;X5YTXWAxcg&RzBP2CP?jL+E0Hbqm=Bmp*a!ZQ<4g`e0?mx?ZE z(gEiWsjLGUvwX4c=Tv+zi9w%mnGdR<6004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00FH@L_t(&-tC%8PQx$|hJRO8To5N9Z~`c-5NALfq3jW$ zii#_>ci;@9MU;@6RB978!v=)Y#BCZoHX<`wd0N?D=H-kNLHJwJ0Ru76zXu91#0wUn zhOZPej35i^ul+BiIk}XW!aRYXymca+JN8kG3w-KacIzAmIK!PjhHkvXac|Yh6Yvs0%@r)BY7OGZPhUEJg3#$Txwb%=o&s7at;!^i{B5u)5ULTLPG zt6Zv7(f8=1r1g^~bDCM`0jC9BKi_A$b=hEhpVjEi@{VZ}fU)UbR3=D8{F^+?pR00000NkvXXu0mjf D)5-5t literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_play_arrow_white.png b/F-Droid/res/drawable-hdpi/ic_play_arrow_white.png new file mode 100644 index 0000000000000000000000000000000000000000..043acd808ec133c8353be75d77ce9afeea456c9b GIT binary patch literal 399 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g&? zz`)4v>EalYaqsPoje<=M0<90{CmAXo=i0)$kt35S`vBJoQx(f%<-Xf{E0uSP));@d zcQ1DGg;{D2j9dW?Od<;ySYM>emFRdaI5OLq*^`~cpfD+n>ENlU+^hLG5|%w($pi(>HJTxmOdS z)&(8!_hqzyA!w%jpmd$1B;)t}8fk2Q9KthK9&c-EsBTyyx2B6ZkK2f2hwKTB2Vn|3 l+l!xbLVcj&@SpD&lWxp4#x1KBYXifH!PC{xWt~$(69D^8i*^72 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_refresh_white.png b/F-Droid/res/drawable-hdpi/ic_refresh_white.png new file mode 100644 index 0000000000000000000000000000000000000000..72128fe69086bd90233bd2f16ca25cd52f423326 GIT binary patch literal 875 zcmV-x1C;!UP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00QGlL_t(&-tE~>NRwe4!0}CTo{HE;x%AM%xu62Y4l4=5 zL)5J>sA)kGC5fenLhzE>F|ZsdL3A_fP}i9R5)?rwRM%VObYIzLi4$aA zihqXFZWXpZqu8**g`zoKtYlnHjrybG@8@_{Ip!(53P$agM%M4?utis$x-&H*Dp zk8kE-)f!b=q{JT%hI7$0*Sxmp~@O40KeOUgyh7>7%-wczKq z66Ym5OSRWg$yI5pEZi39rJ}T(FFuBgtmZFq1JB0n26^b_)jEaWHD1u2f6Y^>?Wt{? z004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00Q4hL_t(&-tE~c&g|N z5EJwTh$skwz7N5N6vS$Uo>hDS62XHl^&maQClVj%p;|jXTSKlL$Mx zh5i5XAu~I(vlAlJUY}3^3P1rU06i;E4>^`7a?Tw!?x;{?ku2S<0FAKC1J3P%Ery#3 z8sr;Kw=WDd1N4Gr+_pLeqW*%?e8;RElJXDqh6~JFg^WL-v;*fS%Vf#WNtjMD}1%0<#`K<4XOG1n67|^s*}TDfMLu(47+4=>Zf|>Te}L zH6<|Y0Tfp1Ywhq4#g+Pd2~efo#*liK(4rE!;sLa#)Xz(RvP$5!hnf19 zQa>gE>ekk>DZ|JaZ7oa4toB<<=p}K3zyv>(+D)m)k74bp=959-XRY#`+!NzhEjaHo z?!8vEC;13FV0k{yyW-TfIr^>auH%Dc7qQ-OEF~GpB;Ye?K2J}lXga0`t3)`)^Sg@18pmG zYD#jm6cn_(r%|ghM}!XTyAv~#yUY67cfr~Fd!u6^rKCY2LJYA%^*@vbg017|>C;&ZY&~Mf+D9V0d!fyZo002ovPDHLkV1g`zh|d53 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_settings_white.png b/F-Droid/res/drawable-hdpi/ic_settings_white.png new file mode 100644 index 0000000000000000000000000000000000000000..6bb8f6e080aa108b72cf86f0963a3c309440b4ea GIT binary patch literal 974 zcmV;<12O!GP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00TxzL_t(&-tC%ONK`=>$5(Te(xUPj>1rX0iHP)INJ2!= zTM#5k6k=FdP*Km(1#(xT2(ya>f=~*Y5ZxXNwTLdHDUw7NO4BY@W?GhsznTShOU=po%aP#Ll~WFDwd3RGtvs9g$l z-#pN;6zG#_paiTU+AL7MCD019KwB+=wwMKSS^^!OYM@Y>7@(RQpEnWg;I$=C52Zx- z^j*$5-cwJAK2V8U`T-?Gx}O))gKE5Dqq|=e+j;1IQ=|(tk9Qu@L!2R-1g@hSSBWEs zYQA_p?I%hP=$K#eZv+zld%1Lg(img9<2y^$fv#a9E~x^o#Z;_T0Se=>F`!OnSp)6C zWbCv8iebIoCpK|2kKD!t@dt+IHUmA+1x)JE88RZzL%Ib5Q| z7$&G>f6kpmOK0P-oJ-}tSx8)5H%Lu0)^dy0UZ#?IhBE20)_qm2+-b{He{kn-L4GOIhB(- zlPAJu?@+=?5ia{~GIW6orFqt+1XLl-^#$rcb}B6^v_s+hSUHE|{_3Hx&?0Url*Jb8 zQk-yZE2uORZv~Xk8%5*LAVCV}qM}M4c`~3_Mm4Net6<<#l)lbneD!qI-ovG5VMiz@ z*$bvZbHhDGjBLnG1z_?!sKtegS~>$T`6uLaXdxesZi5Cq9kstzw#z6`smfNm%NWih zLRAvu8e>4KR7=NYBREa!wJen%(~dLB5}o3DRFHQ9iD8|ciwI~gZ#}vXQb{JUT+zm9 zB$_O$`0Q0OB6a=H@3ZXmKwAn1sN=u;J|NH{a=5=@R*RMEjv`K;PnFo>|+FdXY wbVLet&^%B&&lwlAXQT#yMhOB1fo3YuFU0)zMLKxvdjJ3c07*qoM6N<$f_WFC82|tP literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_share_white.png b/F-Droid/res/drawable-hdpi/ic_share_white.png new file mode 100644 index 0000000000000000000000000000000000000000..9963c6a056186240454290d419181c9bc3ddbbd8 GIT binary patch literal 857 zcmV-f1E&0mP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00PlTL_t(&-tC${OcP-k#%W{ApinwEF!~3iVRJD~h9H6s z>TV1Wg;@t9CL~Qri~}0n%hI#mbufmS5X1<7DNGt@O2_rBlvJkMPnb=7s$)qrY1HK2nZ0pes>roa|Ur0MY&sGavz);ux4fI8Vg zRqJ&40o3k+vx^jy1_~%%R(Z`y1I5u*w=7Ub3*^WGEo*^tvOonb(55WVcC`V0)dDTb z0fo7@&l*&$loU{}!P5cgk^nlxOU&x6{e`iS#|E>tTX|aT0tL9q57%wq@yi3}g5Aya zJRe*){mu;noaT+f{Eaht9~CC+DQA;=)Bin%#! z*9Lis^EE!X&VA#$ZC}=IurU_L`vFI78%3Qs->YYwkZ`VXBbd)zl(NzpgN^a_|Bm-T zfb`;gK{jmoCvk2QXsg!D{oBL?2CUp5v_X@MqWfu^-USy`Z|Y6Eh#KuKAkAuUj!EKpQ4XTNEb z{^mNmFVKkmN8qqR1{Z5a9aq@?Odj>vTxV$V>qmkR3Gx-aGvXcQ$o$a}p`Qt|WJ%Je jYUSGBF*TqXklgVXi6sx9(T=+a00000NkvXXu0mjfFsOg; literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_view_headline_white.png b/F-Droid/res/drawable-hdpi/ic_view_headline_white.png new file mode 100644 index 0000000000000000000000000000000000000000..4f9458b1ad018c75af6f198bf61f0ad1d8d53ed0 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhaw6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6pZ$CaSX}0_x9RGP6h=5mV@>i|4-Y>6nrt=$Cu47V)LuD##|g=u);h;F8|#h?FVdQ&MBb@0Q4<48~^|S literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_attach_money_white.png b/F-Droid/res/drawable-mdpi/ic_attach_money_white.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f1e55676453bf4a1d246486a7be6a2e0e7afee GIT binary patch literal 565 zcmV-50?Pe~P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00FH@L_t(o!|j(pECfLm#&7Ljp}2@nK?o7OLO~&lQ;0ta z3K0^mKZQ{7}1%Q79uxOZm04#im9RU6uV4w}l93YQr)aV2B;0A?ZV+-Z#02Ax@ zJ#JC03RrU_d_)pSWVx9%N>u=@+=eWnj^-P2M9Pc`V3*SL72K&4raZa%x|o<@NfH9B zo#0LKfILgFA2PS%5)(kXS!sfCpJXFRigssBbehCkv=2z=TjwFf}881}T;z zE4YlEgx{A33~=RPirc7K%;ST!ewAAk`(djT(s*&q9jY14!X}DE91(RVchgxo;~wKE z(N&9Du;6GNSJB~CB7*i^JHVtq$aX6Z!GCf9&^004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00B-(L_t(o!|m8XZi6ro1yD=9O)pUO9t|Y;DuuQ=N)=T- zUezs@em4ciFb;oQTk0atCSdVjz%Vv(aCJZK29yN=;I%ejBys=~kp~!vT!1F>0g^~{ z+~Il{3n30A*S}l6Z-DU@8JPQ6?otYefJSTC8p+hBGn;@lLF90+SUjOIGpG0 z5%)Q_=}8dX2Jmz5h^O4=U`;Fl8NdIa#l+NP{Nb-pawiC^zU{_yrJ@tjvW;j;)gq0c z;`w6dS480AE!EUVW6+eJ4`QG#4Qww_TN3cfAd^IHJWW{QgSbU$!V=%kz=0<$@v_)8 zEMbX{%XPEqH3D7004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00I3;L_t(o!|j;OP8v}dK(By{x@$Bp+R9sC^g)bH5MM*| z0kpc6kV$NJDkdas8WNv_@B$j$h|_KzLUOvml+o}0&7C%N!N-m}IfvZ&e(pt~{_RK2 z1%E?OVv|F93`rO>CSgdA12!lX7gRar0aM)bm9=oeD(8%GMM}&{h@inwd?O*sCs^b= zc_XIcD_G)&km$4IBUlW?PZbrn;2T9Eb_p63jUAg{m0yJgPb`}S=i>1Rb*_!D3mQzs zw{4T4D#~iYHi|MG1HOzR5{i`1dcmnc)L&L~iYZ>9*cEHXIzdU(b2@(@Wv&ePvSthK zSdfBE?KbEYc09nk6dW2}4}G?6!eU_@>=18hprS1ExCP}f%@QKAO(rFd}azC zJdC8kHCiq*3Kt&I{~$=b@yb%&mMt23;j=HflkX+>yztkS>UN~*ffwH1KxVNo1sk4t zeRaQ$ny%U1d*a=STV1nLoQMY-mb9Fe-kw-H($9l6&A^E)SwB_O#lU&ylWAnexku)n zW@Mh3XYz`p+&1<6y#LRaI{K7GJM))m9*{Th{#;(((%YYFnDI*4S*{3#Z fY4g7l-m~C0=7T5^qgb*_00000NkvXXu0mjfw2c{^ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_delete_white.png b/F-Droid/res/drawable-mdpi/ic_delete_white.png new file mode 100644 index 0000000000000000000000000000000000000000..cdb230c2f2bc051867c164c35f58cd410efbe139 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=12eq z+dN$yLn02py}psRK|zG&!s4*UQauT|`43H|C7m=oc8hV&LDifW8#f$15uS4**0WH5 zXA8@eu1XvccwE<9|DTMkW>x0R@K!2eAfc#TDf% zmKt6-X~VHLt4;qLnq&W&UEPooXxFznX~*j4=F=Ry=ByTtX57=Ldl~2=22WQ% Jmvv4FO#qn}S*-v7 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_do_not_disturb_white.png b/F-Droid/res/drawable-mdpi/ic_do_not_disturb_white.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4fa1e596a56293ecb4a4dcf5ba83658bde3451 GIT binary patch literal 815 zcmV+~1JL}5P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00O5;L_t(o!|j+)PZL2Dz-!r3Y-6H<_MpNi&?X#8$_Fvn z1F3%+8;+PDP!f)yTrs2xdeIn_CcT&@ehW?0gGv(v!3(8|k^pUpKM&51+wM$vwrJvk z*-N|cz4=YwygxGy5cs!G;0*9L071sdQ=&?P7A+c7DKSrq;OPM)tndZh*x@0=y#wN` z)52>sDKOLvV3Mzj>3%994buJqA)dBPu+K9xB)C9;AQu_s7SGvl(hd@SyB=s;NBfG3VM<&9$f8k-JBc>pHc{CdKK5hBJr zX>+q1AkMdr_%KDp<*(RdP!Cv_zRz>w6OrIZSgh#)BQmX~2{7RsF(Xs&iW;yY6h4Vq z@QVmZH!Z6GL0NNd%XfYeS)sknIX7Tj2*0eBuU5HY=r>`hK~-9#@$QoqS|m}Wf!u{$EC57T)edg+-=-4Gi>GV2D|5&T2d zSq%|USt^pMVz(oNcMZy#8Zjl_ZK;Y~fJZ{SNvJ18KcB^$C3Qa-mboxv)L=Rh3*y@` zmvt?(AY0~j--tz;;`6G$ClASVs}c8!u*YtDHS07;OU3s+B5rscyb4maM#2cO=$QVo z_sPzpEY**A;1N+_7Nqat4Si;Z-?C#(HL^sNlSi47f%$v-^QOml%i$=1174FOMVv4e zF_PrikXdUxr_bL7B+VYCW4Ci_&8|>`tU3Iv8^^3_+N{wf@G8q}YbW1g$(gmiUt`CZ tqez81O`6oHP-K?mpOSX=SHk}c_yuWBIHt^r_|yOZ002ovPDHLkV1jJF}I4okhJ31{w9syt6I zFaG|YeX_TQR>*0;&<3U_XZZMQ3J+&VE^urAXCk)nG&@J?Jr#=wJpV0Ns$IpKcW5<8 zwJUsl)+i-wXTjEC&HYDyt?!nI;sXW6HH(h6-Bv0$Jl-;WKG%G-7RFl{?y;ytFI zpSs^u)z9s2uzAn)X%F{_&pQuf=h^Wqe6nPi>d%~F$9e&zY5M*%Od8MF#f4XFO!+Mg P3LpkgS3j3^P6004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00K8jL_t(o!|j;ON?Sn`$77nLR0<-B3%|OlMY=0ARu|HT zkb+s*jYy;~;Ku_P>sFC~`UHU#je$NvH-bpW3sfu=W2&OIO(^`jnOu$c%$>QD6uR(a zH}gN|7v{`4Gjk(E{_QXF0B8pw$^^^gDO06RohoJWEHO^Bb-*xdd_!j^tTN;d=wpjI zCaaO9*99=c4{WVUDjXor4q+`z%ofneXI!jErwt&k34chM5fUVL!8}JAM9d7>794*v zdr#FtnqT5V82~e4_(?HSZ$-p(5TK89!DUvv)8+;B89jQymbmtf*_Z5d!!>)1dJ84x zVp9hg7RxH_g}?O1EXeMjwkl z$8`7s6QPPK7yx5_z;YAN@xJcb+r@0I!~UkvA*(-Iwc>ViR8aZB=?(oPwbCAV$* zTx=Vb+jPkS5LepoM&*JZFm3^8X#W_MNvYYL&@THbHM7)575fk$?i-=b4;3p}yCzQ-K) z(}w@w2R}ekoLL7yy`NgUCsYrc)GW$sy?JAXdjt-W;W(h@tlX_y_h;-Fi{vO#p+=1g lC2}l~ylrU@pA!COz+amLN;(}y`u_j`002ovPDHLkV1hF$BW?fy literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_info_white.png b/F-Droid/res/drawable-mdpi/ic_info_white.png new file mode 100644 index 0000000000000000000000000000000000000000..bee33abb780f054d0a6a66240da9a1f96c9c3c39 GIT binary patch literal 530 zcmV+t0`2{YP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00D|gL_t(o!|m8lPQpMKhvAB4Av*(Mp~5X7a1at%a1Q<) z0F*6+G(k^5!p4wzj;0ro;6|u!X*aJ6V$@9AnNEl3f?v0E9%yI2`34K?xR-STOaM8W z3|Qjf6B6R%v7}FfTy~(!m{;Pm;f{(vP-aeuECCKB9iYQIs_|*31M)nQHV(EbP~aJz ztSG1edClPuge?ai$Pz~ebjX_9B%sVkW?)A#9+*pR{`y>UW^q7Ox~$ZIfa_>rEdBXv z0F0u5TtdxB3#>U4fhHzX7lDCMpeF)LqrgH0Jfnas0=~j+sxKL~A`lt{fDusl2nZS!oad#JO7YyV4x~LbZLjH6GMd{PxKQkT$y%GpD6< zgeo#4kPg0Q@_S;YI|SY^Vx3TPhCf%W{xP;rj|DDU0s^+UEa+1EE@`K~68k literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_nfc_white.png b/F-Droid/res/drawable-mdpi/ic_nfc_white.png new file mode 100644 index 0000000000000000000000000000000000000000..428351e8eae3c6219c02b004f0c14e8dcec688ad GIT binary patch literal 406 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=14Fw zF!Fi2IEF+VetW~4>yUu}+k-m}D-$Nh9kAxeV_C2ENV4XED_3mSy3j2Zj7x8aiSOsm z%$)T_Fl|%Uxz1WUWr3n% z?7m{|7vj~n(hdF98_N$*kiX%%Fy=FJ=f3-&8E3LA`QxC%sAz9+boq_y1T3eO&S>-KQ}C7S}T0Ub-`P`Q)U-$opL+z$%d(IZ{fNgwYqx;);?-f`r&dc((*3$YWwOk`G`tao|pwDox*U-pzN zcCvc2)-QP1lWII=I7{Re59^(1Ly+=Pgg&e IbxsLQ0F7mBiU0rr literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_refresh_white.png b/F-Droid/res/drawable-mdpi/ic_refresh_white.png new file mode 100644 index 0000000000000000000000000000000000000000..5f89fc257b71bee3b3e6ca55d89e57a3f3b2d4d4 GIT binary patch literal 637 zcmV-@0)qXCP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00H$$L_t(o!|j(%ZxT@yh7oM#OB;7Zi7_}Pu8cndb<-t~ zYFzM7gw|H5E=*jYR5pz+OkDLxXiCzi!L==Zpkg#GG&D?`Jl$MwX2NjqolzE=IkSPx zd*5g7aL&1h2$AVtkq{69{ujV$Zm~|A@7NsigKb*e=8T83;vaCC76ZIuz%wkDwhI0L z2_ABYDs0w>3-uEC16aJpBs%1Xuq?hA1Fq3`n7m_!JQ-$*G0%1Ce01FH)0CeL0hYsl zj{?y##YxJ1@z{?MAVEhi*dRG^muJ}w2B=GUO_O6$cKrh^Z29!MBwh}(gQk4a6+h+iscd#ng}h73 z$bOEVSnIcIL}S$4s-a-l_0ZeZtOt!jvX!p z9e(4~004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00GBIL_t(o!|j*LY63A7hTW))mxW!s@v^PrLPP|`=TP(| zT6)p8I=+G%rLQ4Msg9zpbYBD?Kz#;B@b98&jC7ndv00RQW;Nvh^JOw5=NtzdY9Gf5 z@ZSLmG%&;pCQPhgKu7%d0C~K?DkjJs5Afg-cF~4*4B!lJ;KdXHS%8O`ev1wY2*C*# z!YHDb(a|KKd17Q9y$3ebUyJ zrr!_~12l-O!(CaADC=T?A+Z&>fD%#m#Q-Z}3vmGvqFjgpOk#6#0dAsP%K=5CiDM*5v}8h_WUIh|>d2DqE;)T%Eei d6W~t*J^)IQO7UD07i<6k002ovPDHLkV1mcw=^g+8 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_settings_white.png b/F-Droid/res/drawable-mdpi/ic_settings_white.png new file mode 100644 index 0000000000000000000000000000000000000000..12e5d100dd80ca03315f8d15af6289c3db015327 GIT binary patch literal 737 zcmV<70v`Q|P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00LP_L_t(o!{wOIYZFlr$0xNhfhN^PB<61QP-zeTfPz1; z^dQxPr5C}!K?FUt)`L*6MGt!LRCWH-_|9`s9Bn=AF69nLToD#$tcrfQw2R&clt z?Jgx4ZbNGd=9r>Nk%`tk!#!$fQe%ms7H)zf-3#A zMO5-qBbW&=`pPULBuH?KIZ6TOX)nQf>OKZ}23)5jMZpJM=bUy-$F1*`C+11Wn3duS zr)+tUWkubb0Yn*T$_n}!2mKPQp9db3S=GHD%zLo++2fzdg0N{AjaUgr4W#46csp93 zY#W-S)GbMsl%GbQj`#LfL70Bfkj>C_|EMvq1{1{Pkz@?rB`bmXuj#Dh2rdWEd*6rEAeH9$d4v}Fsr#JEN<>Dyikq>Z;<_H98*skj_azRJuqN|GdF z+*UVdr(D#wXf?8O{ala1@rWAEHe z8?~_0z4vCfm-XgC-*S!p%uw;&4u@%mxX!!QbuAuLt}jp_&*|2D9}E0Yoo{i6!4~c; zPjQ$Gx;pBkXlbM&Y{Ql7^)|E}^@E-hthJ&2H&@^&AFVI6{?+Mc?5_S-(Q|^oR4iHa T4YrnW00000NkvXXu0mjftyfGA literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_share_white.png b/F-Droid/res/drawable-mdpi/ic_share_white.png new file mode 100644 index 0000000000000000000000000000000000000000..dd536bca2db25dbc5ac58808858f540db25b2901 GIT binary patch literal 625 zcmV-%0*?KOP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00HSqL_t(o!|j(bXcJKw$1la^5}|YwThWSxf}1#G5D@~+ zATExbibJ;sY&CImbJD4EOHwJJ&DNz|Iy#t`gm`I5MWKTfT5u4JwOT^^b7(oL&yXB_ucf9+*|M&kKoM<2C91uMq$qQO^>Ee+iAq}|BC)8?lO%jk~lW=X|$^mld zYFZA^)B+lEfWKP6pQr)*Qh@vX)B-+80Isr#sj{{JmzV4r+c)XA13YHSZ+6HmA4Bbj zZ7$kv)|6)NxPwESELHaC@Se%w*PjhA$P8V-`8H#=W*1Z5`ebHm22M?&_sPKhJMD@X0-L4vkXem|Jvg6+V0ZfJhU9G4^}n%*71b609) z>dou7+)R!f&@dcPN(%7Q@RN^t%a8=XrDc?9hdgonTyzC5hm(p+pOh5*dl` z`;gB8`7Q-SQ4Yu7UbNj$&Jb7t7G=WkWr~V?gO!#FUMaq60Y$m!=xG7?!W)1-!fZ?zEXPrLdf9Aor~7mKvlOGOLOa%%$3;_bK3kW iBfHfFC}v=gV78E%&KW;pq4F7^ItEWyKbLh*2~7Z?wl-t{ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_add_white.png b/F-Droid/res/drawable-xhdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..2bef0595839a40b3d454a20cd48288a45da81a8b GIT binary patch literal 269 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcg6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6m0c$aSW-r_4c+S*8v3{7KiX(|MhS0UcN1%)8co-fdv98hKt|My$4i?1nizhg;mz& z9Opz512aXu~v2$8Rl{>+G`sNq!S{wXM8?M!*oXR_}uy`q%@68CiV Kb6Mw<&;$TV=|M&S literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_attach_money_white.png b/F-Droid/res/drawable-xhdpi/ic_attach_money_white.png new file mode 100644 index 0000000000000000000000000000000000000000..385422f14336a956fe355726ab4689fb9384372a GIT binary patch literal 986 zcmV<0110>4P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00UA4s4j_JUE}j32^!!XV4U9N|7^g})ae}0d{bTM+u)tyY8L(AR(?Z83&EdYew2Jj2hKu*-6H}}qMfLfI-`xgi z6G8|%P;ZrVYFq}W7eeT8pgttYOTI5h4_U3n2*Di=vRJr!88yAO{Z9oT$Wu~X5!R|KXbKrfDf|hxsV*sVk{q`I7@@6q_w?1gd$`8u1U_{?O%&-oc9}`J004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00MhSL_t(|+U?t0PQpMG#_=uya4%d6?90R#DK1N%y(aEO z3L7{+8Dlv!ozs5bY0;@~q&WXy!ewS!3MsDpugHK5$bbyUfN=u8kB4gqkO>|DD!~Up zBzOU61U~?Y;0d4*d;tW4HvmrX2Y?Af0HB0n06>EMDK~PEb#DNSV9$RSkvf4OK7=m$ zl0*<6Lbv=BLi2dy5W41zh@7H?<}u+A(oa1ogU1NXqu>zIPhHE&M7RV{2$qv=1PwqS z*iN?)bO4-SeL{$^Nd|xk_NSy0!hk|1@}Uy$eC9%Fgcv~o51%skn#^qy2w}hmm*0Ih z8X|040nq$D)9E&X4%lMz`>hA!1PuVs@4KLh5iS8+Iji}LAy;JEAfb6s@)1i)op4Tg zMC8~vAG0Rb3GD|UAGN6OLO9BDOuG_R0w^E3t)jn4L;0&Q?hB<7YFs{gV=ILK&ByP| zwGqJijIG5Op_cL*w%0=hLVgDPy_Zn?o46b^Anjwc94jFGi>w?Yz%rGn$2XO*g=$9|BC} z1(|OF*z$D%Jl_I%H05gmLcRqslQ%>DC15JAYW~h|GWVNcz2S+HZ~YYdAz*Gf>z!}^ z9+&}d`UB(-dD|rv4q~#~)B($MFVK=y7}j6@DB~K*box53uKq z;|18W#_<8r@_hird=CIMKmQ;)12P~3GT^@hz5xDQb&W}Z*zN!T002ovPDHLkV1g}| BLc#z5 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_cancel_white.png b/F-Droid/res/drawable-xhdpi/ic_cancel_white.png new file mode 100644 index 0000000000000000000000000000000000000000..b9b6c0b3dcbd42e4eb067fddd0ae68e9b3939fd0 GIT binary patch literal 1179 zcmV;M1Z4Y(P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00a|BL_t(|+U=W7OH^SPhED~diUtX841y%1m>Oy6u@F2Ch0H}}Y(GAl(^^K$kctJJw%ogAd)z)nrfO8(-x>Eh3 z(;VP6wV+v3fB|YnSrdQ;VZi*8UJ6FG@}&7C47OAH0FQ+BFB8yWfRLw&fF8C)-@dO4 z(9CzS{lEYr|1XieB0|`pQ4b)eWq%*JU|?U4FsK7?LJ&M0w6BAZKa|Ex5I%)hFv0;c z0vo-d2d`jYKOmr2@Q-OZz>FyEXf{9yw=a8wrlbIk!ZKA9!dox);dj_&8U}DvBl*)o zu(J>ka9Kof1m{$WVStK5LL9@cfJ}jyMN>;g( z3c=1ktg8WbEYrVA2)6d2tOnS#&S0Yu?Crzue*kdgkI<2Sh<#t9&AzYEH(u@ABFw1) zo;$VQVw8AB>#4tBE-L0JI7P_$38yPZocg)5MrGpg54StE?-rp|l2R@^w(kz%gDfW= zb8cUrJ6G0-4B&cpR7-r)9+HI_#*66`fsy-4!c0rjqWV9qFbh;2D3F`|CRZy#93h* z+NXYU#%A26`tuT5+kvNNRki=beqyVRcU7~W=cJ3Fzc$UTnJX=G%GKCq7i+QGU#&c; zr91fA9W%mLF1j8)?&4F7ho@8OOK14IJ*LMwFFE^Wo`$0A}{q z)m}s~SwZRa6eGQ=95lFH0?G=5ZK+l&MVx-tuu?o=!-@G4x#)P`?;>0ygO-Tn(=@RAU)5#dSvcD6+o92>s@V#_u|A8-3hkjCmGWr_3(9lv!ty tIi8XueYilLEJP;&2|xmn03-nBpT91*r%*2m#{~cY002ovPDHLkV1gUj4vzo; literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_delete_white.png b/F-Droid/res/drawable-xhdpi/ic_delete_white.png new file mode 100644 index 000000000..e69de29bb diff --git a/F-Droid/res/drawable-xhdpi/ic_do_not_disturb_white.png b/F-Droid/res/drawable-xhdpi/ic_do_not_disturb_white.png new file mode 100644 index 0000000000000000000000000000000000000000..38f12a7b55bc0e2bd888073bbf3a629234c2595d GIT binary patch literal 1542 zcmV+h2Ko7kP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00n+YL_t(|+U=W9OjJi4$6wiKV0R%zkU!Bhpj)EBCXKPN zO?pK%TY^>60|_RcZNQS1KZ-{Vt-UlM)Cy{-u_T0e^Z-^pG_|I^lA)=l^{C!Ll$XF<2J6K2T4?JPJlRCFpOEK$P9*X46)4uP=F-H@fNzWj4>QRL4E;B zkiuj5kvVjtI6nX}^x-8;$P4r$8Zs?qut z+Ux=p;Rl6jIf^bc;eC{23miDug6-IcPtk=R@kpvaj6z!gRruZ2_0Q+ww3`Ewpu@+eYxF0lF(drbn=3A(?E z?@;FJRalB|krN;!OaW922G0yWF%r*wh(81aslo(6kzndhB5o|EjN%H{$9?&VBsV@H z7^N8;G#VIxffbII%YFe)x-5655t@xfLGNEa{ub1w*lL1O>34nTwi4;hqixNjd}8yNwQ_$L^A^(5(BAJf7< z!dYUyQ5|5MbgYE5v5inn&OAm{0pjGC>Km>L+X!DXmf!FeQ=VcA>C}4y7j_X6WaHVa z1~AO1{o>-pF2X(GO6Ur}9?^d3>cl2OCu8-23P2UUKFmlEfi$NA?h& zGByv(0eTpfF(pT~5ZDF%j2z%9qtdJB$_~PL#@>(|;0~j5RN0vggyW30TXKMDM&$zy zcjgiHGq$GW0BJ_0O3R^Xgq@6~1vvn_tuE4ZX%-<$GHX%*b}ONuhBJu}VXUm^14KNy zHHY8l1LKa+K<=bPb*5q$y~M}^@3n@A@gX;nhoI@mYWf zt$quhE_00LL}_Q^2;cj!*eb+*uIX#0z5sxOQqIb=mYv&bqUf(e@L|J4d~Brq4V-m< zt+=GzRM};Mq%W6*0uqIF3E46eBU-t?SfJM)RuVv^bA=|qAg*~QAj}tD1 z+ZkLCCc{O$tpQ=@ff==@uUq_GDVWad8_#>_RCsRn=q7tzcIuX^_e$?iFBBB0{_*IL=Zs?J8%Fk=)(5tqKcnw>U(f<&|%jB!HLOrhCv} z4qz=(oj2O~Ik$+@27rSox`oyMdXh%UV;;!y`ypH$rxm`6ag$lJ`wL0@2Lc6X#Hh=f zE|Mi&M+5()Ay|O*0o9C)oPt%JOk)sD-Up2xfPVwO!#IN>+`<$Vu#6R~U>OUT!Xz#s sg+_WnzPSJbze0czAOr{jLI7Xa-wq|`3HXV0D*ylh07*qoM6N<$f+JJ47XSbN literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_edit_white.png b/F-Droid/res/drawable-xhdpi/ic_edit_white.png new file mode 100644 index 0000000000000000000000000000000000000000..9380370f484dac3f00b86319e05b155b26de06b0 GIT binary patch literal 632 zcmV-;0*C#HP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00HnxL_t(|+U?slARa_F-oSNFV*CRE zFX3I(y*p1(!2`H*C5($MikM{Hn|YJpZ)9G#oyqspgftY|1*v=X&;bpA4rl;$Km(uy z8UP*80O)}K16a$Oe9MPC$dPGX;CKyTH!oqOUx^O?n{Onb)5r2n1a$m(zJb7+uO{&3 zD+$c`DgrM5QjxD9u%Ce_0ST}W4yX3mzehekJj;QcE8cf1ojkETN}ME}g$>IkG=%qX zmO%L80BloJ2(Ju)U8bFIg$Mjjv=OeS_r>Jf2>sm7sC+%4pWhpouOsxa-I4imLNvcT zAbx|KCMfbt19acA;{;WHS%CIYZH#cCJqknqH~|c>2mc`Dp;6{1gCHz8yf8Zw1ih+W>_5764_w9zdF}1?1%4mdw`yi1Rf7 z>ijr>JU<4Y&z}cWkUs~gB7Y80MScmuqWS)Sh4Z}ui|6|S?v~H@1VHlr0HAy?04(1J z0L=FQK=bnf;QU+wJUsf$WI55UmyYGA&u SvK4y(0000004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00hfPL_t(|+U=XqZxm%1$7{jb?v^G>yR9Aw#Rerr35OehfPXsXyOeCY-@=g`~y50g_SN4HPA>ndnia1qp{H#4>s-A%~Db!HU%xv z#WvaYBLfl2HL@B+VrrplW3HJ7L-_c9;a%t{DD3E-*+hn`xqnc6u3PjC+a* zJ*EI2SDHKp259lrNpP57l_pZk1VD?zqx*p4Y^RtNd5qyh~3SyQ#2u; zr6|@sqYE$~cJ&h-;*6H!XR+=f9e{1(x9+S$`2Qdj#R;K1IDoX~DGMjhessp?uzCQyVsWCt>isf+yhVU%*SCg> zkap{s4={!~fP} z59Xx+n^FJ^{s1e6!=I$h<>Lj)0oBU>0BeTdypOs(Jg>3CPze`cnWtR(y)0=cR>GY^ zZ@JWWX>JUb!+eOR+2GKAf?JxJ3t^_wH(W2SYucKEG;7Lipn5vIV$QkT^|BGwYX3X=7LB-arqDS9ng!W6 z@u(31r)t@N=1CdA0V@FO1~m7{0Fp-TmU+AHwSDWow0JR+0O#dvqkAI>@R}T;(MZlP zYb731+2A-9Ie;T-J2acP6RGeQtM!>y-{dOi%iE} z587%l9tnUig7->npme``xZqr|PfQ)X#)0w&ge5~KG)y!nw0KRkWC2M>=`BrE#gWh;;^hoYf!Q zc6n54U2e_B*6pfW&Oh_$9e_Bb*aNNh)>S@;5~SO(TdPK@Rr72K?+{SNaIrmCtKzhC z3@gFg+&1*Jm91Xy+dT-^%2CbaTH=V_p+pl$5&IZpIlwa2d0zEiX{g6x&;E zYcrf=U+_s|_c(5fJq$6*6{c9AOoa+%7MS8Pr^(W%*ySD{h>ihbfEXYKhyiN;{sw*) VFCH;0#83bL002ovPDHLkV1k~mdO-jH literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_info_white.png b/F-Droid/res/drawable-xhdpi/ic_info_white.png new file mode 100644 index 0000000000000000000000000000000000000000..c2dbf18238175b9ef303284e6729e71de8cfffcd GIT binary patch literal 967 zcmV;&133JNP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00TcsL_t(|+U?uTOB+EL$8l?+Xc9fuXfH*rNTq*A8zqP! z(nCPOe?UZHtF(WISmO&8L=ev&e7#A9UWHgBDpsjP+xn8?LHYL3Q*GkT?##|Uq5I3- z%tvNtCo|7eK}C6gDndXA2mv7=1V{ojkYt1zHpr6anp;alt z!;dV}XamsCC4S|cUVj1Ae8lfeQsoz*fzQ~;C*pnpS~V9;H_7qLG~fl_u$g1pOafXh z5C0FL#T4L)#mV#cM2edLR9kvJ{>@_k8fSoT+rklu!e*z zdj<3&B`Gg}sOE9ihG_Z1*20%g`uqYBRuL_8HIHs}zxGJ`o76bGkHC<|eNwI)&H9$@*U`GYe zfaKIU1CpWv?aqJ^(STuRz>H`>#u>078nEgN$Qt@8^Di=hZD&AUG~mP;a4i~8a0c9p z2HgAyfU=)LW&aSD{W7{P8nEIFm=+C~u(z4>2avY6o%RQia0b+i2Gptiq}&w?*irjU zoDmBcQv-C11$3$bq7*~|E~!>MT3iqbm{UI+?GOogr4FcMUmPIE12-2OL*f7f?rvym zI1>fPm#Q-@eaSZ}3XpbxOBvx%4B(*H0_uFxof31L_1bHx!Lo?qFL|$(XyiQb;s4~R zPPt9+^%5!Ff~ToKhyOvpVylX+z=prcBNIh`@qlj4c6n^7?6QSp>%%{~zf#L>;cJ^^ z!dIS~EZIHsE8W$}}SAeuoVbTQ5srJ-jTqtpAO@wXi}PCLV7SY?|N3fyo*ffKe_VS+S? pe-FsRhUgFw0zyCt2m#(dzX6elZ+S002ovPDHLkV1lD~qpSb` literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_nfc_white.png b/F-Droid/res/drawable-xhdpi/ic_nfc_white.png new file mode 100644 index 0000000000000000000000000000000000000000..821029ce44e125dc0f5cd90c1549659bfdf89ab8 GIT binary patch literal 774 zcmV+h1Nr=kP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00MqVL_t(|+U=Y>FGN8U$NzUD@eV;D8WA)S3YAJ^3&|Es zv#VK!N)#G}MCb7hd;$%Xt#xRHMxr2ysH{YS&`{V>5$?Qp_TFpG$~oK3xyhX0+%t0? zbF+a!ZA%IazyJ)u01Oa8>I`UUq|u9d&lbMp76&*)S)&)~Uq)OCN^&74)t0KwUlH`A zES4(;m=jcGR<<^fa-l9hv#Gso+ zl;Xe&_8iT3mL%W69JZ>l$jzSQ0IIDk$hgoS)fLGB{83$zoC{T3S2(&*UF0Ru)eEf^ zKDX=p4FuKY@o~+^xex;|00S@pNq`AyWW1IDmas349bz$&xulMJSX}iG1#N$SM#Mf} z{Ha7IuFV2#6N3E=u3e+=*4uta*}LF7#6~ggyW_I5)JzBF>j2K&39geIW*500yXGiGDv& z^a~LG4NYW=I1VrX12DkKIk`_kD}zenA}vPBDxP+!H!` z)-Zyz%F^kdiR%#h(BRqlH}m~Mz<-Rv01UtY3=l;=0eAhPwLzwmF8}}l07*qoM6N<$ Eg8wT<-v9sr literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_play_arrow_white.png b/F-Droid/res/drawable-xhdpi/ic_play_arrow_white.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc0084756bfcb034ebd471394ef3c59c518de00 GIT binary patch literal 477 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 j2oM5<03kpK5Y+PsBjmQg004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00YxWL_t(|+U=T6NK{c2$0tLAs7!;*Ce4rNgBERSQ>&y% zT9k{ZML7~wv?wGi`j8;q^dTX(X_JXjW+YOXktu>MYEvtr(vmRBzOXViQ=9pu_vF6Z$$zu~_BJ?GrN51+f2&jauPJOB^C0~oVnh;b*tG74#-8-Q+FIL%V` z04(7)A;w3D8q(bWu$v#KqMv|tfD=TDW0C=Ki6(YR1xTkqDu6x~%LJ$<+NhEUkRC>P zR1qXq9zX#maakFqcw>4bvaSF9RTkeSF3|%4L)i>v)Ql_{sw30sK^AEka~FT~5!SfIdZfA0m`= zLH0Y{&}7r0NN#{aW|GYpW%^&(>TFB7f``iFsz=+DB+A0*(h!$Pbh4?BqeLl}t*jsS zbP{>orGs92c*$)J>ONfi0b~J@OQ#A3BAR#`{#K{S5_7+}g<#D0IS({)ndR}smxS(tgTgi0#e&A@tN~L>hBAPev;uI`Lh`oB zr8z(vLBr$+D6&s3%>hmtrf;)cngcW#rQaa$nzd_k22aibn9VMS+zv1(58#~ispHUuOP?%WD9em<_Q~sHzATo*%I(%gz#e&>>=!0dru^(5v&J2^ z%QZY!YF2qPGk@o8m(vI+8=@YLOs2frrw=-LZDVtoA;KOmQbi@D>?KWx@l_|Ubz`ip zn`oZ!drNd@i2aAXPF~M5o4MRlLe`KV!8fXt*Z0by{7AHuBEvVTlUIGT5ii9zs+L!M z+>&E_)ybf}`wP1207I(ZF$f+x$foD~@%^(Rqzz0E}Fx%Lq7af2-n hzyt6AJb?cK;5XZ0#A>dCZ#Dn`002ovPDHLkV1hi5?Gyk2 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_view_headline_white.png b/F-Droid/res/drawable-xhdpi/ic_view_headline_white.png new file mode 100644 index 0000000000000000000000000000000000000000..c66e9d234589992274416eda85683d28b9c78414 GIT binary patch literal 263 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcg6p}rHd>I(3)EF2VS{N990fib~ zFff!FFfhDIU|_JC!N4G1FlSew4N!t9$=lt9;eUJonf*W>XMsm#F#`j)FbFd;%$g$s z6s-4jaSW-r_4bA%7lQ%^gCp<%|4*x&1X#o_?>xPLCHh>&?;fBkBw%5+b?-&hS(mvO zfr57`V*mCs0U0kCZ4+97^p^%skXrm)kP0xU;fvV)kaK^=)NLsszNf37%Q~loCIG<$ BLR|m= literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_add_white.png b/F-Droid/res/drawable-xxhdpi/ic_add_white.png new file mode 100644 index 0000000000000000000000000000000000000000..b12e040e08381c707280252703332741c447dcba GIT binary patch literal 356 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t(u z1@CyeIEGZ*dVA|2FM|RPv*D)y7YrC{G7p+NK8;_J#y6Ks!J&bHk%@&vKmkGmxtsz} zK@YH0M!5@A1zagUMm$sLPX=Cuu|T!9EGcW4z;@u%2y=%+gBG(WAIuFL6X2GE90@i+ e1w`(;#ngD<2_vKVEGD2o89ZJ6T-G@yGywqeeno!( literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_attach_money_white.png b/F-Droid/res/drawable-xxhdpi/ic_attach_money_white.png new file mode 100644 index 0000000000000000000000000000000000000000..edb895799be97660e78847516faf944b04f6748f GIT binary patch literal 1445 zcmZ8hc{J1u82woWGh?DGV`!Lcc_WlPDHHQ7*@m)Jmh4N#Aj{Or@F+_$=vii_F++Ju zSwi+_moiABj=`Yx@W#%ulQ;e2z4Okw_q+Fg-~Ibb!P!~BAhHku0AN;@SVx|ceo{n` zH*M|FIy^!7SUTDQ06`T1h=~BO!&?z&0pQMQ0N{E7fKeU*z{98w4#vC#zqhpo7C3mA z!EE6raG0fA1OPxKev%I$Q)K`EgtWq5yb?3AFcuo<;w00$qQe?oU_UOdC1PpKxO{WZuz+(nZj-u3Eock8^g)?(qbRsD?g@RX&nG@`OBWK56MQI1?^Gk0imuWvX9InNq|BgnEfks@Jk`Eyr^-uMv zkuUI6^?AYI=&o(j3tF*i>8yl$^KB(u4Rj#%%63Uq=@vOQwQ_^6Ce?Vtfm<~WI}Yb+ zRKZG~thI+7!8;Mj5s6a7BcsIN&VYe=qLaz7m*au(kAxd)1;S;7Y}|TmBQXkWmM>Sh zSbL^oMsPN73e&HnjpYcvYj_&PcUJ2WQq*aa?_GlcV^Ts0Eq=RV$;_!t9PKh330m)t z1U)XEIHP`xf2a58?DeMF1PzRdNNxLX5Z^5<5PTWc(fiG29tIA_W?7t&LwgcKRP2X zwkvz!3BUO)wS68yaQSb7qF5iY?lscBsW%%f(}GU+U70C%N3$2x&D%SzE_it?+9SKK z{Xq05jm4XE%hlhSdbBHmRtii&OhOlJ)k%yzy$dZYD#3lp`Xs3>40=v6D3(p$i@qMN zp!bk-RD8Wsku4nk<{-oEp6pH~Y~u7UuQ5U$F&=a!b+wf`l~rkQDP@X^a| z+U@PKJ*IG%C8j$RW{{RUJiS}j9#rfn5U6H>v(>SRK^PuL%RuTVn}$={^XAh; zp|wlg&M%E=EK)vOP+LZGN=nS-lM5RpyGM(NJD}10P-cwbFTxd?qZlExs$P6w!Yct- zk*zX0{|?G5DMpRq94{&-NS6j_Go^?d!~Ea(D9K+#Jo_;=`xz;GkJGl@pIf^sy!j3Z zU!03kPT(RRJ%e$X7@tXP_@^tMLCU;^CksMIe z6OnjB-&SutifvT3R)5Dh<*DY}#d3Cyyly5-Q(z}WHX}Qua;VXHj=_8js(Mb7EgJWh z?@HIZI&cJ>S2){Y~&6$DM0NEpez<Rd!n>=r`G5S0|I*?+YM6j;Q}3j&otZj7{-Fg*bvGE#nWN fEA`)?_5J{eZe57=&nar+eNVvZS37K(saL{3d!>_V literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_bluetooth_white.png b/F-Droid/res/drawable-xxhdpi/ic_bluetooth_white.png new file mode 100644 index 0000000000000000000000000000000000000000..6c8cb16af8f5bfff9ad470d3fa9c2e2de292b066 GIT binary patch literal 1254 zcmV004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00dr1L_t(|+U?v+QWQZD2H>9~xJF^X#{~@6_MF6Xh@#-@ z7M^AL1cpllLBQt$-0Ok`64ISnRrx9+Yug0md^1c}SN)Z1e~@EttR+waB~St-Py!`T z0wqw61WKTr3iNLg``AmMIiO%f5rIMx#RLjO6cs28QCy%PM3I3)5XA-xKolLwohUw# zGf@vfu0(wRITG~(L^42^Jat5MMVJst0A1y&A*wCHfT$hlf1YxpsUmjp z&YP$e=z1Oi_=15=G*!eJo_Z6t0ZsFi5lsWZo2U(F#N*lPT%tK3Uz!JY=euC&)5Sc9ATL5YBm=Wm% z(L5*Ep15*|2i`>5KpdXwBMR#nao?9n7l_AmJ`1ScYSpmHAWVHWzV zj{=c2Pz?|NI20#yFB}V&1*+pIIZ$U#Bnec@Q+6bCfe*xy%tJhkcqFr)rwoX0cCt$z z9(Mq0;VA(!=8*zw8X}oSle^wL zGV`Dw1j>;>38XOo0{-HHA0Oq= Q*#H0l07*qoM6N<$f=^EoO#lD@ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_cancel_white.png b/F-Droid/res/drawable-xxhdpi/ic_cancel_white.png new file mode 100644 index 0000000000000000000000000000000000000000..215c3b386df530607dacfd97e1334b78e189c8f1 GIT binary patch literal 1742 zcmV;<1~K`GP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00u@$L_t(|+U?!TYgI=a!0}01twO6}>C2jUb8%k?geZw7 zAQTi*u}ujrR?rwULM?3;*$4D*aG~M@EwnM1cBf)cQJYqb4MJCH5)yGGd61Tp8F-||-G_t&$f!5K>qt;UKj2%9K+IU6i@sd_wKzjt| z=Qg9jZXZBx8R01swYmms$-u=m|3cgD5~wclu6g0^y4DrYhJXk5(u01L3!s&O-WDc_ zJL&JP(*vC*%W>Q`&_1#rdu#%2G+eX^W2T?C1ymVGzQ;UYRA~q3d&(ugwE>jN;Jwe{ ziP>s-po^4K{3H#u&EUg4pqWweuQ)<8BL;uJRT8MeV4dp$)hP19L?h%-{ppfjR8CPa(#=o6hZ1zMxEJFIDm zSJfdZ&7)8>YvmbNnE`cb{jW!<5hhxiM|S|&Zw7Qtv$se>G!g(0TrmM!XRx#*M`V)+ z3cRlm)M+#%%MjV+0qD>N`o-|3&YZ|T4_wd%S}u@a9Wf$u!2?q)(*xQfG;`7=a>E0Z z+Kyi8?YqV1-Ze{RD@Q!?K$jj+zhHVIEs;we=+gt508d12d04Ba@AVl_gU^YFF zi*4dbh+ObMAp(g5#Y-%9(q^4>9OmPVqS=k&)!?QYH5Q33rw2M0;AFKXq{-@$Ak!T+ z;iNnKB+T;S+a9bX%$mILj9Sl!eO^J9@j*gOV5oSd5VAOil|<9`^$_+9(P2Oys5i^s^2S5GNPO@QLp|EvxA;_ z+z_?v1ASzWSCm_0w@XyuJ(F#~1KxR(y_!DJC9{3f2Lj-+OVn-#v|6i_x5+~C%T6i6 z3~!qjvz*d++ed7$kQ*#br1uLtVP5UCN$bljPL#J&4yAj6)kzf8-O_sNhYJ$rtrXPh z@?4jxIQ&BM%RF>clBqv}wMZ59-WMUVQ`D=^b5E+MSHGOofO!AEL zv8&^fYcqHv=QN+VJaBslFEhpSf=^u^+5RPk%<#NQcA%RqkYaa6hzfkI|1C{`6s;M! z&|c{Oddz_o+XL^er*-=tDIi5{z=Qs{-pC~jq^Jt?)^SkhzQ+cTVnu)xkYh4W^_2lC zPGiHF=})GelDYs==cJ&pSUUuRLouf>@gL;Ui$mU11S_+ zJ&bF=Ctb)10Z387E~_PZ^W$E!Rnm}viby-T;h;-yvGts1+d#!u&q+%UoOs5GrT6!Q z52R46Vn0^|=l%sQ(at+AKD!&BV*7v&E*O@ZPtr?!>5aSD1uC{>Y@&-k#!|tI(nlBd zrHTg11?Y8KLnC|X;Vf4eVvMKEQlP*rPZ?u~%bcZ$y)-Od?pEGFnP`z90!5$*6oDd8 k1d2crD7FX`fh@Ft08*@YNZ*N58~^|S07*qoM6N<$g3qWX0ssI2 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_delete_white.png b/F-Droid/res/drawable-xxhdpi/ic_delete_white.png new file mode 100644 index 0000000000000000000000000000000000000000..a8d8ca84dffd42fbaab75704348af80c5102df32 GIT binary patch literal 574 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t)J zFfeZOba4!+xb^nBtzJu@$nl5&Jpux>RqPgR;R%d5=&~Z$L1o1SkAM|lmC`tsHgYdL z=DzrRj6dW2D7=^$jkUOD`vt&lI-9Ech z4>Op*9yexLw9Db|=MeQ(oFQ$?jRI{K6xoD!W-(5Ey3*r{{Dt_Nf>uD?aATNEoE-i` zjQXPA5HeGnyqYG=09$0 zC@a1tbs1>P{!zV1cfxg9`?uoUsWrCEj9$-*C*^(Z{5CP}+ozB}Ns<>h zw@&!FCdJZtro@lHkF$Q?dAjRL!g^*CBSd&0f_2|l)(Ce&f!NwKb6~VGc)I$ztaD0e F0sv6F!TtaM literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_do_not_disturb_white.png b/F-Droid/res/drawable-xxhdpi/ic_do_not_disturb_white.png new file mode 100644 index 0000000000000000000000000000000000000000..9ac3d7a025115f789d2098267a588389011d405b GIT binary patch literal 2319 zcmV+q3GnubP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00@UkL_t(|+U=cNOjSn|hR-=Di1C7u-c-;Mh(=RvNeEC; zNsOl2pjA=QM)8UY23xBM?1M&g+fP6qcARmwq$Oq(K zJ|G{^00R__OyprZ_M-tUXhSFNp%=ZlhfcJi1@)-FHsoOjqW@2T!Z8g+s6`t@+7;BI z2pI?;$bd#8ABS-lZdw;=u^ulDG(b_vMo9U)07}PR`~e-U8|6sr&wxV6 z#%btor?3#A{s$`#DZ;wPAH=aAVy0FB3&@TyfK&Tc?qcpu&HvUOpj z+8#+Io&fb|PBB&@*2RpwoA8xc)nB==I?1KLX(FMsCeZA4T`YT4};P!w4m_K-})+lOc@?vr`O5$*ux zOR={PQ3vTqa@o9eML?QJueR^-^~cZGDKOV ziadEh6!qUQm`jvRh{AA<&~Qi=P&k=j-N7x(CPc-Af=<@g%`!O6ZK}XBMD+cn$pI=N z15~m49$OHF;WpuavmBsW!d#`WG+PiIAiUSe0cw-!^{qyBGo-&R3{y!yyG;9NMR>Uz2@UJynM|eN>KNj*Evx14pF+gfnvRz zvLkB|EhcOq69-g5nA)T*kTiivVX(T5F!hPXWQ2Z1yVRvSsDxRQ)`XUBL<$pTGYC_^ z>)MFM0HP2%6*^TMP_*Kty$y(xR3_~MxI!3Pt$lh%7orU+r+w&&!wLgae0mTaQaf=- zO+DY#xo4P0M1#oiNpk8rgOv8VkzxbYh-S&uy0Xh(s2zbmG*qyn77AR?7o=iOeh38Z5t5gQ$_rB|hh-47h<@ zhLF&&5KSTf^QgNz>2;*xodyB9M^q{IfH?yiNiJ)=kFos*(FAhGsxAz32PlAYQc#Ie zKyDDx>kiADyvomJ1CO+at!faZG4^ImaStefQ>6KC3Zp%aZOn*FoSou~h;H1=DMaM3_KR*(SXiYW zQ73;CAR^~orqe28IU@In3iv&U%_^N_y8T@9jF~D#O(HtQ#Khx{10wr>MI9I?VqH{B zu7mq3>-(a&#cYddeZL5ZT&4PX)ecR>DRHu0GtpC7!mtl?Ez-X z*>_Xz$8$2l5lqsGCykK~>zn(OuB>z;ZYdX{NBC650U*yz#UJE4=%xqn$a`@oF`MgD z0^mMM1kPdMxu&3!^K6X=?&@;g3Z28ffpRYnW1fPMb_8-9x7F_3HP3eE&;56q%yA|` z-xO^zrb%KNgjpyhOAwy@Vt0LLg_@V_S;27eHV$AdCUXXjAtYe~4w2QmZ~|`S6#Q6>k#RiY#pK@ zsR=*<#5?4ROtmJlgoZgl0fewo$w7YdtrG=m({?(d>MuOQQ>nh~*pi9ouIa#1T9g&LNnojS%i!x0MM2!LpAb=5A zgF5bm8+qdMqgajMCbXMOi@tz7iGbnXL%?dRW*6@EUqDZ;!AQeq)NrKOpI)$3gUv`) za15#Sfd09nkb#xhihZaPumSap%SYe?@&WmP pd_X=RACM2o2jpKqARmy1>rYe^2D19lGwc8W002ovPDHLkV1mGR8+QNz literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_edit_white.png b/F-Droid/res/drawable-xxhdpi/ic_edit_white.png new file mode 100644 index 0000000000000000000000000000000000000000..fe5bd13fb32b974f8f35cca90f17fc11766b4aa3 GIT binary patch literal 843 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t(u zRUGzoaSW-r_4e-GEb%}A)(<9JD_M4PX#KnI=kZediK)&fnUz~NbR?*X1=cX@Ha(44 z($Sex{J!R`t#SH!=|2%=>E~i^C|f3Nb7Eo=>~LUF5@_UbQD}oPfMS~5B|7h)1pPa) zwPsRqrPqSpmCEr?@4wzWN8p7S`%lg9>+?S~Iq-kpA#n9sp~9+j$!e?4C#$?VkgW9T zV6wuiLyOg39bT;R>d0cHLr*H#JqZ+8c+W9i$q?}fAKg627=HyU4DZ{=W~{IJ#d;(INJ?ZKP%FCLe2D4)*dzvwcT!~gd& zaW3)4`dS6TgZLL|S9aQK*zMUE-MQ|G=)YAPV>2Q;)GDsAR_%FzQONh>8ZMKt_RJ2o z%4;lA`P}K9%PPZIW@!hfcZk@$W;|PdcCm}rysHf*p?^3gPD;P)2@RGlZVD9VP>ETUoj<$4p1%-LG4GSthH6Ezg)cByHsF9)4s8ONvQ-ee0riO&dqK1Y_qlN{Q zj~WkDE^2&GnbgSj!G-DQ$C~qxJDIf(3A2W@sk5yR@Mj6ol&fEmWOL8qX+=#V*QW}m zsZTyKYn^(`8ZzxT`wD}8_JB+|>wrQz?SM)-=>TRq?f`B%+W?jGuS+YoIOJB8IP_MS zIIOLB);R0q8K$j|XEJA<@jW7mJ^dXSx!!7XE`;QmF4tgW|lLP8ClK>^Rt{2=4ClA z%pKC8F0_KhUnW3sp2h+r8$*ZI3Z(|Gj~~)+KvNbdk@0|%7&NsxFPQm004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00&D+L_t(|+U=cPY*a-Q$G0n#55Z#n21}uk@SqVbVoNAc zLxO>%5~QR-p%F^uBep0FX&`Kbo-tj|UCV1Z>1~hb@T1OX)`? z?~25UXF4vx204cu8v(6FKWvdcthNi#7#xIc@)<_i0w^2puu;B8mJNVr<2TqUJ;?PM zP?5*GRw^055-$Oj;0|n;AryNBsFcw^*C`3C@&Zr^8=fB2qXLUC4Vg$sIx;a0FQF0* z_>~P&v1vd>EDzV8*o9eAJG6$7;QUaoUZ0m>$C z?1QMoSS6kJ;{yzlALT3)fX0xIg+JjLSO43Y_?f8rlKSad-At-e(TH)bH->R&B0C_O z0j(t(_%SgU5yf7j1*JLxO(!O8K4EcLh*I%$$<|^_8j8|w2*jePIWBI z!FIHu2SXS_H(Ia_Q&Jusdq_6T=LD2S%yU&rXP=7W_i%iZIErlP=e7_JjcI&g$(fCgwIIn+5#FmsF2EZ+#@3shN0 z19X~ffas31+Bx`!V3vo;_O(&~O=L*zh;BBkIh%umSw=CWgdTMdi2i`_SgU*!%^vo(MY32p3PnZL;$pl(a>#4t%<;S44X*WCbm$Pzqvv0P~54mK?r>VOI@ z0aPieSd6i0DNqMgZUvq#k|kg#qnGK1tuEXkp z+C0Ydjx>$X#H_7V9Z-j-c;1(0={{xG*RBr8%TrK|)P>%oL1U*npqOWPYNdE~YSG!N z4(PT=cy>zh)N0ZiR|n+HE&2v&w)0($?yxTaU6p3>w`z?S_}~%y2Tw`xY}Tqf?uSMC zJqeySH0$oQf1B^bXh~&AQoDBR4~pxBmdrq(cI_?>il>a?`9t{s8G*|N^tY%3sy7Pg zBTX0WG&-xbMgcu1yb=AEx6X|n0W;weNxt5qF<`d9AfSUnE3O!_V6Hl#OoM>l6qfFp zk_+FQLT}AE8_WZVNo1 zleBx%>jMv{j0Pyf@(xo}=}%RQow}IiK%fEbqgVUn2O7|9j)J=LfyHx%r{ZvVU;!0z z74=>VB%Ui=MZNU0UOVMw&c*_l1Bd4#Z)=LezyX@e-Q1+n17}sA9O3W&GFfAE$TXlB zp48D`X>(uzZPL}E>s%lJol$wkgj!Rh98LEuOai)CXmX#g0qxb_ ziE}hA`4-Sc_4kvxX|I+=>>lP7pe|wMB73{!T;_st6M*_K(`3W#CFJJCW&sTfyU=KB z*}eh^-vGMftf3os)BjbBh;&Lq(gjWPYa@zTA2dBO=>FeB2Nc0#i~4)qdud)o`-b$M zs=4f&KIg_hlluKQ+RkQaI6Cqi3n$UP9ZzT8(OMi3s#C%8R$spM#ia%+Dx7=lE4jWG zXNTt;ZSf-2cuzLpx8iMr9Yv+bFY=3F6Ym>Wx%-4iFd4@^!*j%Wtl2w25kxTGgMIBg zr->XJ07a07Wd=*~t~kq*+5~7A(o&o?up}4hde5_NK*J*s`!(HC8^FHQ_j~LI6hQ#Z+eg6bbq736r*lq^U@OTX6I6*8ozlBDW$uI8u7trt+iF{O{37tw{uA>Q6$deTf z1O?E4n|T*t4Qg;0t!PIldJ#tg3B=KhPPC&1hf#wym@6)K3pOBMj1WNxCy)5KuTmKp`Lv<6pOWsQ6c=m;3+#002ovPDHLkV1mWkh|mB4 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_info_white.png b/F-Droid/res/drawable-xxhdpi/ic_info_white.png new file mode 100644 index 0000000000000000000000000000000000000000..cf18a5ca11fac1f0d01925f19a8ebffe0951c61f GIT binary patch literal 1434 zcmV;L1!ek)P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00k0BL_t(|+U?!nOVwu_!13eRvVtwcv0p7Ur2By&8d?d# zpuk1j8M4s?vqKrIv3cRWP=7-g8I{yvGs*3)B+01eCfOv|wW86Lc>FP`j=Z}#Hycbn z-{<@MexA?s2XuPj_xb(#JQ*^vZPp?|1d2crC;~;G2o!-LP;3z>0(k~hO&fVGa+5Kp znWw-S>#VazfqAC6#|RlNuX<8hL@O@tg+JK<$P{t*v5(V(QLmN9*`t_=SvX<=kq~9;fssZ`|U*bCzK>Hk=`)s^1TPq*vHa^AA zvVo2${ka?ct19D=OrQ$I=UgU$Cd)f&EO+IOcR+AL7Eo6CA+i~u>2m&t$^ptL?=sB* z!Erf2kG%zYAOq-thC$a95HzI+y6!d5m9#*+72OWA1!#p`>418)%xld7!Rd5B_q_)i zO#`$?)6tGepdufL1L^s|Oh;ZE=+_{CZixa_da%J-P88TC26T{6qFD^6FBqU+F`$uP zfQH3@=7Ry65dnISkfJI%P+L$yt;vD%K>>9o2f7#(P=9iun?V5$CI=eR`14FIR<~Hw zZwxdY6wp+1p!uMHW|IRIf&yAd4zv~&&}wp^^`L-?5lA{v{KR4>ovxFP!+e}5rkg0< z3kv9Na-bVQ0SPQtF9ZeDZ}*0oc_4uev%^6FwIm0s4hpDBU4!`sks9jc4?=vrf zp2|w zIaMXP9(utup5%#%WJTWUS#q9dkvf%S0y~`t`dW3N$pNQ%E~&4?xr0e(c_xy-PbO0A zwua|U^33sxu5!r@4knB*@u|*&+sAn26wgaO(_OOtD~itWye_Yxo9v?hCmkUw@{Ory zBXSOWXfMV8dSpC2a@6wfdP%hJk@s{>gT;gXoY=@E`|+b1OQ((#BJ&fmMnWS!ID)rnt)>{dCc?ak`t|Ku)ws5P>34 o1d2crC;~;G2ozfcia-k58&`sPu=ujfQ~&?~07*qoM6N<$g68y-`v3p{ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_nfc_white.png b/F-Droid/res/drawable-xxhdpi/ic_nfc_white.png new file mode 100644 index 0000000000000000000000000000000000000000..81b19ef04e05ddbe7f3135b6ff67b13093815525 GIT binary patch literal 1126 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t)J zFfiZqba4!+xb=3fZH9=W$npQHr^{sYD5zyPIQM*f%(OD8hwG8Ji}EoO#*H4ys?#)s z?kx=zWNM1&>|Ma9s}iAMCZ=?%^9Q3~a7#sRm%OW<;6>xeZUv1yZ_e$$z4z|ipa0jN z-fNpyH}iYiy*GPekF!2qD(d3kz~TZ&f&z@J>!&b0)!UM+d`)G~@%~b`bG%*K~Ehu(^| zxoz$bGv6HEek#q7UHI5z#%+()=A2@f>$G3gxKh4NyKtk(vW-V49ra_3{upi5#24Aj zo^dpAXZPGKSqE(t*so-ZE|9ZaAhw!e>&fOXi|y)sI?*f6l+F&e_qKV&ab99{T7i^pCw(oykOP+mq$3-E(Fw`Z+aQ7 z752DNRPSNVo;!Aa4N(R1vCobz$hz9%e&blAkF25EqYibuhI0=JY?lA5IFnR5DL@fdNM=kxUAf&atF0?G|4h5{`Ho(7mvG!suZP=>qa0p;kdT~T|4{nP z@tBf^MJ0yp+iN~D#O_GU=8^#N54Y=Z(T#8;9A?B8*QR zP5FL^?Je_`=oHz=uTdq28H}r0-_CJgocnr#>%|wbhrDh#eJP*#wW-!{g~FzJTloX* zT(XrJeA_Z|kLajbhJKTiU8Sx(LwbpK)329ng!&IEa}<0Ka5uU2gW>q1+wVHc>@#fV z{gC_jwQ^to&ExU=9x23CN9{SnIA7K^IelYl?E$%%gOifh)=E}?)O;kiYgNCO@%q!e%@({^HP89IWU=8fSO_ix1}MjgmYAF~J`Od@Y>{M?6uKHXRxH~&V|7H$ z^PVRUP8DxVdK6!+8z`8)R4;79smWI{=iAPTU-d8)D4Th8v1Jtd zVIzry69TngE9E82y#K>1`*w11{p+2VINd)7hxI?(v0zoFBZ~_QQqFP+vHZ&zb!9Qr UzPK-6fZ3nH)78&qol`;+0FK`3L;wH) literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_play_arrow_white.png b/F-Droid/res/drawable-xxhdpi/ic_play_arrow_white.png new file mode 100644 index 0000000000000000000000000000000000000000..de7f9bdb5b6080e7e47bf9203e44008d60bbfbe2 GIT binary patch literal 666 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q1xWh(YZ(J6g=CK)Uj~LMH3o);76yi2K%s^g z3=E|P3=FRl7#OT(FffQ0%-I!a1C(G&@^*J&_}|`tWO>_%)r1c48n{Iv*t(u zRS0>yIEGZ*dV6PM=^+OJ*NgreEtJ~17O)lwUSL_r819hQuCk~p^ugxcpVR*=YpL$5 zh*CV8Cb-#5x`Ba-LjgiaAlPhBAqJ)nMv(L>-jb_+{tdS_zi?-d-NJB%Y0{G2c3&7) z@T%-BxfH115alrG%Z0FbhOLcDT*cphso{ zJoI=zqlU>p%Lb;$^Vu~t{>^M~dNiLmqw3FRM&G`AzXhEi&vTn-{+rpj?1;Vfg%3ZT zvrEbRmvUf!9M75&@kg7Hx3k`K!Ml&^*{@jsGi|gwwqGk?`;Tz8S3>`9ImkZZzhJ-r z$T_Qm&X3A}m!FX@Rr>r^`smtyI|FlMO~3tmvf^N3Y2Afv=2DKTR0X-6=Qyf-7xX!T z%zAOP;m7OBvJ0yjc)6>172+0jaaWZsc<6YZ$3*o-Wn;y}JIXJ9HmI@NN(;<)-pOjq z9}sY%n1PkemRVqat2O&w$rDO1_BrU7pA;-{S6ICO=zG??+#Ra<+74?tV;GJ2b}ycC6ZZQiuFfg(}2n72G160U?!4NFH@H5*~({%#I4h PlMI8WtDnm{r-UW|PO004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN 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 REQA06002ovPDHLkV1hu?13mx% literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_settings_white.png b/F-Droid/res/drawable-xxhdpi/ic_settings_white.png new file mode 100644 index 0000000000000000000000000000000000000000..5940812da624e7a4ef74c0d5c7307250642cd5de GIT binary patch literal 1933 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 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_view_headline_white.png b/F-Droid/res/drawable-xxhdpi/ic_view_headline_white.png new file mode 100644 index 0000000000000000000000000000000000000000..faccf3d2380ee6f1f5851e2eb65e2106c47e84ea GIT binary patch literal 374 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 KfM4Y$ko*fs+LwI* literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_cancel_white.png b/F-Droid/res/drawable-xxxhdpi/ic_cancel_white.png new file mode 100644 index 0000000000000000000000000000000000000000..bef7e17fedf751057265dbdd3403c9309511fbe4 GIT binary patch literal 2357 zcmV-53Ci|~P)G00000WV@Og>004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00^x~L_t(|+U?y~5t|{=1lDAvGX+Wp z=H>ZzF;GPr=H7e$=k4DA|Lli5zYp^~=jEPr3`SeXrV%KB0%%hJ1<Kx%Xr#a6Mml$P|Nk+NE5a&6~ao(oRRyNX3s{+`! zg*-tI@9_s2X7}OR5X-DDN^s=o;rQ zJX}41M>s}I^>@}(2cVq~h_9NwPh0r`7I7egkDcj1j`sT=w)y1hh-_B=vuFI zowQlLw*lCt+25!gApllYuMdwJ10&LaHe^4*^1XxtjvG1Mg zTki7*@S6A^@CL4U?mVvNAHqNLx4r<{g0Sm1Y@l91Mq=^_>D(` zsrU_sd3-MVlaKlUc*Ifsz6idHU!I`JL+$~NIg8&1!L9Mj6MX0z;8CaXyC(Q?{8I#y zyOh_s1$f7C{4NP@AHOug5tjf<#ri+f$M1?@`uJrD##!tRV4s)x4TiPC1b2?#V7Nz& zV2>++p9&o+FmwFU1T)7kNidvep)wD^CXew;5@d^ClHfTv05T8PmE&KOAjlrS!LUK( zcRb+&piSuG;0kL!66A|t?#ut@@&Iz{U;YR}#h+u1Ec@e0o@SMo;BsQ|!&lM(tA%^z z?g>K14`e;;AnOCZDn2lBOAtDK*e(lj%E8otJA&}>Lqif^k)wko-4MhOKTKp>qRjS? z-45p`r3vDQAK)oD0GE5eWC`MlA6}OOaC@L?K7x)r5X2OJ#;$0V2VE$1hO=vexZ;ON z=F0#)f%~#2h%Nq%^Iq}nf3zcrG5$>v9(W??C`b@z{Lm)@@C`&w5NrH!Rsx{a z(I8BUAl~?4f;sj8tMFTS3F3_(n0ep8%n!X0q9%wre%N3S@CuZIAVmDI#U7vzB_IeB zKkT*#I0DHNgo+;y+XEbjU<8jP9zPtj2MDNBl6M0LcUBM^NC8ApH^!{3Wd2(7@eL z9f$=Ui0M>B;He0mjI*ha?E&6SJpSaTqwC4X?_%F^))H8d*;<15!x0E9Np37f{GkZC z>;c-Nj=z)6s0miu1NeS1EAM+ge)k>ct8s}JvkYHH96t~-!Jy2$p+}>RzX-u)Prs4S z%eg&~#~&|2O$Olc=;M!-Kxl);0uNi1Bwn$NjbM^{r8emeCJ=vo1ZU)SNxqdt{8K+I z9)bgM08b|pe_RAl$^k5NG#GQUyG3r_vE3kSz8rvYa$SSF%|$T~^vj->vNNIh;~=QX z0<20Z{ul__WC3KmqJDNZ7Te z5v&*a9Y1k#aI4g<*`G7d^Scb$38Ze7dBzPuD;i|z9SPiA;LzbE{xN>$4#3bS7RdAjA+9iyyHTt+U!r*d z9u^Fw5zQq&k{7x?(&~|wgf2yV0c** z|3PgomSj3usfWdGd`-ER^m#i`B#a^*Z2ViP{!O;k(Dw^~adA~nJ0E6Kdd>B>y zf6c$SC~bhL-DZD|;NxhF-n=^-%K~69+|M3{gM~j#UEFQV3)~(U3LcoGaHWD|25J7%V)m% zWZcD%^PiTTTeZF+{%63qU-mIoe{{nfiuB`)mp)N`@i%q{r~3V(fKR{b_y75EdriZW zD7&YQ8$Z^ctNZunke`KT-E-HvDN_H{^rL(B-;KObI8RLJ#*@g^yeYd6?$~}#J-d_B?a`NA4$JKN54*f4TU;kO_`tCbrvd?n6)#txuQaRacbAGbT znYnjPU0oahdAa3DEBBu}{%l(<&XD_54kaQP8U%sK5GiU6eEu_}e*L9se#tSJ0SG)@ L{an^LB{Ts5VA1dF literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_do_not_disturb_white.png b/F-Droid/res/drawable-xxxhdpi/ic_do_not_disturb_white.png new file mode 100644 index 0000000000000000000000000000000000000000..446abf64859154ffe4602ad2f0dc0a7447785182 GIT binary patch literal 3134 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 O^}xjFqG27LNd6D=4u}o_ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_edit_white.png b/F-Droid/res/drawable-xxxhdpi/ic_edit_white.png new file mode 100644 index 0000000000000000000000000000000000000000..736806495cda3ada8a955e409798a33cd73c690d GIT binary patch literal 1098 zcmbVLZA?>V6h61TKtBM9B~aw6QcD~tEx`m3Sg#Ifkq--70-Gc4Aafuzkg1hHXk55q%D$2CJ0PTm;|X2rjt!(yHPH7x4R@u{55~<$8%1ebCPqO z=f~5i$dUPQwr~KzCqtf^M|aBxSU6qR?sb1cH$kyHFB_mb5&+c$tkWa(1mLIy;He5= zcRN5}rRiF3GQD6FWy(^)eX?o7Ofz<+{O!X4T;2vSIAh`iFh?^|_q zP#vQ7;gB_@YygxD>?( zKmEC1!qQ5pWjTg=moez7WmZvb2`4SF`YCuY6_+KdoLms2W7PM!S?h3e6hikdhVw6$ zBHCQvB}HdQ4}1i>dwapuB}M^cFz-)%I!gbhK?m>rQX!w_63ebh);cH_$m$ym7h)b>+_-U!hqgkTghK%GR*ftsNAy89%XmrRx-l5#ooNV4A nsMYQKPg7p6NY2YheX|lDa$6K$S2dqYM-DR5a#DNusto@CcT}=U literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_help_white.png b/F-Droid/res/drawable-xxxhdpi/ic_help_white.png new file mode 100644 index 0000000000000000000000000000000000000000..c996532616a899b6c7146d57bf6b0e5fbd0270e5 GIT binary patch literal 2735 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 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_info_white.png b/F-Droid/res/drawable-xxxhdpi/ic_info_white.png new file mode 100644 index 0000000000000000000000000000000000000000..00fc89c268c6ce6a67287826a7abd20afd9fdfa8 GIT binary patch literal 1993 zcmZ{l`#aQW6u{4S#<&hDBll|&nxw`mWf?KXB-a!sBvUR?Zq?S*FtQ}%8e^o0Vc7>u z<<_buw}z!$mWte}9WkyYA)1~21NMi{`8?;G=XuY0p7X3BGO2KTuP6W+h5tYxFJBoz!kS`h?S6S^cBI=`(L?#gB6>jD$36bsE;}Q?9?xRq z$KkyiPiLqKi?T}FbHrFaiHUf%xZ?^n$K8gn*Lgmw4Az^gh!>YK+pK19+FNTaLke*j z9}QCq3b_>-$j@h+2l7{cj$wYjc42>5l-+&Kdwg&#NHTB#FY71b@qr3ggC?*n<#D-f zDFo{TtU?N*Swp;`HJDl!sF015W=1R4Waz()NXABIP3ZfW*2Ko6#_N;HOK+WekZYf# zJog5Uj8vtJ)v~*T}*77 z`o!+{oys>1DZ9DUr?k8VLR)=>lNML?agCu>jnU7Ly`ISEh`g8d5sm9V`O5Su-txIT5H}oEkj8V+CF(I6GJSLOWc-Y~EuNQHoU)3_M7gwsG2f_!0%iY%d3W1f zYLs=cm#CV=VX9F0i31c~tqE&qbBOS}9M=4ayyNRe@#>ZuSLZ5=(jh&jekKclbp&`H zBq@%`8?gE)@GmANTH$ugNbRFM@sBxY8vMb83 z#Fl%_Zx8;9MtDdggjg1H_Iz`!*v$4^Y;vMTImVQ@YZbzTt*Q+a1i1jJvf9- zKXYaUMo=aW#=pu|n=SVyjKB)x?W^5fFNBvtIEBL3jbjcDBc+}=O{VM(xpNW=SAyD{ zzmFv@x*;sE+X!`EL&|FWJHxCXR9>Rc11$K5l|eBFPj3X6&y@(;3R=OY@+Nq&jBGLG zN{0-Ni~v_~M#2}7(_UmT0+6c1WJc-h?ZgOsC5paz|7e}T6U5!Y3Vr&aIn!(^fT_Mu z!UR8p4KbK5*&rp%g*%d2O5vmXi2-do_- z1pyi>$+TPhip+_0UVu&!X>J;wBEz(6UqbZ7h}+VZkiDqFTGj1Ph30KD;FHtWx1_GJ zs2)WSOP&D zZ!K zgxLe?bkDmJnL9ArL~1t@x|s>9J?g{^Qq~?1C~a!uOm93$wL})!nh)zsW|ZJd6j;Gu z7;d0Z7wQcGcMMM3{ADt(*AM6^vnEqvs4h9fvD87_WRXMH}g zGU-ZO-<*77W#apz36q#{#i^ySZf#IJBE@xhVdYS~mOtx?^k@hL2>zpGrZBK|cq>N* z+Q@y2N^WWuS=Pws6-W9H?`WOfRA}3K&wi{R;QE6?23F3m-;VJvSaOu4!8HGTMg{0s zc4-31Q2yxHkBF^;B^P;O&CahdymX*Urf_BNz^u@_K4uLt>-UD1zkQ*vLsI7Lm&xSy zSw_4>q34SaxYUg?LhnHWBu(FD1RDxw3Fmqd164Q7LQVNoHrl?Q^sOLQkVk>Go9>T< zxgGb|7~Z6ywO;7nbMch{;I2psPxhRx+u$R(B%9AOX-dT2bgp&q;5*)qYe2f#TpFNI z+P>k67!2wK#rn5)RqUeziE=1sYRwDh%W4*L!G#+kGB3Y0&$u>#G)Y!oeROtc>!p}x z376Z{3%#)gOVj7fhR}t%HpR5!BB|x+(Ar^flc?0P*X-q!QqeD8_yS4u&&Ek^`oqDc z$hch~-G)kSr8h8^kMwPsNvu2wgn6Y4UJdRUB?R7Tw32AeUmE?6eSh{%7@08)gs?jd z+Vc-VnR3s<%=9tD`TL08bJimIJ5aR&rLFfrC|Ylzt58iE3bgrb(GLq0l9O%KA;0VY0$99eLI3~& literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_nfc_white.png b/F-Droid/res/drawable-xxxhdpi/ic_nfc_white.png new file mode 100644 index 0000000000000000000000000000000000000000..12cd90eae936c99914fc36f544c69650bcc72808 GIT binary patch literal 1370 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}*n4DI|LY`7$t6sWC7#v@kII0tz*} zU|=XUU|@Kaz`$TNgMmT3V9u^U8=wSJlDE4H!~gdFGy8!&&H|6fVg?3oVGw3ym^DX& zfq~_cr;B4q#jUq<&*zJViX7k1yfs0S%}no#p3X83whk|)&TAeij8Re(q%QNewQP9Y z-f8%cSxxQD3#QcLLZX;T!-aMB+Mhc!yZm{b z-RtUjjW#~{s(i` zy`Ho#V_zv#SXO`B^|G~3|HVG=kG(L%?(*6PzbyjpaLHc0T{WXa|I~Y{b%JL@)gM^a zuCjZyN5wvK|Gu?qobp$~TkfBn{40ummB5E-c9)osTyOTPU8J0OBx#l<^?2T4>+n2`2L@JHA^k zx}GTdKx)EoZm#5-87>l(HyPpRI& z@K#mpqFX;oRQ|E3$FHBTt%2=|_L4a*(t+zItZQfkvS+nKhp!JP6!Hsp(0(ndB{t!i z(3J&Mts<-@tKt-08GBcSd+4)o*%j9)Q^3!=I^4tG!Jg?=sxs57rHNm;zV7egUaS8? z@8{=lPygv^d}(#sT`TvM@d<+i0}}%y2LsTx0u4wUDYb-3PGtdsvuge4{s_NwtK{fN zju5W8W%(+ycKLOI0K4`QFaE0DH-C5bB z?hv%SuJ4!Sx7pXS-5zmnC~`i&=$Y(S4tviV-G;R?Ht8YhK|8jDm_4;F4bx|jxn9Zp zV%v3(X$tz!Ojc~Zx7hx=-1!fiSt^YeJMMk|aqY8p_b$i%oqQ^F>9sY;_MN4ea0kHYhe_9Na5!@VK%`S?8EV;?Zh%Bf>l)ANKZG&w zukM%u60NLrd>DB`?$&<$@;&GEKh#Q!Ujs6k&gp-smwt5{#5}LB!KD1^)&2AO_YZvL z?&^rH`Uuo04AdyNbJ?Hk`|Dl5HJZ_9LZRuas&M+$<~i}9we?)z=gc%u zE{T7adNLMh9MCeL0c;EnjKng{1Rt!Gd(Eb>z54IU=T&R18$=vJufG2kIG>@2A@u&& zpzFWbCNQm7^=nQ0FNO)sD|TJaw!Iq9pxLl$RkZCj>joL1>ff4$w?r5Cbza`b(0(K{ zzk&JD7V87QEaoz7u<}c1(CNEu$>9CCMC`yJi@9tY^!(JB)^uH-$C-6_w--pWmx>Ug#Ew+i@VGQCVu9OJw2Cm86H0>kw37(;x4CwhTnH4p3ci>8I&KD zSUdc-xXXXR#_v2shRpKi3|o3HPiBZZ_A-;c$v2Fo-<~U zxhKn#AvfnX<1O~*X$^u0cZwfSwR2>+9>L1k#`@fhAzYG;(T(l7T*JbHJDC#-3i22V zmR1TR)D-x!MF`F*W}L?I+^50yprv3!Xn`Mdg!r7z3}S50Wg3mN`&cvJMj+bIv;a25O@D=mGK|II${nw@-Jb4D7_00vK2KbLh*2~7am CC>bOG literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_refresh_white.png b/F-Droid/res/drawable-xxxhdpi/ic_refresh_white.png new file mode 100644 index 0000000000000000000000000000000000000000..fe0ae13aae3e7af5b8c0f656213b125c2dd78fe0 GIT binary patch literal 2312 zcmbuB`9BnT7stOdwzMZrBZ`Of5P*6e)ydC=X}oj{hVlHBVATzK4t&_tG?bX(=!r(3r2rde@qm` zok7S^&-4xe0b&5)!T|Vr7U5O_@R0*x9SwkTIskA?cB6^P*#PQbsCx^XM!xQEJ?s9! z=vjLMz{2@k5RjS83jhqKe@nw0`(-P~-`zr+E7H>sDf18Ryc{yhOT*l}$i}OXFZz*# zQ2nKzT?t~}dzkRE9ylR;uVU%0D(=`S!Z1)}G2QsEwKc&3 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ஂ|tP literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_share_white.png b/F-Droid/res/drawable-xxxhdpi/ic_share_white.png new file mode 100644 index 0000000000000000000000000000000000000000..4eeff94cffb8837a453dba19120f80fd59bcc599 GIT binary patch literal 2298 zcmYk8dpy&77{|ZAnbW4@p44*d#Bz(WA=*%s`w%HgE}>EFy*7b9KnuB+90iVs&#-!{Gb z0h`{kPtOpuE#UMS#kVX(E29ISec(~Is$svKVKFxE)MCaT_wt%D6%@}ndmA!T!t|ZD z$>{vCB{J6I_>xibk9YDhR|OA~Pfpyjh@N@%(^|~K-AYVdn75uJOsdJd=`#utkOFW7 zNE;;pT|wFa?f)Txc}EGZFummK-Djh00iy7IZ2j<{3ees^xuUa2Z{yh}YP2*v>%jb= zxHO^b`CM|RE{J4W|LAj~?(VLiG6r)~ftbdp#km{dDc05UBcmvDlnuVODnqyL5dmuW zd-AEGwF)RsRhSdb8V9w{%1~SPo@) z0s|!5ZA%GaPz$6carEFe32ItO&z=X%)xCN!O(IY9U~g!azqJYssRHYt5|bZdxavCz zs#ONo#NiEWTM05qkc%=L$hvr#R%y*UDP3kRTbUpCshb>Yuj=hTF%NwkHG_03B1Kv` zr%sfC#oDwZwg)4-ej9(lv~~`vOrnH< zmTkpEdx+b7{Oo{eLQU>k%osJGYr>ErcDi>xWvtxQv&U(kH|k?MpT7|E)a04kXG+}o z`-Wm?m$(HT=3IMvCF+UsX!-GTt9EFfZi zKKJ~y^&F2PROS#X%vUlJ3P!UR>%ToSksH$}lES8;Ge%gHmJhw}u@CN-7+j|Ra3MD~ zq6W;&Ofs5BI&R`C(`u{PYg)}x_q^9d$^KX%>=|@h-z)kOI}l-PN>djLDtO^mK~2S7 zArXl~9xTy>K+UWo%~S1i7>ez6UGPacm3j;$F^g`x#6>$2POo~Iff_+d;R1g);>H6( z2QgTxnjfC;{IaT*W*q*z82`PO*C>}CtPJ0MWc4`M{G*RgT^!C4Y-`W4G;6$RJ9a)u z6ol&vBD1+Qsh>icijAW^R*P=T(Ze^bPw}n(NS)+UqSViBxz=@4C=~H8HRrKRhB4X~ z5;LP^=%WUe7P zrBj`K5xLz8zi{1fur~p!bJI z{3T0&E*)8RF>a4^gC|&Pd87v?Np@iy3HoFJQdttrk~pK);=3QR9U$GhE{g-Nd!*(_ z*<1{~M`_cQ#G9IEo3ofdi>wr_Fv~2kVc-c`wr4Ui_XNWIl$YUsW;=z-6Cz&wT~^xh z>ca~=Xo|^Sv470S+1y&SWNU9RBv*BX=vPQg#*7c2R`S;}%~-;J_|jGwoZek?Odpi6 zbViO|DIaQR^`#$r=n`{xH_$lD4#+6N3R8r%TKk;%CyqjFjVP(wY7>(QX19B0W-bJCoQu5K~*nZy3 z<*e$fZCNjoT1K*yIXw+k<0+A!cji58ZP@9$)Y4&GFON$!{%t0Ork4qhMDb$O-}Mm{K&^!Wb1DX;n6F5%|D7l*HLiCz{s2N4swFLT%&FQQT^#V z>Qk?}aebpmbETVHjSV$K!LuVN{q>`2+!zpA+b}qwOw?c$HwkMcb67T zjW|z&Xu>pvt4EwiuM(`>#pSpzEutrGZFKoY3d$TNNYL(nIyWF~1QjM- zRzo~@Wxx(!xFtu&iQuZz7jOonFP#pn5*-8-TOp>DU) zpurtr?=NX}E*KMT2dS$}ewR%vSdRB-&ux?ptB?e}MZu+YD)5`H`cX)qGi%=I|RPJFE*&&KVzIIL-W$_9I)^_%Z`)jf$?pj>WOg++RcXQTtt9p z8gsL=tZD9FmTO1qL4}>ud4Pho0YVE60|$f_xb{CpPo=RgDmFI$cn~Z(Nr2;il3m@t Hfb@R>F=;C| literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxxhdpi/ic_view_headline_white.png b/F-Droid/res/drawable-xxxhdpi/ic_view_headline_white.png new file mode 100644 index 0000000000000000000000000000000000000000..7674b081f8393f7cc6ae103b4266b0f8b7a3acd2 GIT binary patch literal 460 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}*n4DI|LY`7$t6sWC7#v@kII0tz*} zU|=XUU|@Kaz`$TNgMmT3V9u^U8=wSJlDE4H!~gdFGy8!&&H|6fVg?3oVGw3ym^DX& zfq~J*)5S5Q;?~>ShFnJscw8M%|M(YP_G*eE)9Ivpj6rV{#JTOiT{yXHK2Sd+g8~Bs z3qu0~g8VnwhyQPwH*Mzsz`}{{mH_?V4C0usZ1{JRJtbZK0jChUClFVdQ&MBb@0Q4<48~^|S literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_attach_money_white.png b/F-Droid/res/drawable/ic_attach_money_white.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f1e55676453bf4a1d246486a7be6a2e0e7afee GIT binary patch literal 565 zcmV-50?Pe~P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00FH@L_t(o!|j(pECfLm#&7Ljp}2@nK?o7OLO~&lQ;0ta z3K0^mKZQ{7}1%Q79uxOZm04#im9RU6uV4w}l93YQr)aV2B;0A?ZV+-Z#02Ax@ zJ#JC03RrU_d_)pSWVx9%N>u=@+=eWnj^-P2M9Pc`V3*SL72K&4raZa%x|o<@NfH9B zo#0LKfILgFA2PS%5)(kXS!sfCpJXFRigssBbehCkv=2z=TjwFf}881}T;z zE4YlEgx{A33~=RPirc7K%;ST!ewAAk`(djT(s*&q9jY14!X}DE91(RVchgxo;~wKE z(N&9Du;6GNSJB~CB7*i^JHVtq$aX6Z!GCf9&^004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00B-(L_t(o!|m8XZi6ro1yD=9O)pUO9t|Y;DuuQ=N)=T- zUezs@em4ciFb;oQTk0atCSdVjz%Vv(aCJZK29yN=;I%ejBys=~kp~!vT!1F>0g^~{ z+~Il{3n30A*S}l6Z-DU@8JPQ6?otYefJSTC8p+hBGn;@lLF90+SUjOIGpG0 z5%)Q_=}8dX2Jmz5h^O4=U`;Fl8NdIa#l+NP{Nb-pawiC^zU{_yrJ@tjvW;j;)gq0c z;`w6dS480AE!EUVW6+eJ4`QG#4Qww_TN3cfAd^IHJWW{QgSbU$!V=%kz=0<$@v_)8 zEMbX{%XPEqH3D7004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00I3;L_t(o!|j;OP8v}dK(By{x@$Bp+R9sC^g)bH5MM*| z0kpc6kV$NJDkdas8WNv_@B$j$h|_KzLUOvml+o}0&7C%N!N-m}IfvZ&e(pt~{_RK2 z1%E?OVv|F93`rO>CSgdA12!lX7gRar0aM)bm9=oeD(8%GMM}&{h@inwd?O*sCs^b= zc_XIcD_G)&km$4IBUlW?PZbrn;2T9Eb_p63jUAg{m0yJgPb`}S=i>1Rb*_!D3mQzs zw{4T4D#~iYHi|MG1HOzR5{i`1dcmnc)L&L~iYZ>9*cEHXIzdU(b2@(@Wv&ePvSthK zSdfBE?KbEYc09nk6dW2}4}G?6!eU_@>=18hprS1ExCP}f%@QKAO(rFd}azC zJdC8kHCiq*3Kt&I{~$=b@yb%&mMt23;j=HflkX+>yztkS>UN~*ffwH1KxVNo1sk4t zeRaQ$ny%U1d*a=STV1nLoQMY-mb9Fe-kw-H($9l6&A^E)SwB_O#lU&ylWAnexku)n zW@Mh3XYz`p+&1<6y#LRaI{K7GJM))m9*{Th{#;(((%YYFnDI*4S*{3#Z fY4g7l-m~C0=7T5^qgb*_00000NkvXXu0mjfw2c{^ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_delete_white.png b/F-Droid/res/drawable/ic_delete_white.png new file mode 100644 index 0000000000000000000000000000000000000000..cdb230c2f2bc051867c164c35f58cd410efbe139 GIT binary patch literal 270 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=12eq z+dN$yLn02py}psRK|zG&!s4*UQauT|`43H|C7m=oc8hV&LDifW8#f$15uS4**0WH5 zXA8@eu1XvccwE<9|DTMkW>x0R@K!2eAfc#TDf% zmKt6-X~VHLt4;qLnq&W&UEPooXxFznX~*j4=F=Ry=ByTtX57=Ldl~2=22WQ% Jmvv4FO#qn}S*-v7 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_do_not_disturb_white.png b/F-Droid/res/drawable/ic_do_not_disturb_white.png new file mode 100644 index 0000000000000000000000000000000000000000..4e4fa1e596a56293ecb4a4dcf5ba83658bde3451 GIT binary patch literal 815 zcmV+~1JL}5P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00O5;L_t(o!|j+)PZL2Dz-!r3Y-6H<_MpNi&?X#8$_Fvn z1F3%+8;+PDP!f)yTrs2xdeIn_CcT&@ehW?0gGv(v!3(8|k^pUpKM&51+wM$vwrJvk z*-N|cz4=YwygxGy5cs!G;0*9L071sdQ=&?P7A+c7DKSrq;OPM)tndZh*x@0=y#wN` z)52>sDKOLvV3Mzj>3%994buJqA)dBPu+K9xB)C9;AQu_s7SGvl(hd@SyB=s;NBfG3VM<&9$f8k-JBc>pHc{CdKK5hBJr zX>+q1AkMdr_%KDp<*(RdP!Cv_zRz>w6OrIZSgh#)BQmX~2{7RsF(Xs&iW;yY6h4Vq z@QVmZH!Z6GL0NNd%XfYeS)sknIX7Tj2*0eBuU5HY=r>`hK~-9#@$QoqS|m}Wf!u{$EC57T)edg+-=-4Gi>GV2D|5&T2d zSq%|USt^pMVz(oNcMZy#8Zjl_ZK;Y~fJZ{SNvJ18KcB^$C3Qa-mboxv)L=Rh3*y@` zmvt?(AY0~j--tz;;`6G$ClASVs}c8!u*YtDHS07;OU3s+B5rscyb4maM#2cO=$QVo z_sPzpEY**A;1N+_7Nqat4Si;Z-?C#(HL^sNlSi47f%$v-^QOml%i$=1174FOMVv4e zF_PrikXdUxr_bL7B+VYCW4Ci_&8|>`tU3Iv8^^3_+N{wf@G8q}YbW1g$(gmiUt`CZ tqez81O`6oHP-K?mpOSX=SHk}c_yuWBIHt^r_|yOZ002ovPDHLkV1jJF}I4okhJ31{w9syt6I zFaG|YeX_TQR>*0;&<3U_XZZMQ3J+&VE^urAXCk)nG&@J?Jr#=wJpV0Ns$IpKcW5<8 zwJUsl)+i-wXTjEC&HYDyt?!nI;sXW6HH(h6-Bv0$Jl-;WKG%G-7RFl{?y;ytFI zpSs^u)z9s2uzAn)X%F{_&pQuf=h^Wqe6nPi>d%~F$9e&zY5M*%Od8MF#f4XFO!+Mg P3LpkgS3j3^P6004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00K8jL_t(o!|j;ON?Sn`$77nLR0<-B3%|OlMY=0ARu|HT zkb+s*jYy;~;Ku_P>sFC~`UHU#je$NvH-bpW3sfu=W2&OIO(^`jnOu$c%$>QD6uR(a zH}gN|7v{`4Gjk(E{_QXF0B8pw$^^^gDO06RohoJWEHO^Bb-*xdd_!j^tTN;d=wpjI zCaaO9*99=c4{WVUDjXor4q+`z%ofneXI!jErwt&k34chM5fUVL!8}JAM9d7>794*v zdr#FtnqT5V82~e4_(?HSZ$-p(5TK89!DUvv)8+;B89jQymbmtf*_Z5d!!>)1dJ84x zVp9hg7RxH_g}?O1EXeMjwkl z$8`7s6QPPK7yx5_z;YAN@xJcb+r@0I!~UkvA*(-Iwc>ViR8aZB=?(oPwbCAV$* zTx=Vb+jPkS5LepoM&*JZFm3^8X#W_MNvYYL&@THbHM7)575fk$?i-=b4;3p}yCzQ-K) z(}w@w2R}ekoLL7yy`NgUCsYrc)GW$sy?JAXdjt-W;W(h@tlX_y_h;-Fi{vO#p+=1g lC2}l~ylrU@pA!COz+amLN;(}y`u_j`002ovPDHLkV1hF$BW?fy literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_info_white.png b/F-Droid/res/drawable/ic_info_white.png new file mode 100644 index 0000000000000000000000000000000000000000..bee33abb780f054d0a6a66240da9a1f96c9c3c39 GIT binary patch literal 530 zcmV+t0`2{YP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00D|gL_t(o!|m8lPQpMKhvAB4Av*(Mp~5X7a1at%a1Q<) z0F*6+G(k^5!p4wzj;0ro;6|u!X*aJ6V$@9AnNEl3f?v0E9%yI2`34K?xR-STOaM8W z3|Qjf6B6R%v7}FfTy~(!m{;Pm;f{(vP-aeuECCKB9iYQIs_|*31M)nQHV(EbP~aJz ztSG1edClPuge?ai$Pz~ebjX_9B%sVkW?)A#9+*pR{`y>UW^q7Ox~$ZIfa_>rEdBXv z0F0u5TtdxB3#>U4fhHzX7lDCMpeF)LqrgH0Jfnas0=~j+sxKL~A`lt{fDusl2nZS!oad#JO7YyV4x~LbZLjH6GMd{PxKQkT$y%GpD6< zgeo#4kPg0Q@_S;YI|SY^Vx3TPhCf%W{xP;rj|DDU0s^+UEa+1EE@`K~68k literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_nfc_white.png b/F-Droid/res/drawable/ic_nfc_white.png new file mode 100644 index 0000000000000000000000000000000000000000..428351e8eae3c6219c02b004f0c14e8dcec688ad GIT binary patch literal 406 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}a}tg=CK)Uj~LMH3o);76yi2K%s^g z3=E|}g|8AA7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^+;1OBOz`!jG!i)^F=14Fw zF!Fi2IEF+VetW~4>yUu}+k-m}D-$Nh9kAxeV_C2ENV4XED_3mSy3j2Zj7x8aiSOsm z%$)T_Fl|%Uxz1WUWr3n% z?7m{|7vj~n(hdF98_N$*kiX%%Fy=FJ=f3-&8E3LA`QxC%sAz9+boq_y1T3eO&S>-KQ}C7S}T0Ub-`P`Q)U-$opL+z$%d(IZ{fNgwYqx;);?-f`r&dc((*3$YWwOk`G`tao|pwDox*U-pzN zcCvc2)-QP1lWII=I7{Re59^(1Ly+=Pgg&e IbxsLQ0F7mBiU0rr literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_refresh_white.png b/F-Droid/res/drawable/ic_refresh_white.png new file mode 100644 index 0000000000000000000000000000000000000000..5f89fc257b71bee3b3e6ca55d89e57a3f3b2d4d4 GIT binary patch literal 637 zcmV-@0)qXCP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00H$$L_t(o!|j(%ZxT@yh7oM#OB;7Zi7_}Pu8cndb<-t~ zYFzM7gw|H5E=*jYR5pz+OkDLxXiCzi!L==Zpkg#GG&D?`Jl$MwX2NjqolzE=IkSPx zd*5g7aL&1h2$AVtkq{69{ujV$Zm~|A@7NsigKb*e=8T83;vaCC76ZIuz%wkDwhI0L z2_ABYDs0w>3-uEC16aJpBs%1Xuq?hA1Fq3`n7m_!JQ-$*G0%1Ce01FH)0CeL0hYsl zj{?y##YxJ1@z{?MAVEhi*dRG^muJ}w2B=GUO_O6$cKrh^Z29!MBwh}(gQk4a6+h+iscd#ng}h73 z$bOEVSnIcIL}S$4s-a-l_0ZeZtOt!jvX!p z9e(4~004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00GBIL_t(o!|j*LY63A7hTW))mxW!s@v^PrLPP|`=TP(| zT6)p8I=+G%rLQ4Msg9zpbYBD?Kz#;B@b98&jC7ndv00RQW;Nvh^JOw5=NtzdY9Gf5 z@ZSLmG%&;pCQPhgKu7%d0C~K?DkjJs5Afg-cF~4*4B!lJ;KdXHS%8O`ev1wY2*C*# z!YHDb(a|KKd17Q9y$3ebUyJ zrr!_~12l-O!(CaADC=T?A+Z&>fD%#m#Q-Z}3vmGvqFjgpOk#6#0dAsP%K=5CiDM*5v}8h_WUIh|>d2DqE;)T%Eei d6W~t*J^)IQO7UD07i<6k002ovPDHLkV1mcw=^g+8 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_settings_white.png b/F-Droid/res/drawable/ic_settings_white.png new file mode 100644 index 0000000000000000000000000000000000000000..12e5d100dd80ca03315f8d15af6289c3db015327 GIT binary patch literal 737 zcmV<70v`Q|P)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00LP_L_t(o!{wOIYZFlr$0xNhfhN^PB<61QP-zeTfPz1; z^dQxPr5C}!K?FUt)`L*6MGt!LRCWH-_|9`s9Bn=AF69nLToD#$tcrfQw2R&clt z?Jgx4ZbNGd=9r>Nk%`tk!#!$fQe%ms7H)zf-3#A zMO5-qBbW&=`pPULBuH?KIZ6TOX)nQf>OKZ}23)5jMZpJM=bUy-$F1*`C+11Wn3duS zr)+tUWkubb0Yn*T$_n}!2mKPQp9db3S=GHD%zLo++2fzdg0N{AjaUgr4W#46csp93 zY#W-S)GbMsl%GbQj`#LfL70Bfkj>C_|EMvq1{1{Pkz@?rB`bmXuj#Dh2rdWEd*6rEAeH9$d4v}Fsr#JEN<>Dyikq>Z;<_H98*skj_azRJuqN|GdF z+*UVdr(D#wXf?8O{ala1@rWAEHe z8?~_0z4vCfm-XgC-*S!p%uw;&4u@%mxX!!QbuAuLt}jp_&*|2D9}E0Yoo{i6!4~c; zPjQ$Gx;pBkXlbM&Y{Ql7^)|E}^@E-hthJ&2H&@^&AFVI6{?+Mc?5_S-(Q|^oR4iHa T4YrnW00000NkvXXu0mjftyfGA literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_share_white.png b/F-Droid/res/drawable/ic_share_white.png new file mode 100644 index 0000000000000000000000000000000000000000..dd536bca2db25dbc5ac58808858f540db25b2901 GIT binary patch literal 625 zcmV-%0*?KOP)004R> z004l5008;`004mK004C`008P>0026e000+ooVrmw00002VoOIv0RM-N%)bBt010qN zS#tmY3ljhU3ljkVnw%H_00HSqL_t(o!|j(bXcJKw$1la^5}|YwThWSxf}1#G5D@~+ zATExbibJ;sY&CImbJD4EOHwJJ&DNz|Iy#t`gm`I5MWKTfT5u4JwOT^^b7(oL&yXB_ucf9+*|M&kKoM<2C91uMq$qQO^>Ee+iAq}|BC)8?lO%jk~lW=X|$^mld zYFZA^)B+lEfWKP6pQr)*Qh@vX)B-+80Isr#sj{{JmzV4r+c)XA13YHSZ+6HmA4Bbj zZ7$kv)|6)NxPwESELHaC@Se%w*PjhA$P8V-`8H#=W*1Z5`ebHm22M?&_sPKhJMD@X0-L4vkXem|Jvg6+V0ZfJhU9G4^}n%*71b609) z>dou7+)R!f&@dcPN(%7Q@RN^t%a8=XrDc?9hdgonTyzC5hm(p+pOh5*dl` z`;gB8`7Q-SQ4Yu7UbNj$&Jb7t7G=WkWr~V?gO!#FUMaq60Y$m!=xG7?!W)1-!fZ?zEXPrLdf9Aor~7mKvlOGOLOa%%$3;_bK3kW iBfHfFC}v=gV78E%&KW;pq4F7^ItEWyKbLh*2~7Z?wl-t{ literal 0 HcmV?d00001 diff --git a/F-Droid/res/menu/main.xml b/F-Droid/res/menu/main.xml index e768a8152..b454adc54 100644 --- a/F-Droid/res/menu/main.xml +++ b/F-Droid/res/menu/main.xml @@ -4,12 +4,12 @@ diff --git a/F-Droid/res/menu/manage_repo_context.xml b/F-Droid/res/menu/manage_repo_context.xml index cef66b120..57506a395 100644 --- a/F-Droid/res/menu/manage_repo_context.xml +++ b/F-Droid/res/menu/manage_repo_context.xml @@ -5,11 +5,11 @@ + android:icon="@drawable/ic_edit_white" /> + android:icon="@drawable/ic_delete_white" /> \ No newline at end of file diff --git a/F-Droid/res/menu/manage_repos.xml b/F-Droid/res/menu/manage_repos.xml index 6fa0b699a..ef440e3dc 100644 --- a/F-Droid/res/menu/manage_repos.xml +++ b/F-Droid/res/menu/manage_repos.xml @@ -4,12 +4,12 @@ diff --git a/F-Droid/res/values/styles.xml b/F-Droid/res/values/styles.xml index 7c783a394..bc1e3c317 100644 --- a/F-Droid/res/values/styles.xml +++ b/F-Droid/res/values/styles.xml @@ -104,7 +104,6 @@ 10dp 5dp 5dp - @drawable/swap_action_button_skin + + + + + + diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java index 9723945b9..800c05eef 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java @@ -18,6 +18,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; +import android.widget.Button; import android.widget.TextView; import org.fdroid.fdroid.FDroidApp; @@ -56,6 +57,15 @@ public class JoinWifiFragment extends Fragment { openAvailableNetworks(); } }); + + Button bluetooth = (Button)joinWifiView.findViewById(R.id.btn_bluetooth); + bluetooth.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + ((SwapProcessManager)getActivity()).connectWithBluetooth(); + } + }); + return joinWifiView; } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java index 2c556bf14..654b705dc 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java @@ -1,7 +1,9 @@ package org.fdroid.fdroid.views.swap; import android.app.ProgressDialog; +import android.bluetooth.BluetoothAdapter; import android.content.Context; +import android.content.Intent; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -10,6 +12,7 @@ import android.support.annotation.NonNull; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v7.app.ActionBarActivity; +import android.util.Log; import android.view.MenuItem; import android.widget.Toast; @@ -19,6 +22,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.net.bluetooth.BluetoothServer; import java.util.Set; import java.util.Timer; @@ -31,6 +35,11 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage private static final String STATE_JOIN_WIFI = "joinWifi"; private static final String STATE_NFC = "nfc"; private static final String STATE_WIFI_QR = "wifiQr"; + private static final String STATE_BLUETOOTH_DEVICE_LIST = "bluetoothDeviceList"; + + private static final int REQUEST_ENABLE_BLUETOOTH = 1; + + private static final String TAG = "org.fdroid.fdroid.views.swap.SwapActivity"; private Timer shutdownLocalRepoTimer; private UpdateAsyncTask updateSwappableAppsTask = null; @@ -141,14 +150,14 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage return false; } - private void showBluetooth() { - - } - private void showWifiQr() { showFragment(new WifiQrFragment(), STATE_WIFI_QR); } + private void showBluetoothDeviceList() { + showFragment(new BluetoothDeviceListFragment(), STATE_BLUETOOTH_DEVICE_LIST); + } + private void showFragment(Fragment fragment, String name) { getSupportFragmentManager() .beginTransaction() @@ -215,6 +224,55 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage finish(); } + /** + * The process for setting up bluetooth is as follows: + * * Assume we have bluetooth available (otherwise the button which allowed us to start + * the bluetooth process should not have been available). TODO: Remove button if bluetooth unavailable. + * * Ask user to enable (if not enabled yet). + * * Start bluetooth server socket. + * * Enable bluetooth discoverability, so that people can connect to our server socket. + * + * Note that this is a little different than the usual process for bluetooth _clients_, which + * involves pairing and connecting with other devices. + */ + @Override + public void connectWithBluetooth() { + + Log.d(TAG, "Initiating Bluetooth swap instead of wifi."); + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + if (adapter.isEnabled()) { + Log.d(TAG, "Bluetooth enabled, will pair with device."); + startBluetoothServer(); + } else { + Log.d(TAG, "Bluetooth disabled, asking user to enable it."); + Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE); + startActivityForResult(enableBtIntent, REQUEST_ENABLE_BLUETOOTH); + } + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == REQUEST_ENABLE_BLUETOOTH) { + + if (resultCode == RESULT_OK) { + Log.d(TAG, "User enabled Bluetooth, will pair with device."); + startBluetoothServer(); + } else { + // Didn't enable bluetooth + Log.d(TAG, "User chose not to enable Bluetooth, so doing nothing (i.e. sticking with wifi)."); + } + + } + } + + private void startBluetoothServer() { + Log.d(TAG, "Starting bluetooth server."); + new BluetoothServer().start(); + showBluetoothDeviceList(); + } + class UpdateAsyncTask extends AsyncTask { @SuppressWarnings("UnusedDeclaration") diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java index cb9567bf9..9d2fbc9b4 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java @@ -9,4 +9,5 @@ package org.fdroid.fdroid.views.swap; public interface SwapProcessManager { void nextStep(); void stopSwapping(); + void connectWithBluetooth(); } diff --git a/bluetooth-notes.txt b/bluetooth-notes.txt new file mode 100644 index 000000000..918d0ca13 --- /dev/null +++ b/bluetooth-notes.txt @@ -0,0 +1,21 @@ +One is server, the other is the client (always the case with Bluetooth). + +When does the pairing happen? I can think of a few times: + +Use case 1 - + * Swapper decides to use bluetooth to send apps to others. + * Selects "Use bluetooth instead" on the "join wifi" screen. + * Starts a bluetooth server + + Make itself discoverable + + Opens a bluetooth server socket + + Waits for incoming client connections. + + * Swapee opens swap workflow + * Selects the bluetooth option + * Is asked to pair with nearby bluetooth devices, using the F-Droid UUID to make sure it doesn't connect to, e.g. bluetooth headphones. + * Stays connected in the background + * Adds the repo as per usual (with a url such as bluetooth://device-mac-address) + * When repo updates, it uses the open connection to get data + * If the connection has closed, attempts to reconnect + * Same when downloading files + diff --git a/res/layout/swap_bluetooth_header.xml b/res/layout/swap_bluetooth_header.xml new file mode 100644 index 000000000..ae9788b29 --- /dev/null +++ b/res/layout/swap_bluetooth_header.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothDownloader.java b/src/org/fdroid/fdroid/net/BluetoothDownloader.java similarity index 61% rename from src/org/fdroid/fdroid/net/bluetooth/BluetoothDownloader.java rename to src/org/fdroid/fdroid/net/BluetoothDownloader.java index da66f67d6..c1c770056 100644 --- a/src/org/fdroid/fdroid/net/bluetooth/BluetoothDownloader.java +++ b/src/org/fdroid/fdroid/net/BluetoothDownloader.java @@ -1,49 +1,45 @@ -package org.fdroid.fdroid.net.bluetooth; +package org.fdroid.fdroid.net; import android.content.Context; import android.util.Log; -import org.fdroid.fdroid.net.Downloader; +import org.fdroid.fdroid.net.bluetooth.BluetoothClient; +import org.fdroid.fdroid.net.bluetooth.FileDetails; import org.fdroid.fdroid.net.bluetooth.httpish.Request; import org.fdroid.fdroid.net.bluetooth.httpish.Response; -import java.io.*; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.MalformedURLException; public class BluetoothDownloader extends Downloader { - private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothDownloader"; + private static final String TAG = "org.fdroid.fdroid.net.BluetoothDownloader"; private BluetoothClient client; private FileDetails fileDetails; - public BluetoothDownloader(BluetoothClient client, String destFile, Context ctx) throws FileNotFoundException, MalformedURLException { + BluetoothDownloader(BluetoothClient client, String destFile, Context ctx) throws FileNotFoundException, MalformedURLException { super(destFile, ctx); - this.client = client; } - public BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException { + BluetoothDownloader(BluetoothClient client, Context ctx) throws IOException { super(ctx); - this.client = client; } - public BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException { + BluetoothDownloader(BluetoothClient client, File destFile) throws FileNotFoundException, MalformedURLException { super(destFile); - this.client = client; } - public BluetoothDownloader(BluetoothClient client, File destFile, Context ctx) throws IOException { - super(destFile, ctx); - this.client = client; - } - - public BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException { + BluetoothDownloader(BluetoothClient client, OutputStream output) throws MalformedURLException { super(output); - this.client = client; } @Override - public InputStream inputStream() throws IOException { - Response response = new Request(Request.Methods.GET, client).send(); + public InputStream getInputStream() throws IOException { + Response response = Request.createGET(sourceUrl.getPath(), client.openConnection()).send(); fileDetails = response.toFileDetails(); return response.toContentStream(); } @@ -58,7 +54,7 @@ public class BluetoothDownloader extends Downloader { if (fileDetails == null) { Log.d(TAG, "Going to Bluetooth \"server\" to get file details."); try { - fileDetails = new Request(Request.Methods.HEAD, client).send().toFileDetails(); + fileDetails = Request.createHEAD(sourceUrl.getPath(), client.openConnection()).send().toFileDetails(); } catch (IOException e) { Log.e(TAG, "Error getting file details from Bluetooth \"server\": " + e.getMessage()); } diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java index 68ddf52e6..4f77b80a1 100644 --- a/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java +++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java @@ -2,22 +2,18 @@ package org.fdroid.fdroid.net.bluetooth; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; -import android.bluetooth.BluetoothSocket; -import android.util.Log; -import org.fdroid.fdroid.Utils; -import java.io.*; -import java.util.UUID; +import java.io.IOException; public class BluetoothClient { private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothClient"; - private BluetoothAdapter adapter; + private final BluetoothAdapter adapter; private BluetoothDevice device; - public BluetoothClient(BluetoothAdapter adapter) { - this.adapter = adapter; + public BluetoothClient() { + this.adapter = BluetoothAdapter.getDefaultAdapter(); } public void pairWithDevice() throws IOException { @@ -27,55 +23,15 @@ public class BluetoothClient { } // TODO: Don't just take a random bluetooth device :) + device = adapter.getBondedDevices().iterator().next(); + device.createRfcommSocketToServiceRecord(BluetoothConstants.fdroidUuid()); } - public Connection openConnection() throws IOException { - return new Connection(); + public BluetoothConnection openConnection() throws IOException { + return null; + // return new BluetoothConnection(); } - public class Connection { - - private InputStream input = null; - private OutputStream output = null; - - private BluetoothSocket socket; - - private Connection() throws IOException { - Log.d(TAG, "Attempting to create connection to Bluetooth device '" + device.getName() + "'..."); - socket = device.createRfcommSocketToServiceRecord(UUID.fromString(BluetoothConstants.fdroidUuid())); - } - - public InputStream getInputStream() { - return input; - } - - public OutputStream getOutputStream() { - return output; - } - - public void open() throws IOException { - socket.connect(); - input = socket.getInputStream(); - output = socket.getOutputStream(); - Log.d(TAG, "Opened connection to Bluetooth device '" + device.getName() + "'"); - } - - public void closeQuietly() { - Utils.closeQuietly(input); - Utils.closeQuietly(output); - Utils.closeQuietly(socket); - } - - public void close() throws IOException { - if (input == null || output == null) { - throw new RuntimeException("Cannot close() a BluetoothConnection before calling open()" ); - } - - input.close(); - output.close(); - socket.close(); - } - } } diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java new file mode 100644 index 000000000..43ba63d8d --- /dev/null +++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConnection.java @@ -0,0 +1,61 @@ +package org.fdroid.fdroid.net.bluetooth; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothSocket; +import android.os.Build; +import android.util.Log; +import org.fdroid.fdroid.Utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class BluetoothConnection { + + private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothConnection"; + + private InputStream input = null; + private OutputStream output = null; + protected final BluetoothSocket socket; + + public BluetoothConnection(BluetoothSocket socket) throws IOException { + this.socket = socket; + } + + public InputStream getInputStream() { + return input; + } + + public OutputStream getOutputStream() { + return output; + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + public void open() throws IOException { + if (!socket.isConnected()) { + // Server sockets will already be connected when they are passed to us, + // client sockets require us to call connect(). + socket.connect(); + } + + input = socket.getInputStream(); + output = socket.getOutputStream(); + Log.d(TAG, "Opened connection to Bluetooth device"); + } + + public void closeQuietly() { + Utils.closeQuietly(input); + Utils.closeQuietly(output); + Utils.closeQuietly(socket); + } + + public void close() throws IOException { + if (input == null || output == null) { + throw new RuntimeException("Cannot close() a BluetoothConnection before calling open()" ); + } + + input.close(); + output.close(); + socket.close(); + } +} \ No newline at end of file diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java index 7a44a480c..e0876462d 100644 --- a/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java +++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothConstants.java @@ -1,14 +1,16 @@ package org.fdroid.fdroid.net.bluetooth; +import java.util.UUID; + /** * We need some shared information between the client and the server app. */ public class BluetoothConstants { - public static String fdroidUuid() { + public static UUID fdroidUuid() { // TODO: Generate a UUID deterministically from, e.g. "org.fdroid.fdroid.net.Bluetooth"; // This UUID is just from the first example at http://www.ietf.org/rfc/rfc4122.txt - return "f81d4fae-7dec-11d0-a765-00a0c91e6bf6"; + return UUID.fromString("f81d4fae-7dec-11d0-a765-00a0c91e6bf6"); } } diff --git a/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java b/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java new file mode 100644 index 000000000..570f5eba0 --- /dev/null +++ b/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java @@ -0,0 +1,112 @@ +package org.fdroid.fdroid.net.bluetooth; + +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothServerSocket; +import android.bluetooth.BluetoothSocket; +import android.util.Log; +import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.net.bluetooth.httpish.Request; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * Act as a layer on top of LocalHTTPD server, by forwarding requests served + * over bluetooth to that server. + */ +public class BluetoothServer extends Thread { + + private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer"; + + private BluetoothServerSocket serverSocket; + + private List clients = new ArrayList(); + + public void close() { + + for (Connection connection : clients) { + connection.interrupt(); + } + + if (serverSocket != null) { + Utils.closeQuietly(serverSocket); + } + + } + + @Override + public void run() { + + BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); + try { + serverSocket = adapter.listenUsingRfcommWithServiceRecord("FDroid App Swap", BluetoothConstants.fdroidUuid()); + } catch (IOException e) { + Log.e(TAG, "Error starting Bluetooth server socket, will stop the server now - " + e.getMessage()); + return; + } + + while (true) { + try { + BluetoothSocket clientSocket = serverSocket.accept(); + if (clientSocket != null && !isInterrupted()) { + Connection client = new Connection(clientSocket); + client.start(); + clients.add(client); + } else { + break; + } + } catch (IOException e) { + Log.e(TAG, "Error receiving client connection over Bluetooth server socket, will continue listening for other clients - " + e.getMessage()); + } + } + + } + + private static class Connection extends Thread + { + + private static final String TAG = "org.fdroid.fdroid.net.bluetooth.BluetoothServer.Connection"; + private BluetoothSocket socket; + + public Connection(BluetoothSocket socket) { + this.socket = socket; + } + + @Override + public void run() { + + Log.d(TAG, "Listening for incoming Bluetooth requests from client"); + + BluetoothConnection connection; + try { + connection = new BluetoothConnection(socket); + } catch (IOException e) { + Log.e(TAG, "Error listening for incoming connections over bluetooth - " + e.getMessage()); + return; + } + + while (true) { + + try { + Log.d(TAG, "Listening for new Bluetooth request from client."); + Request incomingRequest = Request.listenForRequest(connection); + handleRequest(incomingRequest); + } catch (IOException e) { + Log.e(TAG, "Error receiving incoming connection over bluetooth - " + e.getMessage()); + } + + if (isInterrupted()) + break; + + } + + } + + private void handleRequest(Request request) { + + Log.d(TAG, "Received Bluetooth request from client, will process it now."); + + } + } +} diff --git a/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java b/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java index 32ae07eef..3066c7142 100644 --- a/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java +++ b/src/org/fdroid/fdroid/net/bluetooth/httpish/Request.java @@ -1,37 +1,53 @@ package org.fdroid.fdroid.net.bluetooth.httpish; -import org.fdroid.fdroid.net.bluetooth.BluetoothClient; +import org.fdroid.fdroid.net.bluetooth.BluetoothConnection; import java.io.*; import java.util.HashMap; +import java.util.Locale; import java.util.Map; public class Request { + public static interface Methods { public static final String HEAD = "HEAD"; public static final String GET = "GET"; } - private final BluetoothClient client; - private final String method; + private String method; + private String path; + private Map headers; - private BluetoothClient.Connection connection; + private BluetoothConnection connection; private BufferedWriter output; private BufferedReader input; - public Request(String method, BluetoothClient client) { + private Request(String method, String path, BluetoothConnection connection) { this.method = method; - this.client = client; + this.path = path; + this.connection = connection; + } + + public static Request createHEAD(String path, BluetoothConnection connection) + { + return new Request(Methods.HEAD, path, connection); + } + + public static Request createGET(String path, BluetoothConnection connection) { + return new Request(Methods.GET, path, connection); } public Response send() throws IOException { - connection = client.openConnection(); output = new BufferedWriter(new OutputStreamWriter(connection.getOutputStream())); input = new BufferedReader(new InputStreamReader(connection.getInputStream())); output.write(method); + output.write(' '); + output.write(path); + + output.write("\n\n"); int responseCode = readResponseCode(); Map headers = readHeaders(); @@ -44,6 +60,40 @@ public class Request { } + /** + * Helper function used by listenForRequest(). + * The reason it is here is because the listenForRequest() is a static function, which would + * need to instantiate it's own InputReaders from the bluetooth connection. However, we already + * have that happening in a Request, so it is in some ways simpler to delegate to a member + * method like this. + */ + private boolean listen() throws IOException { + + String requestLine = input.readLine(); + + if (requestLine == null || requestLine.trim().length() == 0) + return false; + + String[] parts = requestLine.split("\\s+"); + + // First part is the method (GET/HEAD), second is the path (/fdroid/repo/index.jar) + if (parts.length < 2) + return false; + + method = parts[0].toUpperCase(Locale.ENGLISH); + path = parts[1]; + headers = readHeaders(); + return true; + } + + /** + * This is a blocking method, which will wait until a full Request is received. + */ + public static Request listenForRequest(BluetoothConnection connection) throws IOException { + Request request = new Request("", "", connection); + return request.listen() ? request : null; + } + /** * First line of a HTTP response is the status line: * http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1 diff --git a/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java b/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java new file mode 100644 index 000000000..f9e68699b --- /dev/null +++ b/src/org/fdroid/fdroid/views/swap/BluetoothDeviceListFragment.java @@ -0,0 +1,111 @@ +package org.fdroid.fdroid.views.swap; + +import android.bluetooth.BluetoothDevice; +import android.content.Context; +import android.database.Cursor; +import android.os.Bundle; +import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.data.InstalledAppProvider; +import org.fdroid.fdroid.views.fragments.ThemeableListFragment; + +import java.util.List; + +public class BluetoothDeviceListFragment extends ThemeableListFragment { + + private Adapter adapter = null; + + private class Adapter extends ArrayAdapter { + + public Adapter(Context context, int resource) { + super(context, resource); + } + + public Adapter(Context context, int resource, int textViewResourceId) { + super(context, resource, textViewResourceId); + } + + public Adapter(Context context, int resource, BluetoothDevice[] objects) { + super(context, resource, objects); + } + + public Adapter(Context context, int resource, int textViewResourceId, BluetoothDevice[] objects) { + super(context, resource, textViewResourceId, objects); + } + + public Adapter(Context context, int resource, List objects) { + super(context, resource, objects); + } + + public Adapter(Context context, int resource, int textViewResourceId, List objects) { + super(context, resource, textViewResourceId, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + View view; + if (convertView == null) { + view = getActivity().getLayoutInflater().inflate(android.R.layout.simple_list_item_2, parent); + } else { + view = convertView; + } + + BluetoothDevice device = getItem(position); + TextView nameView = (TextView)view.findViewById(android.R.id.text1); + TextView descriptionView = (TextView)view.findViewById(android.R.id.text2); + + nameView.setText(device.getName()); + descriptionView.setText(device.getAddress()); + + return view; + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(false); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + setEmptyText("No bluetooth devices found. Is the other device \"discoverable\"?"); + + Adapter adapter = new Adapter( + new ContextThemeWrapper(getActivity(), R.style.SwapTheme_BluetoothDeviceList_ListItem), + R.layout.select_local_apps_list_item + ); + + setListAdapter(adapter); + setListShown(false); // start out with a progress indicator + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + Cursor c = (Cursor) l.getAdapter().getItem(position); + String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); + if (FDroidApp.selectedApps.contains(packageName)) { + FDroidApp.selectedApps.remove(packageName); + } else { + FDroidApp.selectedApps.add(packageName); + } + } + + @Override + protected int getThemeStyle() { + return R.style.SwapTheme_StartSwap; + } + + @Override + protected int getHeaderLayout() { + return R.layout.swap_bluetooth_header; + } +} From 7dff9a9499253f3a161a4fe918c8931ee64c1769 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 14 Oct 2014 07:02:19 +1030 Subject: [PATCH 09/78] WIP: Bluetooth communication between devices is up and running (not finished). Devices now make themselves discoverable, and the client sends a test ping. They UI is not styles properly though, and it doesn't handle the case where somebody chooses to make their device not-discoverable (because the desired peer is already paired and it is unneccesary). It also doesn't handle failure anywhere. --- F-Droid/AndroidManifest.xml | 3 +- F-Droid/res/values/styles.xml | 11 +- F-Droid/src/org/fdroid/fdroid/FDroidApp.java | 111 ++++++---- .../fdroid/localrepo/LocalRepoService.java | 155 +++---------- .../org/fdroid/fdroid/net/HttpDownloader.java | 18 +- .../src/org/fdroid/fdroid/net/LocalHTTPD.java | 4 +- .../fdroid/net/WifiStateChangeService.java | 2 +- .../fdroid/views/LocalRepoActivity.java | 10 +- .../views/QrWizardWifiNetworkActivity.java | 2 +- .../fragments/ThemeableListFragment.java | 13 +- .../fdroid/views/swap/SwapActivity.java | 56 +++-- res/layout-v14/simple_list_item_3.xml | 52 +++++ res/layout/simple_list_item_3.xml | 52 +++++ res/layout/swap_bluetooth_header.xml | 38 +++- .../localrepo/LocalRepoProxyService.java | 29 +++ .../localrepo/LocalRepoWifiService.java | 160 ++++++++++++++ .../fdroid/net/bluetooth/BluetoothClient.java | 26 +-- .../fdroid/net/bluetooth/BluetoothServer.java | 45 +++- .../fdroid/net/bluetooth/httpish/Request.java | 36 ++- .../net/bluetooth/httpish/Response.java | 82 ++++++- .../swap/BluetoothDeviceListFragment.java | 208 ++++++++++++++---- 21 files changed, 832 insertions(+), 281 deletions(-) create mode 100644 res/layout-v14/simple_list_item_3.xml create mode 100644 res/layout/simple_list_item_3.xml create mode 100644 src/org/fdroid/fdroid/localrepo/LocalRepoProxyService.java create mode 100644 src/org/fdroid/fdroid/localrepo/LocalRepoWifiService.java diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml index 42b92faf2..083f0cfc0 100644 --- a/F-Droid/AndroidManifest.xml +++ b/F-Droid/AndroidManifest.xml @@ -456,7 +456,8 @@ - + + diff --git a/F-Droid/res/values/styles.xml b/F-Droid/res/values/styles.xml index 9626b410e..0a2dcc8b2 100644 --- a/F-Droid/res/values/styles.xml +++ b/F-Droid/res/values/styles.xml @@ -43,15 +43,20 @@ @color/white - + + - + + - - - From 38059ec324fbc03a2c841a4f266c02a331ed1a7d Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sat, 23 May 2015 13:17:33 +1000 Subject: [PATCH 22/78] WIP: Initial state handling for swap process. The state is saved to a preference file, but that is abstracted from the SwapWorkflowActivity. It interacts with a SwapState object, which is relatively safe in that you should not be able to save it into an invalid state. Note: At this point it is not tied to any service. I'm not sure it will ever have to be either, as the service needs to persist state somewhere anyway, so that will probably also end up saving to a preferences file too. --- .../fdroid/fdroid/localrepo/SwapState.java | 73 +++++++++++++++++++ .../views/swap/SwapWorkflowActivity.java | 49 ++++++++++--- .../fdroid/views/swap/views/JoinWifiView.java | 6 ++ .../fdroid/views/swap/views/NfcView.java | 6 ++ .../views/swap/views/SelectAppsView.java | 6 ++ .../views/swap/views/StartSwapView.java | 8 +- .../fdroid/views/swap/views/WifiQrView.java | 6 ++ 7 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java new file mode 100644 index 000000000..1783eb000 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java @@ -0,0 +1,73 @@ +package org.fdroid.fdroid.localrepo; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class SwapState { + + private static final String SHARED_PREFERENCES = "swap-state"; + + public static final int STEP_INTRO = 1; + public static final int STEP_SELECT_APPS = 2; + public static final int STEP_JOIN_WIFI = 3; + public static final int STEP_SHOW_NFC = 4; + public static final int STEP_WIFI_QR = 5; + + private int step; + + @NonNull + private final Context context; + + private SwapState(@NonNull Context context) { + this.context = context; + } + + /** + * Current screen that the swap process is up to. + * Will be one of the SwapState.STEP_* values. + */ + @SwapStep + public int getStep() { + return step; + } + + public SwapState setStep(@SwapStep int step) { + this.step = step; + persist(); + return this; + } + + private static final String KEY_STEP = "step"; + + @NonNull + public static SwapState load(@NonNull Context context) { + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + + @SwapStep int step = preferences.getInt(KEY_STEP, STEP_INTRO); + + return new SwapState(context) + .setStep(step); + } + + private void persist() { + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_APPEND); + preferences.edit() + .putInt(KEY_STEP, step) + .commit(); + } + + /** + * Ensure that we don't get put into an incorrect state, by forcing people to pass valid + * states to setStep. Ideally this would be done by requiring an enum or something to + * be passed rather than in integer, however that is harder to persist on disk than an int. + * This is the same as, e.g. {@link Context#getSystemService(String)} + */ + @IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR}) + @Retention(RetentionPolicy.SOURCE) + public @interface SwapStep {} +} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index f8fc37ace..72e8e4b37 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -28,6 +28,7 @@ import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.SwapState; import java.util.Set; import java.util.Timer; @@ -37,18 +38,20 @@ public class SwapWorkflowActivity extends FragmentActivity { private ViewGroup container; - private enum State { - INTRO, SELECT_APPS, JOIN_WIFI - } - public interface InnerView { /** @return True if the menu should be shown. */ boolean buildMenu(Menu menu, @NonNull MenuInflater inflater); + + /** @return The step that this view represents. */ + @SwapState.SwapStep int getStep(); + + // TODO: Handle back presses with a method like this: + // @SwapState.SwapStep int getPreviousStep(); } private static final int CONNECT_TO_SWAP = 1; - private State currentState = State.INTRO; + private SwapState state; private InnerView currentView; private boolean hasPreparedLocalRepo = false; private UpdateAsyncTask updateSwappableAppsTask = null; @@ -57,8 +60,10 @@ public class SwapWorkflowActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + state = SwapState.load(this); setContentView(R.layout.swap_activity); container = (ViewGroup) findViewById(R.id.fragment_container); + showRelevantView(); } @Override @@ -72,12 +77,37 @@ public class SwapWorkflowActivity extends FragmentActivity { @Override protected void onResume() { super.onResume(); - showView(); + showRelevantView(); } - private void showView() { - if (currentState == State.INTRO) { - showIntro(); + private void showRelevantView() { + if (currentView != null && currentView.getStep() == state.getStep()) { + // Already showing the currect step, so don't bother changing anything. + return; + } + + switch(state.getStep()) { + case SwapState.STEP_INTRO: + showIntro(); + break; + case SwapState.STEP_SELECT_APPS: + showSelectApps(); + break; + case SwapState.STEP_SHOW_NFC: + showNfc(); + break; + case SwapState.STEP_JOIN_WIFI: + showJoinWifi(); + break; + case SwapState.STEP_WIFI_QR: + showWifiQr(); + break; + } + } + + private void showNfc() { + if (!attemptToShowNfc()) { + showWifiQr(); } } @@ -85,6 +115,7 @@ public class SwapWorkflowActivity extends FragmentActivity { container.removeAllViews(); View view = ((LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE)).inflate(viewRes, container, false); currentView = (InnerView)view; + state.setStep(currentView.getStep()); container.addView(view); supportInvalidateOptionsMenu(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java index 653db430b..e77d0b9a6 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java @@ -22,6 +22,7 @@ import android.widget.TextView; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.localrepo.SwapState; import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; @@ -117,4 +118,9 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity }); return true; } + + @Override + public int getStep() { + return SwapState.STEP_JOIN_WIFI; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java index 63ce23c61..b3a566203 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java @@ -15,6 +15,7 @@ import android.widget.RelativeLayout; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.localrepo.SwapState; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class NfcView extends RelativeLayout implements SwapWorkflowActivity.InnerView { @@ -67,4 +68,9 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne }); return true; } + + @Override + public int getStep() { + return SwapState.STEP_SHOW_NFC; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java index 7a17788da..2917e8c02 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java @@ -36,6 +36,7 @@ import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.InstalledAppProvider; import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.SwapState; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; import java.util.HashSet; @@ -142,6 +143,11 @@ public class SelectAppsView extends ListView implements return true; } + @Override + public int getStep() { + return SwapState.STEP_SELECT_APPS; + } + private void toggleAppSelected(int position) { Cursor c = (Cursor) adapter.getItem(position - 1); String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java index 72560f92d..a681f3de7 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java @@ -12,6 +12,7 @@ import android.view.View; import android.widget.LinearLayout; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.localrepo.SwapState; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.InnerView { @@ -34,8 +35,6 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. super(context, attrs, defStyleAttr, defStyleRes); } - - private SwapWorkflowActivity getActivity() { // TODO: Try and find a better way to get to the SwapActivity, which makes less asumptions. return (SwapWorkflowActivity)getContext(); @@ -58,4 +57,9 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. public boolean buildMenu(Menu menu, @NonNull MenuInflater inflater) { return false; } + + @Override + public int getStep() { + return SwapState.STEP_INTRO; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java index 09b5d66c6..47120a492 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java @@ -30,6 +30,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.localrepo.SwapState; import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; @@ -106,6 +107,11 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner return false; } + @Override + public int getStep() { + return SwapState.STEP_WIFI_QR; + } + private void setUIFromWifi() { if (TextUtils.isEmpty(FDroidApp.repo.address)) From 68c6648da51e9e1288f2d20903ad83c3e7bd0772 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 25 May 2015 08:17:09 +1000 Subject: [PATCH 23/78] WIP: Store selected apps in SwapState. Handle back presses for swap. Currently the view to show after pressing the back button is defined by individual InnerView classes. For example, the JoinWifiView always says its previous step is the SelectAppsView. This works for the most part, but doesn't work for: * The first StartSwapView class (instead handled by a special case in the Activity). * WifiQrView which could either be going back to the NfcView or the JoinWifiView, depending on a few cases, which the WifiQrView probably shouldn't care about. --- .../fdroid/fdroid/localrepo/SwapState.java | 101 +++++++++++++++--- .../views/swap/SwapWorkflowActivity.java | 30 ++++-- .../fdroid/views/swap/views/JoinWifiView.java | 10 +- .../fdroid/views/swap/views/NfcView.java | 6 +- .../views/swap/views/SelectAppsView.java | 44 +++----- .../views/swap/views/StartSwapView.java | 15 +++ .../fdroid/views/swap/views/WifiQrView.java | 13 ++- 7 files changed, 168 insertions(+), 51 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java index 1783eb000..802de5cd9 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java @@ -7,6 +7,9 @@ import android.support.annotation.NonNull; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; public class SwapState { @@ -18,13 +21,19 @@ public class SwapState { public static final int STEP_SHOW_NFC = 4; public static final int STEP_WIFI_QR = 5; - private int step; @NonNull private final Context context; - private SwapState(@NonNull Context context) { + @NonNull + private Set appsToSwap; + + private int step; + + private SwapState(@NonNull Context context, @SwapStep int step, @NonNull Set appsToSwap) { this.context = context; + this.step = step; + this.appsToSwap = appsToSwap; } /** @@ -38,27 +47,93 @@ public class SwapState { public SwapState setStep(@SwapStep int step) { this.step = step; - persist(); + persistStep(); return this; } - private static final String KEY_STEP = "step"; + public Set getAppsToSwap() { + return appsToSwap; + } + + private static final String KEY_STEP = "step"; + private static final String KEY_APPS_TO_SWAP = "appsToSwap"; + + private static SwapState instance; @NonNull public static SwapState load(@NonNull Context context) { - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); + if (instance == null) { + SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); - @SwapStep int step = preferences.getInt(KEY_STEP, STEP_INTRO); + @SwapStep int step = preferences.getInt(KEY_STEP, STEP_INTRO); + Set appsToSwap = deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, "")); - return new SwapState(context) - .setStep(step); + instance = new SwapState(context, step, appsToSwap); + } + + return instance; } - private void persist() { - SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_APPEND); - preferences.edit() - .putInt(KEY_STEP, step) - .commit(); + private SharedPreferences persistence() { + return context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_APPEND); + } + + private void persistStep() { + persistence().edit().putInt(KEY_STEP, step).commit(); + } + + private void persistAppsToSwap() { + persistence().edit().putString(KEY_APPS_TO_SWAP, serializePackages(appsToSwap)).commit(); + } + + /** + * Replacement for {@link android.content.SharedPreferences.Editor#putStringSet(String, Set)} + * which is only available in API >= 11. + * Package names are reverse-DNS-style, so they should only have alpha numeric values. Thus, + * this uses a comma as the separator. + * @see SwapState#deserializePackages(String) + */ + private static String serializePackages(Set packages) { + StringBuilder sb = new StringBuilder(); + for (String pkg : packages) { + if (sb.length() > 0) { + sb.append(','); + } + sb.append(pkg); + } + return sb.toString(); + } + + /** + * @see SwapState#deserializePackages(String) + */ + private static Set deserializePackages(String packages) { + Set set = new HashSet<>(); + Collections.addAll(set, packages.split(",")); + return set; + } + + public void ensureFDroidSelected() { + String fdroid = context.getPackageName(); + if (!hasSelectedPackage(fdroid)) { + selectPackage(fdroid); + } + } + + public boolean hasSelectedPackage(String packageName) { + return appsToSwap.contains(packageName); + } + + public void selectPackage(String packageName) { + appsToSwap.add(packageName); + persistAppsToSwap(); + } + + public void deselectPackage(String packageName) { + if (appsToSwap.contains(packageName)) { + appsToSwap.remove(packageName); + } + persistAppsToSwap(); } /** diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 72e8e4b37..1dab7b974 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -45,8 +45,7 @@ public class SwapWorkflowActivity extends FragmentActivity { /** @return The step that this view represents. */ @SwapState.SwapStep int getStep(); - // TODO: Handle back presses with a method like this: - // @SwapState.SwapStep int getPreviousStep(); + @SwapState.SwapStep int getPreviousStep(); } private static final int CONNECT_TO_SWAP = 1; @@ -57,6 +56,17 @@ public class SwapWorkflowActivity extends FragmentActivity { private UpdateAsyncTask updateSwappableAppsTask = null; private Timer shutdownLocalRepoTimer; + @Override + public void onBackPressed() { + if (currentView.getStep() == SwapState.STEP_INTRO) { + finish(); + } else { + int nextStep = currentView.getPreviousStep(); + state.setStep(nextStep); + showRelevantView(); + } + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -105,6 +115,10 @@ public class SwapWorkflowActivity extends FragmentActivity { } } + public SwapState getState() { + return state; + } + private void showNfc() { if (!attemptToShowNfc()) { showWifiQr(); @@ -128,10 +142,12 @@ public class SwapWorkflowActivity extends FragmentActivity { inflateInnerView(R.layout.swap_select_apps); } - // TODO: Pass in the selected apps, then we can figure out whether they have changed. - public void onAppsSelected(Set selectedApps) { + // TODO: Figure out whether they have changed since last time UpdateAsyncTask was run. + // If the local repo is running, then we can ask it what apps it is swapping and compare with that. + // Otherwise, probably will need to scan the file system. + public void onAppsSelected() { if (updateSwappableAppsTask == null && !hasPreparedLocalRepo) { - updateSwappableAppsTask = new UpdateAsyncTask(this, selectedApps); + updateSwappableAppsTask = new UpdateAsyncTask(state.getAppsToSwap()); updateSwappableAppsTask.execute(); } else { showJoinWifi(); @@ -247,10 +263,10 @@ public class SwapWorkflowActivity extends FragmentActivity { @NonNull private final Context context; - public UpdateAsyncTask(Context c, @NonNull Set apps) { + public UpdateAsyncTask(@NonNull Set apps) { context = SwapWorkflowActivity.this.getApplicationContext(); selectedApps = apps; - progressDialog = new ProgressDialog(c); + progressDialog = new ProgressDialog(context); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); progressDialog.setTitle(R.string.updating); sharingUri = Utils.getSharingUri(FDroidApp.repo); diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java index e77d0b9a6..44a16c4e4 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java @@ -46,7 +46,6 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity } private SwapWorkflowActivity getActivity() { - // TODO: Try and find a better way to get to the SwapActivity, which makes less asumptions. return (SwapWorkflowActivity)getContext(); } @@ -61,7 +60,8 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity }); refreshWifiState(); - // TODO: Listen for "Connecting..." state and reflect that in the view too. + // TODO: This is effectively swap state management code, shouldn't be isolated to the + // WifiStateChangeService, but should be bundled with the main swap state handling code. LocalBroadcastManager.getInstance(getActivity()).registerReceiver( new BroadcastReceiver() { @Override @@ -74,6 +74,7 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity } + // TODO: Listen for "Connecting..." state and reflect that in the view too. private void refreshWifiState() { TextView descriptionView = (TextView) findViewById(R.id.text_description); ImageView wifiIcon = (ImageView) findViewById(R.id.wifi_icon); @@ -123,4 +124,9 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity public int getStep() { return SwapState.STEP_JOIN_WIFI; } + + @Override + public int getPreviousStep() { + return SwapState.STEP_SELECT_APPS; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java index b3a566203..482e23f8d 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java @@ -38,7 +38,6 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne } private SwapWorkflowActivity getActivity() { - // TODO: Try and find a better way to get to the SwapActivity, which makes less asumptions. return (SwapWorkflowActivity)getContext(); } @@ -73,4 +72,9 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne public int getStep() { return SwapState.STEP_SHOW_NFC; } + + @Override + public int getPreviousStep() { + return SwapState.STEP_JOIN_WIFI; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java index 2917e8c02..b07347fc7 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java @@ -32,15 +32,11 @@ import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; -import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.InstalledAppProvider; -import org.fdroid.fdroid.localrepo.LocalRepoManager; import org.fdroid.fdroid.localrepo.SwapState; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; -import java.util.HashSet; - public class SelectAppsView extends ListView implements SwapWorkflowActivity.InnerView, LoaderManager.LoaderCallbacks, @@ -67,6 +63,10 @@ public class SelectAppsView extends ListView implements return (SwapWorkflowActivity)getContext(); } + private SwapState getState() { + return getActivity().getState(); + } + private static final int LOADER_INSTALLED_APPS = 253341534; private AppListAdapter adapter; @@ -95,17 +95,6 @@ public class SelectAppsView extends ListView implements // either reconnect with an existing loader or start a new one getActivity().getSupportLoaderManager().initLoader(LOADER_INSTALLED_APPS, null, this); - // build list of existing apps from what is on the file system - if (FDroidApp.selectedApps == null) { - FDroidApp.selectedApps = new HashSet<>(); - for (String filename : LocalRepoManager.get(getActivity()).repoDir.list()) { - if (filename.matches(".*\\.apk")) { - String packageName = filename.substring(0, filename.indexOf("_")); - FDroidApp.selectedApps.add(packageName); - } - } - } - setOnItemClickListener(new AdapterView.OnItemClickListener() { public void onItemClick(AdapterView parent, View v, int position, long id) { if (position > 0) { @@ -128,7 +117,7 @@ public class SelectAppsView extends ListView implements nextMenuItem.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { - getActivity().onAppsSelected(FDroidApp.selectedApps); + getActivity().onAppsSelected(); return true; } }); @@ -148,13 +137,18 @@ public class SelectAppsView extends ListView implements return SwapState.STEP_SELECT_APPS; } + @Override + public int getPreviousStep() { + return SwapState.STEP_INTRO; + } + private void toggleAppSelected(int position) { Cursor c = (Cursor) adapter.getItem(position - 1); String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); - if (FDroidApp.selectedApps.contains(packageName)) { - FDroidApp.selectedApps.remove(packageName); + if (getState().hasSelectedPackage(packageName)) { + getState().deselectPackage(packageName); } else { - FDroidApp.selectedApps.add(packageName); + getState().selectPackage(packageName); } } @@ -179,17 +173,13 @@ public class SelectAppsView extends ListView implements public void onLoadFinished(Loader loader, Cursor cursor) { adapter.swapCursor(cursor); - String fdroid = loader.getContext().getPackageName(); for (int i = 0; i < getCount() - 1; i++) { Cursor c = ((Cursor) getItemAtPosition(i + 1)); String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); - if (TextUtils.equals(packageName, fdroid)) { - setItemChecked(i + 1, true); // always include FDroid - } else { - for (String selected : FDroidApp.selectedApps) { - if (TextUtils.equals(packageName, selected)) { - setItemChecked(i + 1, true); - } + getState().ensureFDroidSelected(); + for (String selected : getState().getAppsToSwap()) { + if (TextUtils.equals(packageName, selected)) { + setItemChecked(i + 1, true); } } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java index a681f3de7..c251dd0bf 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java @@ -17,6 +17,12 @@ import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.InnerView { + // TODO: Is there a way to guarangee which of these constructors the inflater will call? + // Especially on different API levels? It would be nice to only have the one which accepts + // a Context, but I'm not sure if that is correct or not. As it stands, this class provides + // constructurs which match each of the ones available in the parent class. + // The same is true for the other views in the swap process too. + public StartSwapView(Context context) { super(context); } @@ -62,4 +68,13 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. public int getStep() { return SwapState.STEP_INTRO; } + + @Override + public int getPreviousStep() { + // TODO: Currently this is handleed by the SwapWorkflowActivity as a special case, where + // if getStep is STEP_INTRO, don't even bother asking for getPreviousStep. But that is a + // bit messy. It would be nicer if this was handled using the same mechanism as everything + // else. + return SwapState.STEP_INTRO; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java index 47120a492..fc451de9b 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java @@ -60,7 +60,6 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner } private SwapWorkflowActivity getActivity() { - // TODO: Try and find a better way to get to the SwapActivity, which makes less asumptions. return (SwapWorkflowActivity)getContext(); } @@ -78,6 +77,9 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner openQr.setOnClickListener(new Button.OnClickListener() { @Override public void onClick(View v) { + // TODO: Should probably ask the activity or some other class to do this for us. + // The view should be dumb and only know how to show things and delegate things to + // other classes that know how to do things. IntentIntegrator integrator = new IntentIntegrator(getActivity()); integrator.initiateScan(); } @@ -91,6 +93,9 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner } }); + // TODO: As with the JoinWifiView, this should be refactored to be part of the SwapState. + // Otherwise, we are left with SwapState, LocalRepoService, WifiStateChangeService, and + // some static variables in FDroidApp all which manage the state for swap. LocalBroadcastManager.getInstance(getActivity()).registerReceiver( new BroadcastReceiver() { @Override @@ -112,6 +117,12 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner return SwapState.STEP_WIFI_QR; } + @Override + public int getPreviousStep() { + // TODO: Find a way to make this optionally go back to the NFC screen if appropriate. + return SwapState.STEP_JOIN_WIFI; + } + private void setUIFromWifi() { if (TextUtils.isEmpty(FDroidApp.repo.address)) From f325e9d057f4c0edff54ec9796c690e2c155be4c Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 25 May 2015 08:39:31 +1000 Subject: [PATCH 24/78] WIP: Moved start/stop local repo code from static FDroidApp to SwapState Didn't change any behaviour, but wanted to start unifying swap state management in one location. --- F-Droid/src/org/fdroid/fdroid/FDroidApp.java | 52 +-------------- .../fdroid/fdroid/localrepo/SwapState.java | 64 ++++++++++++++++++- .../fdroid/net/WifiStateChangeService.java | 3 +- .../views/swap/ConnectSwapActivity.java | 3 +- .../fdroid/views/swap/SwapActivity.java | 17 +++-- .../views/swap/SwapWorkflowActivity.java | 10 +-- 6 files changed, 84 insertions(+), 65 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java index 6f99b3106..7c0036545 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java @@ -73,9 +73,8 @@ public class FDroidApp extends Application { // Leaving the fully qualified class name here to help clarify the difference between spongy/bouncy castle. private static final org.spongycastle.jce.provider.BouncyCastleProvider spongyCastleProvider; - private static Messenger localRepoServiceMessenger = null; - private static boolean localRepoServiceIsBound = false; + @SuppressWarnings("unused") private static final String TAG = "FDroidApp"; BluetoothAdapter bluetoothAdapter = null; @@ -301,53 +300,4 @@ public class FDroidApp extends Application { } } } - - private static final ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - localRepoServiceMessenger = new Messenger(service); - } - - @Override - public void onServiceDisconnected(ComponentName className) { - localRepoServiceMessenger = null; - } - }; - - public static void startLocalRepoService(Context context) { - if (!localRepoServiceIsBound) { - Context app = context.getApplicationContext(); - Intent service = new Intent(app, LocalRepoService.class); - localRepoServiceIsBound = app.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE); - if (localRepoServiceIsBound) - app.startService(service); - } - } - - public static void stopLocalRepoService(Context context) { - Context app = context.getApplicationContext(); - if (localRepoServiceIsBound) { - app.unbindService(serviceConnection); - localRepoServiceIsBound = false; - } - app.stopService(new Intent(app, LocalRepoService.class)); - } - - /** - * Handles checking if the {@link LocalRepoService} is running, and only restarts it if it was running. - */ - public static void restartLocalRepoServiceIfRunning() { - if (localRepoServiceMessenger != null) { - try { - Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0); - localRepoServiceMessenger.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } - } - } - - public static boolean isLocalRepoServiceRunning() { - return localRepoServiceIsBound; - } } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java index 802de5cd9..ad458c57d 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java @@ -1,7 +1,14 @@ package org.fdroid.fdroid.localrepo; +import android.content.ComponentName; import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.os.IBinder; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; import android.support.annotation.IntDef; import android.support.annotation.NonNull; @@ -31,7 +38,7 @@ public class SwapState { private int step; private SwapState(@NonNull Context context, @SwapStep int step, @NonNull Set appsToSwap) { - this.context = context; + this.context = context.getApplicationContext(); this.step = step; this.appsToSwap = appsToSwap; } @@ -145,4 +152,59 @@ public class SwapState { @IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR}) @Retention(RetentionPolicy.SOURCE) public @interface SwapStep {} + + + // ========================================== + // Local repo stop/start/restart handling + // ========================================== + + private Messenger localRepoServiceMessenger = null; + private boolean localRepoServiceIsBound = false; + + private final ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + localRepoServiceMessenger = new Messenger(service); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + localRepoServiceMessenger = null; + } + }; + + public void startLocalRepoService() { + if (!localRepoServiceIsBound) { + Intent service = new Intent(context, LocalRepoService.class); + localRepoServiceIsBound = context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE); + if (localRepoServiceIsBound) + context.startService(service); + } + } + + public void stopLocalRepoService() { + if (localRepoServiceIsBound) { + context.unbindService(serviceConnection); + localRepoServiceIsBound = false; + } + context.stopService(new Intent(context, LocalRepoService.class)); + } + + /** + * Handles checking if the {@link LocalRepoService} is running, and only restarts it if it was running. + */ + public void restartLocalRepoServiceIfRunning() { + if (localRepoServiceMessenger != null) { + try { + Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0); + localRepoServiceMessenger.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + + public boolean isLocalRepoServiceRunning() { + return localRepoServiceIsBound; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java index bb90e1af4..9c2330a04 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -17,6 +17,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.SwapState; import java.net.Inet6Address; import java.net.InetAddress; @@ -151,7 +152,7 @@ public class WifiStateChangeService extends Service { Intent intent = new Intent(BROADCAST); LocalBroadcastManager.getInstance(WifiStateChangeService.this).sendBroadcast(intent); WifiStateChangeService.this.stopSelf(); - FDroidApp.restartLocalRepoServiceIfRunning(); + SwapState.load(WifiStateChangeService.this).restartLocalRepoServiceIfRunning(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java index 3eebca77c..6bb1f7efd 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java @@ -27,6 +27,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; +import org.fdroid.fdroid.localrepo.SwapState; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -154,7 +155,7 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi // Only ask server to swap with us, if we are actually running a local repo service. // It is possible to have a swap initiated without first starting a swap, in which // case swapping back is pointless. - if (!newRepoConfig.preventFurtherSwaps() && FDroidApp.isLocalRepoServiceRunning()) { + if (!newRepoConfig.preventFurtherSwaps() && SwapState.load(this).isLocalRepoServiceRunning()) { askServerToSwapWithUs(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java index c27f0097b..d1fe5d77c 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java @@ -19,6 +19,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.LocalRepoManager; +import org.fdroid.fdroid.localrepo.SwapState; import java.util.Set; import java.util.Timer; @@ -100,7 +101,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage showFragment(new StartSwapFragment(), STATE_START_SWAP); - if (FDroidApp.isLocalRepoServiceRunning()) { + if (getState().isLocalRepoServiceRunning()) { showSelectApps(); showJoinWifi(); attemptToShowNfc(); @@ -181,12 +182,16 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage } private void ensureLocalRepoRunning() { - if (!FDroidApp.isLocalRepoServiceRunning()) { - FDroidApp.startLocalRepoService(this); + if (!getState().isLocalRepoServiceRunning()) { + getState().startLocalRepoService(); initLocalRepoTimer(900000); // 15 mins } } + private SwapState getState() { + return SwapState.load(this); + } + private void initLocalRepoTimer(long timeoutMilliseconds) { // reset the timer if viewing this Activity again @@ -198,7 +203,7 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage shutdownLocalRepoTimer.schedule(new TimerTask() { @Override public void run() { - FDroidApp.stopLocalRepoService(SwapActivity.this); + getState().stopLocalRepoService(); } }, timeoutMilliseconds); @@ -206,11 +211,11 @@ public class SwapActivity extends ActionBarActivity implements SwapProcessManage @Override public void stopSwapping() { - if (FDroidApp.isLocalRepoServiceRunning()) { + if (getState().isLocalRepoServiceRunning()) { if (shutdownLocalRepoTimer != null) { shutdownLocalRepoTimer.cancel(); } - FDroidApp.stopLocalRepoService(SwapActivity.this); + getState().stopLocalRepoService(); } finish(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 1dab7b974..c954a1c03 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -196,8 +196,8 @@ public class SwapWorkflowActivity extends FragmentActivity { } private void ensureLocalRepoRunning() { - if (!FDroidApp.isLocalRepoServiceRunning()) { - FDroidApp.startLocalRepoService(this); + if (!getState().isLocalRepoServiceRunning()) { + getState().startLocalRepoService(); initLocalRepoTimer(900000); // 15 mins } } @@ -213,18 +213,18 @@ public class SwapWorkflowActivity extends FragmentActivity { shutdownLocalRepoTimer.schedule(new TimerTask() { @Override public void run() { - FDroidApp.stopLocalRepoService(SwapWorkflowActivity.this); + getState().stopLocalRepoService(); } }, timeoutMilliseconds); } public void stopSwapping() { - if (FDroidApp.isLocalRepoServiceRunning()) { + if (getState().isLocalRepoServiceRunning()) { if (shutdownLocalRepoTimer != null) { shutdownLocalRepoTimer.cancel(); } - FDroidApp.stopLocalRepoService(SwapWorkflowActivity.this); + getState().stopLocalRepoService(); } finish(); } From c26c6b9e8980f007c5adeb080c973cb8f3fafae9 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 25 May 2015 16:02:41 +1000 Subject: [PATCH 25/78] Removed old SwapActivity + Fragments. All the code from the activity and the fragments has been successfully ported to the SwapWorkflowActivity + Views. Thus, the code is no longer useful, as it was only kept over the previous WIP commits so that it can be referred to to help re-implement fragments with views. --- F-Droid/AndroidManifest.xml | 11 - F-Droid/res/layout/swap_blank.xml | 4 +- F-Droid/res/layout/swap_join_wifi.xml | 4 +- F-Droid/res/layout/swap_nfc.xml | 4 +- F-Droid/res/layout/swap_select_apps.xml | 4 +- F-Droid/res/layout/swap_wifi_qr.xml | 4 +- F-Droid/src/org/fdroid/fdroid/FDroid.java | 1 - F-Droid/src/org/fdroid/fdroid/FDroidApp.java | 1 - .../fdroid/localrepo/LocalRepoService.java | 4 +- .../fdroid/views/swap/JoinWifiFragment.java | 109 ------ .../views/swap/{views => }/JoinWifiView.java | 2 +- .../fdroid/views/swap/NfcSwapFragment.java | 47 --- .../views/swap/{views => }/NfcView.java | 2 +- .../fdroid/views/swap/SelectAppsFragment.java | 319 ------------------ .../swap/{views => }/SelectAppsView.java | 3 +- .../fdroid/views/swap/StartSwapFragment.java | 22 -- .../views/swap/{views => }/StartSwapView.java | 2 +- .../fdroid/views/swap/SwapActivity.java | 295 ---------------- .../fdroid/views/swap/SwapProcessManager.java | 12 - .../fdroid/views/swap/WifiQrFragment.java | 164 --------- .../views/swap/{views => }/WifiQrView.java | 2 +- 21 files changed, 17 insertions(+), 999 deletions(-) delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java rename F-Droid/src/org/fdroid/fdroid/views/swap/{views => }/JoinWifiView.java (99%) delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/NfcSwapFragment.java rename F-Droid/src/org/fdroid/fdroid/views/swap/{views => }/NfcView.java (98%) delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsFragment.java rename F-Droid/src/org/fdroid/fdroid/views/swap/{views => }/SelectAppsView.java (99%) delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapFragment.java rename F-Droid/src/org/fdroid/fdroid/views/swap/{views => }/StartSwapView.java (98%) delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java delete mode 100644 F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java rename F-Droid/src/org/fdroid/fdroid/views/swap/{views => }/WifiQrView.java (99%) diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml index be3883ec7..9bcc88614 100644 --- a/F-Droid/AndroidManifest.xml +++ b/F-Droid/AndroidManifest.xml @@ -385,17 +385,6 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".FDroid" /> - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/F-Droid/res/layout/swap_join_wifi.xml b/F-Droid/res/layout/swap_join_wifi.xml index bf24e6ff4..a07230bb1 100644 --- a/F-Droid/res/layout/swap_join_wifi.xml +++ b/F-Droid/res/layout/swap_join_wifi.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/F-Droid/res/layout/swap_nfc.xml b/F-Droid/res/layout/swap_nfc.xml index 33deea105..572edfac0 100644 --- a/F-Droid/res/layout/swap_nfc.xml +++ b/F-Droid/res/layout/swap_nfc.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/F-Droid/res/layout/swap_select_apps.xml b/F-Droid/res/layout/swap_select_apps.xml index d87aeb313..52489d994 100644 --- a/F-Droid/res/layout/swap_select_apps.xml +++ b/F-Droid/res/layout/swap_select_apps.xml @@ -1,9 +1,9 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/F-Droid/res/layout/swap_wifi_qr.xml b/F-Droid/res/layout/swap_wifi_qr.xml index ae6a11d76..da0ba3a3b 100644 --- a/F-Droid/res/layout/swap_wifi_qr.xml +++ b/F-Droid/res/layout/swap_wifi_qr.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/F-Droid/src/org/fdroid/fdroid/FDroid.java b/F-Droid/src/org/fdroid/fdroid/FDroid.java index a04e3d4ba..8cbddc22e 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroid.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroid.java @@ -50,7 +50,6 @@ import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.views.AppListFragmentPagerAdapter; import org.fdroid.fdroid.views.ManageReposActivity; import org.fdroid.fdroid.views.swap.ConnectSwapActivity; -import org.fdroid.fdroid.views.swap.SwapActivity; import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class FDroid extends ActionBarActivity { diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java index 7c0036545..6cb5a63ec 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java @@ -69,7 +69,6 @@ public class FDroidApp extends Application { public static String ssid; public static String bssid; public static final Repo repo = new Repo(); - public static Set selectedApps = null; // init in SelectLocalAppsFragment // Leaving the fully qualified class name here to help clarify the difference between spongy/bouncy castle. private static final org.spongycastle.jce.provider.BouncyCastleProvider spongyCastleProvider; diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java index 8e835e2b6..223acf42a 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java @@ -26,7 +26,7 @@ import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.net.LocalHTTPD; import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapActivity; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; import java.io.IOException; import java.net.BindException; @@ -136,7 +136,7 @@ public class LocalRepoService extends Service { private void showNotification() { notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); // launch LocalRepoActivity if the user selects this notification - Intent intent = new Intent(this, SwapActivity.class); + Intent intent = new Intent(this, SwapWorkflowActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); notification = new NotificationCompat.Builder(this) diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java deleted file mode 100644 index 9723945b9..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiFragment.java +++ /dev/null @@ -1,109 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.net.wifi.WifiManager; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.content.LocalBroadcastManager; -import android.support.v4.view.MenuItemCompat; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.net.WifiStateChangeService; - -public class JoinWifiFragment extends Fragment { - - private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - refreshWifiState(); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { - menuInflater.inflate(R.menu.swap_next, menu); - MenuItem nextMenuItem = menu.findItem(R.id.action_next); - int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT; - MenuItemCompat.setShowAsAction(nextMenuItem, flags); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View joinWifiView = inflater.inflate(R.layout.swap_join_wifi, container, false); - joinWifiView.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - openAvailableNetworks(); - } - }); - return joinWifiView; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - // TODO: Listen for "Connecting..." state and reflect that in the view too. - LocalBroadcastManager.getInstance(activity).registerReceiver( - onWifiChange, - new IntentFilter(WifiStateChangeService.BROADCAST)); - } - - @Override - public void onResume() { - super.onResume(); - refreshWifiState(); - } - - private void refreshWifiState() { - View view = getView(); - if (view != null) { - TextView descriptionView = (TextView) view.findViewById(R.id.text_description); - ImageView wifiIcon = (ImageView) view.findViewById(R.id.wifi_icon); - TextView ssidView = (TextView) view.findViewById(R.id.wifi_ssid); - TextView tapView = (TextView) view.findViewById(R.id.wifi_available_networks_prompt); - if (TextUtils.isEmpty(FDroidApp.bssid) && !TextUtils.isEmpty(FDroidApp.ipAddressString)) { - // empty bssid with an ipAddress means hotspot mode - descriptionView.setText(R.string.swap_join_this_hotspot); - wifiIcon.setImageDrawable(getResources().getDrawable(R.drawable.hotspot)); - ssidView.setText(R.string.swap_active_hotspot); - tapView.setText(R.string.swap_switch_to_wifi); - } else if (TextUtils.isEmpty(FDroidApp.ssid)) { - // not connected to or setup with any wifi network - descriptionView.setText(R.string.swap_join_same_wifi); - wifiIcon.setImageDrawable(getResources().getDrawable(R.drawable.wifi)); - ssidView.setText(R.string.swap_no_wifi_network); - tapView.setText(R.string.swap_view_available_networks); - } else { - // connected to a regular wifi network - descriptionView.setText(R.string.swap_join_same_wifi); - wifiIcon.setImageDrawable(getResources().getDrawable(R.drawable.wifi)); - ssidView.setText(FDroidApp.ssid); - tapView.setText(R.string.swap_view_available_networks); - } - } - } - - private void openAvailableNetworks() { - startActivity(new Intent(WifiManager.ACTION_PICK_WIFI_NETWORK)); - } -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java similarity index 99% rename from F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java rename to F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java index 44a16c4e4..99baf6706 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/JoinWifiView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java @@ -1,4 +1,4 @@ -package org.fdroid.fdroid.views.swap.views; +package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; import android.content.BroadcastReceiver; diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcSwapFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcSwapFragment.java deleted file mode 100644 index 6cf5b23e6..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcSwapFragment.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.view.MenuItemCompat; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.CompoundButton; - -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.R; - -public class NfcSwapFragment extends Fragment { - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { - menuInflater.inflate(R.menu.swap_skip, menu); - MenuItem nextMenuItem = menu.findItem(R.id.action_next); - int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT; - MenuItemCompat.setShowAsAction(nextMenuItem, flags); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.swap_nfc, container, false); - CheckBox dontShowAgain = (CheckBox)view.findViewById(R.id.checkbox_dont_show); - dontShowAgain.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Preferences.get().setShowNfcDuringSwap(!isChecked); - } - }); - return view; - } - -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java similarity index 98% rename from F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java rename to F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java index 482e23f8d..142218aca 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/NfcView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java @@ -1,4 +1,4 @@ -package org.fdroid.fdroid.views.swap.views; +package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; import android.content.Context; diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsFragment.java deleted file mode 100644 index 0fa746256..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsFragment.java +++ /dev/null @@ -1,319 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Bundle; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.app.LoaderManager; -import android.support.v4.content.CursorLoader; -import android.support.v4.content.Loader; -import android.support.v4.view.MenuItemCompat; -import android.support.v4.widget.CursorAdapter; -import android.support.v7.widget.SearchView; -import android.text.TextUtils; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.ImageView; -import android.widget.ListView; -import android.widget.TextView; - -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.data.InstalledAppProvider; -import org.fdroid.fdroid.localrepo.LocalRepoManager; -import org.fdroid.fdroid.views.fragments.ThemeableListFragment; - -import java.util.HashSet; -import java.util.Set; - -public class SelectAppsFragment extends ThemeableListFragment - implements LoaderManager.LoaderCallbacks, SearchView.OnQueryTextListener { - - @SuppressWarnings("UnusedDeclaration") - private static final String TAG = "SwapAppsList"; - - private String mCurrentFilterString; - - @NonNull - private final Set previouslySelectedApps = new HashSet<>(); - - public Set getSelectedApps() { - return FDroidApp.selectedApps; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - } - - @Override - public void onCreateOptionsMenu(Menu menu, MenuInflater menuInflater) { - menuInflater.inflate(R.menu.swap_next_search, menu); - MenuItem nextMenuItem = menu.findItem(R.id.action_next); - int flags = MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT; - MenuItemCompat.setShowAsAction(nextMenuItem, flags); - - SearchView searchView = new SearchView(getActivity()); - - MenuItem searchMenuItem = menu.findItem(R.id.action_search); - MenuItemCompat.setActionView(searchMenuItem, searchView); - MenuItemCompat.setShowAsAction(searchMenuItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM); - - searchView.setOnQueryTextListener(this); - } - - @Override - public void onResume() { - super.onResume(); - previouslySelectedApps.clear(); - if (FDroidApp.selectedApps != null) { - previouslySelectedApps.addAll(FDroidApp.selectedApps); - } - } - - public boolean hasSelectionChanged() { - - Set currentlySelected = getSelectedApps(); - if (currentlySelected.size() != previouslySelectedApps.size()) { - return true; - } - - for (String current : currentlySelected) { - boolean found = false; - for (String previous : previouslySelectedApps) { - if (current.equals(previous)) { - found = true; - break; - } - } - if (!found) { - return true; - } - } - - return false; - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - - setEmptyText(getString(R.string.no_applications_found)); - - ListView listView = getListView(); - listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); - - setListAdapter(new AppListAdapter(listView, getActivity(), null)); - setListShown(false); // start out with a progress indicator - - // either reconnect with an existing loader or start a new one - getLoaderManager().initLoader(0, null, this); - - // build list of existing apps from what is on the file system - if (FDroidApp.selectedApps == null) { - FDroidApp.selectedApps = new HashSet<>(); - for (String filename : LocalRepoManager.get(getActivity()).repoDir.list()) { - if (filename.matches(".*\\.apk")) { - String packageName = filename.substring(0, filename.indexOf("_")); - FDroidApp.selectedApps.add(packageName); - } - } - } - } - - @Override - public void onListItemClick(ListView l, View v, int position, long id) { - // Ignore the headerView at position 0. - if (position > 0) { - toggleAppSelected(position); - } - } - - private void toggleAppSelected(int position) { - Cursor c = (Cursor) getListAdapter().getItem(position - 1); - String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); - if (FDroidApp.selectedApps.contains(packageName)) { - FDroidApp.selectedApps.remove(packageName); - } else { - FDroidApp.selectedApps.add(packageName); - } - } - - @Override - public CursorLoader onCreateLoader(int id, Bundle args) { - Uri baseUri; - if (TextUtils.isEmpty(mCurrentFilterString)) { - baseUri = InstalledAppProvider.getContentUri(); - } else { - baseUri = InstalledAppProvider.getSearchUri(mCurrentFilterString); - } - return new CursorLoader( - this.getActivity(), - baseUri, - InstalledAppProvider.DataColumns.ALL, - null, - null, - InstalledAppProvider.DataColumns.APPLICATION_LABEL); - } - - @Override - public void onLoadFinished(Loader loader, Cursor cursor) { - ((AppListAdapter)getListAdapter()).swapCursor(cursor); - - ListView listView = getListView(); - String fdroid = loader.getContext().getPackageName(); - for (int i = 0; i < listView.getCount() - 1; i++) { - Cursor c = ((Cursor) listView.getItemAtPosition(i + 1)); - String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); - if (TextUtils.equals(packageName, fdroid)) { - listView.setItemChecked(i + 1, true); // always include FDroid - } else { - for (String selected : FDroidApp.selectedApps) { - if (TextUtils.equals(packageName, selected)) { - listView.setItemChecked(i + 1, true); - } - } - } - } - - if (isResumed()) { - setListShown(true); - } else { - setListShownNoAnimation(true); - } - } - - @Override - public void onLoaderReset(Loader loader) { - ((AppListAdapter)getListAdapter()).swapCursor(null); - } - - @Override - public boolean onQueryTextChange(String newText) { - String newFilter = !TextUtils.isEmpty(newText) ? newText : null; - if (mCurrentFilterString == null && newFilter == null) { - return true; - } - if (mCurrentFilterString != null && mCurrentFilterString.equals(newFilter)) { - return true; - } - mCurrentFilterString = newFilter; - getLoaderManager().restartLoader(0, null, this); - return true; - } - - @Override - public boolean onQueryTextSubmit(String query) { - // this is not needed since we respond to every change in text - return true; - } - - @Override - protected int getThemeStyle() { - return R.style.SwapTheme_StartSwap; - } - - @Override - protected int getHeaderLayout() { - return R.layout.swap_create_header; - } - - private class AppListAdapter extends CursorAdapter { - - @SuppressWarnings("UnusedDeclaration") - private static final String TAG = "AppListAdapter"; - - @Nullable - private LayoutInflater inflater; - - @Nullable - private Drawable defaultAppIcon; - - @NonNull - private final ListView listView; - - public AppListAdapter(@NonNull ListView listView, @NonNull Context context, @Nullable Cursor c) { - super(context, c, FLAG_REGISTER_CONTENT_OBSERVER); - this.listView = listView; - } - - @NonNull - private LayoutInflater getInflater(Context context) { - if (inflater == null) { - Context themedContext = new ContextThemeWrapper(context, R.style.SwapTheme_AppList_ListItem); - inflater = (LayoutInflater)themedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - } - return inflater; - } - - @NonNull - private Drawable getDefaultAppIcon(Context context) { - if (defaultAppIcon == null) { - defaultAppIcon = context.getResources().getDrawable(android.R.drawable.sym_def_app_icon); - } - return defaultAppIcon; - } - - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - View view = getInflater(context).inflate(R.layout.select_local_apps_list_item, parent, false); - bindView(view, context, cursor); - return view; - } - - @Override - public void bindView(final View view, final Context context, final Cursor cursor) { - - TextView packageView = (TextView)view.findViewById(R.id.package_name); - TextView labelView = (TextView)view.findViewById(R.id.application_label); - ImageView iconView = (ImageView)view.findViewById(android.R.id.icon); - - String packageName = cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); - String appLabel = cursor.getString(cursor.getColumnIndex(InstalledAppProvider.DataColumns.APPLICATION_LABEL)); - - Drawable icon; - try { - icon = context.getPackageManager().getApplicationIcon(packageName); - } catch (PackageManager.NameNotFoundException e) { - icon = getDefaultAppIcon(context); - } - - packageView.setText(packageName); - labelView.setText(appLabel); - iconView.setImageDrawable(icon); - - // Since v11, the Android SDK provided the ability to show selected list items - // by highlighting their background. Prior to this, we need to handle this ourselves - // by adding a checkbox which can toggle selected items. - View checkBoxView = view.findViewById(R.id.checkbox); - if (checkBoxView != null) { - CheckBox checkBox = (CheckBox)checkBoxView; - checkBox.setOnCheckedChangeListener(null); - - final int listPosition = cursor.getPosition() + 1; // To account for the header view. - - checkBox.setChecked(listView.isItemChecked(listPosition)); - checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - listView.setItemChecked(listPosition, isChecked); - toggleAppSelected(listPosition); - } - }); - } - } - } - -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java similarity index 99% rename from F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java rename to F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java index b07347fc7..fa8864d20 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java @@ -1,4 +1,4 @@ -package org.fdroid.fdroid.views.swap.views; +package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; import android.content.Context; @@ -35,7 +35,6 @@ import android.widget.TextView; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.InstalledAppProvider; import org.fdroid.fdroid.localrepo.SwapState; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class SelectAppsView extends ListView implements SwapWorkflowActivity.InnerView, diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapFragment.java deleted file mode 100644 index 00d090442..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapFragment.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.app.Activity; -import android.content.Context; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.view.ContextThemeWrapper; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import org.fdroid.fdroid.R; - -public class StartSwapFragment extends Fragment { - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - LayoutInflater themedInflater = (LayoutInflater)new ContextThemeWrapper(inflater.getContext(), R.style.SwapTheme_StartSwap).getSystemService(Context.LAYOUT_INFLATER_SERVICE); - return themedInflater.inflate(R.layout.swap_blank, container, false); - } - -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java similarity index 98% rename from F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java rename to F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java index c251dd0bf..3f71317bf 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -1,4 +1,4 @@ -package org.fdroid.fdroid.views.swap.views; +package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; import android.content.Context; diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java deleted file mode 100644 index d1fe5d77c..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapActivity.java +++ /dev/null @@ -1,295 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.app.ProgressDialog; -import android.content.Context; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.NonNull; -import android.support.v4.app.Fragment; -import android.support.v4.app.FragmentManager; -import android.support.v7.app.ActionBarActivity; -import android.view.MenuItem; -import android.widget.Toast; - -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.NfcHelper; -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.localrepo.LocalRepoManager; -import org.fdroid.fdroid.localrepo.SwapState; - -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; - -public class SwapActivity extends ActionBarActivity implements SwapProcessManager { - - private static final String STATE_START_SWAP = "startSwap"; - private static final String STATE_SELECT_APPS = "selectApps"; - private static final String STATE_JOIN_WIFI = "joinWifi"; - private static final String STATE_NFC = "nfc"; - private static final String STATE_WIFI_QR = "wifiQr"; - - private Timer shutdownLocalRepoTimer; - private UpdateAsyncTask updateSwappableAppsTask = null; - private boolean hasPreparedLocalRepo = false; - - @Override - public void onBackPressed() { - switch (currentState()) { - case STATE_START_SWAP: - finish(); - break; - default: - super.onBackPressed(); - break; - } - } - - private String currentState() { - FragmentManager.BackStackEntry lastFragment = getSupportFragmentManager().getBackStackEntryAt(getSupportFragmentManager().getBackStackEntryCount() - 1); - return lastFragment.getName(); - } - - public void nextStep() { - switch (currentState()) { - case STATE_START_SWAP: - showSelectApps(); - break; - case STATE_SELECT_APPS: - prepareLocalRepo(); - break; - case STATE_JOIN_WIFI: - ensureLocalRepoRunning(); - if (!attemptToShowNfc()) { - showWifiQr(); - } - break; - case STATE_NFC: - showWifiQr(); - break; - case STATE_WIFI_QR: - break; - } - supportInvalidateOptionsMenu(); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_next) { - nextStep(); - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onCreate(Bundle savedInstanceState) { - - super.onCreate(savedInstanceState); - - if (savedInstanceState == null) { - - setContentView(R.layout.swap_activity); - - // Necessary to run on an Android 2.3.[something] device. - new Handler().post(new Runnable() { - @Override - public void run() { - - showFragment(new StartSwapFragment(), STATE_START_SWAP); - - if (getState().isLocalRepoServiceRunning()) { - showSelectApps(); - showJoinWifi(); - attemptToShowNfc(); - showWifiQr(); - } - - } - }); - } - - } - - public void showSelectApps() { - - showFragment(new SelectAppsFragment(), STATE_SELECT_APPS); - - } - - private void showJoinWifi() { - - showFragment(new JoinWifiFragment(), STATE_JOIN_WIFI); - - } - - private boolean attemptToShowNfc() { - // TODO: What if NFC is disabled? Hook up with NfcNotEnabledActivity? Or maybe only if they - // click a relevant button? - - // Even if they opted to skip the message which says "Touch devices to swap", - // we still want to actually enable the feature, so that they could touch - // during the wifi qr code being shown too. - boolean nfcMessageReady = NfcHelper.setPushMessage(this, Utils.getSharingUri(FDroidApp.repo)); - - if (Preferences.get().showNfcDuringSwap() && nfcMessageReady) { - showFragment(new NfcSwapFragment(), STATE_NFC); - return true; - } - return false; - } - - private void showBluetooth() { - - } - - private void showWifiQr() { - showFragment(new WifiQrFragment(), STATE_WIFI_QR); - } - - private void showFragment(Fragment fragment, String name) { - getSupportFragmentManager() - .beginTransaction() - .replace(R.id.fragment_container, fragment, name) - .addToBackStack(name) - .commit(); - } - - private void prepareLocalRepo() { - SelectAppsFragment fragment = (SelectAppsFragment)getSupportFragmentManager().findFragmentByTag(STATE_SELECT_APPS); - boolean needsUpdating = !hasPreparedLocalRepo || fragment.hasSelectionChanged(); - if (updateSwappableAppsTask == null && needsUpdating) { - updateSwappableAppsTask = new UpdateAsyncTask(this, fragment.getSelectedApps()); - updateSwappableAppsTask.execute(); - } else { - showJoinWifi(); - } - } - - /** - * Once the UpdateAsyncTask has finished preparing our repository index, we can - * show the next screen to the user. - */ - private void onLocalRepoPrepared() { - - updateSwappableAppsTask = null; - hasPreparedLocalRepo = true; - showJoinWifi(); - - } - - private void ensureLocalRepoRunning() { - if (!getState().isLocalRepoServiceRunning()) { - getState().startLocalRepoService(); - initLocalRepoTimer(900000); // 15 mins - } - } - - private SwapState getState() { - return SwapState.load(this); - } - - private void initLocalRepoTimer(long timeoutMilliseconds) { - - // reset the timer if viewing this Activity again - if (shutdownLocalRepoTimer != null) - shutdownLocalRepoTimer.cancel(); - - // automatically turn off after 15 minutes - shutdownLocalRepoTimer = new Timer(); - shutdownLocalRepoTimer.schedule(new TimerTask() { - @Override - public void run() { - getState().stopLocalRepoService(); - } - }, timeoutMilliseconds); - - } - - @Override - public void stopSwapping() { - if (getState().isLocalRepoServiceRunning()) { - if (shutdownLocalRepoTimer != null) { - shutdownLocalRepoTimer.cancel(); - } - getState().stopLocalRepoService(); - } - finish(); - } - - class UpdateAsyncTask extends AsyncTask { - - @SuppressWarnings("UnusedDeclaration") - private static final String TAG = "SwapActivity.UpdateAsyncTask"; - - @NonNull - private final ProgressDialog progressDialog; - - @NonNull - private final Set selectedApps; - - @NonNull - private final Uri sharingUri; - - public UpdateAsyncTask(Context c, @NonNull Set apps) { - selectedApps = apps; - progressDialog = new ProgressDialog(c); - progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); - progressDialog.setTitle(R.string.updating); - sharingUri = Utils.getSharingUri(FDroidApp.repo); - } - - @Override - protected void onPreExecute() { - progressDialog.show(); - } - - @Override - protected Void doInBackground(Void... params) { - try { - final LocalRepoManager lrm = LocalRepoManager.get(SwapActivity.this); - publishProgress(getString(R.string.deleting_repo)); - lrm.deleteRepo(); - for (String app : selectedApps) { - publishProgress(String.format(getString(R.string.adding_apks_format), app)); - lrm.addApp(SwapActivity.this, app); - } - lrm.writeIndexPage(sharingUri.toString()); - publishProgress(getString(R.string.writing_index_jar)); - lrm.writeIndexJar(); - publishProgress(getString(R.string.linking_apks)); - lrm.copyApksToRepo(); - publishProgress(getString(R.string.copying_icons)); - // run the icon copy without progress, its not a blocker - new AsyncTask() { - - @Override - protected Void doInBackground(Void... params) { - lrm.copyIconsToRepo(); - return null; - } - }.execute(); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void onProgressUpdate(String... progress) { - super.onProgressUpdate(progress); - progressDialog.setMessage(progress[0]); - } - - @Override - protected void onPostExecute(Void result) { - progressDialog.dismiss(); - Toast.makeText(SwapActivity.this, R.string.updated_local_repo, Toast.LENGTH_SHORT).show(); - onLocalRepoPrepared(); - } - } - -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java deleted file mode 100644 index cb9567bf9..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapProcessManager.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -/** - * Defines the contract between the {@link org.fdroid.fdroid.views.swap.SwapActivity} - * and the fragments which live in it. The fragments each have the responsibility of - * moving to the next stage of the process, and are entitled to stop swapping too - * (e.g. when a "Cancel" button is pressed). - */ -public interface SwapProcessManager { - void nextStep(); - void stopSwapping(); -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java deleted file mode 100644 index ecb7d70ca..000000000 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrFragment.java +++ /dev/null @@ -1,164 +0,0 @@ -package org.fdroid.fdroid.views.swap; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.graphics.LightingColorFilter; -import android.net.Uri; -import android.os.Bundle; -import android.support.v4.app.Fragment; -import android.support.v4.content.LocalBroadcastManager; -import android.text.TextUtils; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.TextView; -import android.widget.Toast; - -import com.google.zxing.integration.android.IntentIntegrator; -import com.google.zxing.integration.android.IntentResult; - -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URLEncodedUtils; -import org.fdroid.fdroid.FDroid; -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.QrGenAsyncTask; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.data.NewRepoConfig; -import org.fdroid.fdroid.net.WifiStateChangeService; - -import java.net.URI; -import java.util.List; -import java.util.Locale; - -public class WifiQrFragment extends Fragment { - - private static final int CONNECT_TO_SWAP = 1; - - private static final String TAG = "WifiQrFragment"; - - private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent i) { - setUIFromWifi(); - } - }; - - private SwapProcessManager swapManager; - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View view = inflater.inflate(R.layout.swap_wifi_qr, container, false); - ImageView qrImage = (ImageView)view.findViewById(R.id.wifi_qr_code); - - // Replace all blacks with the background blue. - qrImage.setColorFilter(new LightingColorFilter(0xffffffff, getResources().getColor(R.color.swap_blue))); - - Button openQr = (Button)view.findViewById(R.id.btn_qr_scanner); - openQr.setOnClickListener(new Button.OnClickListener() { - @Override - public void onClick(View v) { - IntentIntegrator integrator = new IntentIntegrator(WifiQrFragment.this); - integrator.initiateScan(); - } - }); - - Button cancel = (Button)view.findViewById(R.id.btn_cancel_swap); - cancel.setOnClickListener(new Button.OnClickListener() { - @Override - public void onClick(View v) { - swapManager.stopSwapping(); - } - }); - return view; - } - - @Override - public void onAttach(Activity activity) { - super.onAttach(activity); - swapManager = (SwapProcessManager)activity; - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent intent) { - IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent); - if (scanResult != null) { - if (scanResult.getContents() != null) { - NewRepoConfig repoConfig = new NewRepoConfig(getActivity(), scanResult.getContents()); - if (repoConfig.isValidRepo()) { - startActivityForResult(new Intent(FDroid.ACTION_ADD_REPO, Uri.parse(scanResult.getContents()), getActivity(), ConnectSwapActivity.class), CONNECT_TO_SWAP); - } else { - Toast.makeText(getActivity(), "The QR code you scanned doesn't look like a swap code.", Toast.LENGTH_SHORT).show(); - } - } - } else if (requestCode == CONNECT_TO_SWAP && resultCode == Activity.RESULT_OK) { - getActivity().finish(); - } - } - - public void onResume() { - super.onResume(); - setUIFromWifi(); - - LocalBroadcastManager.getInstance(getActivity()).registerReceiver(onWifiChange, - new IntentFilter(WifiStateChangeService.BROADCAST)); - } - - private void setUIFromWifi() { - - if (TextUtils.isEmpty(FDroidApp.repo.address) || getView() == null) - return; - - String scheme = Preferences.get().isLocalRepoHttpsEnabled() ? "https://" : "http://"; - - // the fingerprint is not useful on the button label - String buttonLabel = scheme + FDroidApp.ipAddressString + ":" + FDroidApp.port; - TextView ipAddressView = (TextView) getView().findViewById(R.id.device_ip_address); - ipAddressView.setText(buttonLabel); - - /* - * Set URL to UPPER for compact QR Code, FDroid will translate it back. - * Remove the SSID from the query string since SSIDs are case-sensitive. - * Instead the receiver will have to rely on the BSSID to find the right - * wifi AP to join. Lots of QR Scanners are buggy and do not respect - * custom URI schemes, so we have to use http:// or https:// :-( - */ - Uri sharingUri = Utils.getSharingUri(FDroidApp.repo); - String qrUriString = (scheme + sharingUri.getHost()).toUpperCase(Locale.ENGLISH); - if (sharingUri.getPort() != 80) { - qrUriString += ":" + sharingUri.getPort(); - } - qrUriString += sharingUri.getPath().toUpperCase(Locale.ENGLISH); - boolean first = true; - - // Andorid provides an API for getting the query parameters and iterating over them: - // Uri.getQueryParameterNames() - // But it is only available on later Android versions. As such we use URLEncodedUtils instead. - List parameters = URLEncodedUtils.parse(URI.create(sharingUri.toString()), "UTF-8"); - for (NameValuePair parameter : parameters) { - if (!parameter.getName().equals("ssid")) { - if (first) { - qrUriString += "?"; - first = false; - } else { - qrUriString += "&"; - } - qrUriString += parameter.getName().toUpperCase(Locale.ENGLISH) + "=" + - parameter.getValue().toUpperCase(Locale.ENGLISH); - } - } - - Log.i(TAG, "Encoded swap URI in QR Code: " + qrUriString); - - new QrGenAsyncTask(getActivity(), R.id.wifi_qr_code).execute(qrUriString); - - } - -} diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java similarity index 99% rename from F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java rename to F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java index fc451de9b..8b215d505 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/views/WifiQrView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java @@ -1,4 +1,4 @@ -package org.fdroid.fdroid.views.swap.views; +package org.fdroid.fdroid.views.swap; import android.annotation.TargetApi; import android.content.BroadcastReceiver; From ea61e8f2d3870ec3d5cc5ced2fe797be44893c85 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Mon, 25 May 2015 16:20:56 +1000 Subject: [PATCH 26/78] Organised SwapState class, moved methods around to make more sense. Put all of the step handling code in one place, added some comments to make sense of the different parts of the claass and their responsibilities. --- .../fdroid/fdroid/localrepo/SwapState.java | 115 ++++++++++-------- 1 file changed, 65 insertions(+), 50 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java index ad458c57d..635676c8b 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java @@ -22,46 +22,6 @@ public class SwapState { private static final String SHARED_PREFERENCES = "swap-state"; - public static final int STEP_INTRO = 1; - public static final int STEP_SELECT_APPS = 2; - public static final int STEP_JOIN_WIFI = 3; - public static final int STEP_SHOW_NFC = 4; - public static final int STEP_WIFI_QR = 5; - - - @NonNull - private final Context context; - - @NonNull - private Set appsToSwap; - - private int step; - - private SwapState(@NonNull Context context, @SwapStep int step, @NonNull Set appsToSwap) { - this.context = context.getApplicationContext(); - this.step = step; - this.appsToSwap = appsToSwap; - } - - /** - * Current screen that the swap process is up to. - * Will be one of the SwapState.STEP_* values. - */ - @SwapStep - public int getStep() { - return step; - } - - public SwapState setStep(@SwapStep int step) { - this.step = step; - persistStep(); - return this; - } - - public Set getAppsToSwap() { - return appsToSwap; - } - private static final String KEY_STEP = "step"; private static final String KEY_APPS_TO_SWAP = "appsToSwap"; @@ -81,14 +41,79 @@ public class SwapState { return instance; } + @NonNull + private final Context context; + + @NonNull + private Set appsToSwap; + + private SwapState(@NonNull Context context, @SwapStep int step, @NonNull Set appsToSwap) { + this.context = context.getApplicationContext(); + this.step = step; + this.appsToSwap = appsToSwap; + } + + /** + * Where relevant, the state of the swap process will be saved to disk using preferences. + * Note that this is not always useful, for example saving the "current wifi network" is + * bound to cause trouble when the user opens the swap process again and is connected to + * a different network. + */ private SharedPreferences persistence() { return context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_APPEND); } + // ========================================================== + // Manage the current step + // ("Step" refers to the current view being shown in the UI) + // ========================================================== + + public static final int STEP_INTRO = 1; + public static final int STEP_SELECT_APPS = 2; + public static final int STEP_JOIN_WIFI = 3; + public static final int STEP_SHOW_NFC = 4; + public static final int STEP_WIFI_QR = 5; + + private @SwapStep int step; + + /** + * Current screen that the swap process is up to. + * Will be one of the SwapState.STEP_* values. + */ + @SwapStep + public int getStep() { + return step; + } + + public SwapState setStep(@SwapStep int step) { + this.step = step; + persistStep(); + return this; + } + + public @NonNull Set getAppsToSwap() { + return appsToSwap; + } + private void persistStep() { persistence().edit().putInt(KEY_STEP, step).commit(); } + /** + * Ensure that we don't get put into an incorrect state, by forcing people to pass valid + * states to setStep. Ideally this would be done by requiring an enum or something to + * be passed rather than in integer, however that is harder to persist on disk than an int. + * This is the same as, e.g. {@link Context#getSystemService(String)} + */ + @IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR}) + @Retention(RetentionPolicy.SOURCE) + public @interface SwapStep {} + + + // ========================================== + // Remember apps user wants to swap + // ========================================== + private void persistAppsToSwap() { persistence().edit().putString(KEY_APPS_TO_SWAP, serializePackages(appsToSwap)).commit(); } @@ -143,16 +168,6 @@ public class SwapState { persistAppsToSwap(); } - /** - * Ensure that we don't get put into an incorrect state, by forcing people to pass valid - * states to setStep. Ideally this would be done by requiring an enum or something to - * be passed rather than in integer, however that is harder to persist on disk than an int. - * This is the same as, e.g. {@link Context#getSystemService(String)} - */ - @IntDef({STEP_INTRO, STEP_SELECT_APPS, STEP_JOIN_WIFI, STEP_SHOW_NFC, STEP_WIFI_QR}) - @Retention(RetentionPolicy.SOURCE) - public @interface SwapStep {} - // ========================================== // Local repo stop/start/restart handling From 0e16eae5bc28348db55f6ab900e6624f15b8864b Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 28 May 2015 17:20:22 +1000 Subject: [PATCH 27/78] Minor fixes to swap after refactor. List views need to have their header view set _before_ an adapter is assigned to them. Was incorrectly passing an application context instead of an Activity context to the progress dialog for swap. Extend ActionBarActivity rather than FragmentActivity. Don't persist swap workflow state to disk, only store it in the singleton. --- .../org/fdroid/fdroid/localrepo/SwapState.java | 15 +++------------ .../fdroid/fdroid/views/swap/SelectAppsView.java | 5 ++++- .../fdroid/views/swap/SwapWorkflowActivity.java | 3 ++- 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java index 635676c8b..bea440d76 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java @@ -31,11 +31,8 @@ public class SwapState { public static SwapState load(@NonNull Context context) { if (instance == null) { SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); - - @SwapStep int step = preferences.getInt(KEY_STEP, STEP_INTRO); Set appsToSwap = deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, "")); - - instance = new SwapState(context, step, appsToSwap); + instance = new SwapState(context, appsToSwap); } return instance; @@ -47,9 +44,8 @@ public class SwapState { @NonNull private Set appsToSwap; - private SwapState(@NonNull Context context, @SwapStep int step, @NonNull Set appsToSwap) { + private SwapState(@NonNull Context context, @NonNull Set appsToSwap) { this.context = context.getApplicationContext(); - this.step = step; this.appsToSwap = appsToSwap; } @@ -74,7 +70,7 @@ public class SwapState { public static final int STEP_SHOW_NFC = 4; public static final int STEP_WIFI_QR = 5; - private @SwapStep int step; + private @SwapStep int step = STEP_INTRO; /** * Current screen that the swap process is up to. @@ -87,7 +83,6 @@ public class SwapState { public SwapState setStep(@SwapStep int step) { this.step = step; - persistStep(); return this; } @@ -95,10 +90,6 @@ public class SwapState { return appsToSwap; } - private void persistStep() { - persistence().edit().putInt(KEY_STEP, step).commit(); - } - /** * Ensure that we don't get put into an incorrect state, by forcing people to pass valid * states to setStep. Ideally this would be done by requiring an enum or something to diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java index fa8864d20..27ac9a012 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java @@ -87,8 +87,11 @@ public class SelectAppsView extends ListView implements super.onFinishInflate(); adapter = new AppListAdapter(this, getContext(), getContext().getContentResolver().query(InstalledAppProvider.getContentUri(), InstalledAppProvider.DataColumns.ALL, null, null, null)); - setAdapter(adapter); + + // Has to be _before_ "setAdapter()", as per the API docs. addHeaderView(inflate(getContext(), R.layout.swap_create_header, null), null, false); + + setAdapter(adapter); setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); // either reconnect with an existing loader or start a new one diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index c954a1c03..5a185c672 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -10,6 +10,7 @@ import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; +import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -264,7 +265,7 @@ public class SwapWorkflowActivity extends FragmentActivity { private final Context context; public UpdateAsyncTask(@NonNull Set apps) { - context = SwapWorkflowActivity.this.getApplicationContext(); + context = SwapWorkflowActivity.this; selectedApps = apps; progressDialog = new ProgressDialog(context); progressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); From 7c9492e6b421e49faff56147974006476b6c3d53 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sun, 31 May 2015 22:54:57 +1000 Subject: [PATCH 28/78] WIP: Refactoring swap service. Removed LocalRepoService, replaced with SwapService. Still TODO: Manage threads. Currently everything is called from the UI thread, which is a regression from the previous behaviour. I'd like to manage this so that the code interacting with the SwapManager doesn't need to bother itself with whether it is calling from the UI thread or not. The local repo service had many different methods and properties for dealing with starting and stopping various things (webserver, bonjour, in the future it will also need to know about bluetooth and Wifi AP). The SwapService handles this stuff by delegating to specific classes that are only responsible for one of these. Hopefully this will make the process of enabling and disabling swap repos easier to reason about. The local repo service was also stopped and started quite regularly. This meant it was up to the code making use of the service to know if it was running or not, and to enable it if required. The new SwapService is only started once (when the singleton SwapManager is created for the first time). It should not use any more resources, because it is a background service most the time, and it is responsible for moving itself to the foreground when required (the burden is not on the code consuming this service to know when to do this). By having the service running more often, it doesn't need to' continually figure out if it needs to register or unregister listeners for various properties (e.g. https enabled) or wifi broadcasts. The listeners can stay active, and do nothing once notified if swapping is not enabled. Moved the timeout timer (which cancels the swap service after 15 mins) into the SwapService, rather than being managed by the SwapWorkflowActivity. Seems more appropriate for the service to know to time itself out rather than the Activity, seeing as the Activity can die and get GC'ed while the service is still running. Finally, although there is nothing stopping code in F-Droid from talking to the service directly, it is now handled by the SwapManager singleton. This means that details such as using a Messenger or Handler object in order to communicate via arg1 and arg2 is no longer required, and instead methods with proper type signatures can be used. This is similar (but not exactly the same) to how Android system services work. That is, ask for a "Manager" object using getSystemService(), and then use that to perform functionality and query state via that object, which delegates to the service. Then we get the best of both worlds: * Reasonable and type safe method signatures * Services that are not tied to activity lifecycles, which persist beyond the closing of the swap activity. --- F-Droid/AndroidManifest.xml | 2 +- F-Droid/src/org/fdroid/fdroid/FDroidApp.java | 8 - .../fdroid/localrepo/LocalRepoManager.java | 6 + .../fdroid/localrepo/LocalRepoService.java | 312 ------------------ .../{SwapState.java => SwapManager.java} | 100 +++--- .../fdroid/fdroid/localrepo/SwapService.java | 187 +++++++++++ .../fdroid/localrepo/type/BonjourType.java | 87 +++++ .../fdroid/fdroid/localrepo/type/NfcType.java | 27 ++ .../fdroid/localrepo/type/SwapType.java | 11 + .../fdroid/localrepo/type/WebServerType.java | 96 ++++++ .../fdroid/localrepo/type/WifiType.java | 28 ++ .../fdroid/net/WifiStateChangeService.java | 4 +- .../views/swap/ConnectSwapActivity.java | 4 +- .../fdroid/views/swap/JoinWifiView.java | 7 +- .../org/fdroid/fdroid/views/swap/NfcView.java | 7 +- .../fdroid/views/swap/SelectAppsView.java | 8 +- .../fdroid/views/swap/StartSwapView.java | 8 +- .../views/swap/SwapWorkflowActivity.java | 63 ++-- .../fdroid/fdroid/views/swap/WifiQrView.java | 7 +- 19 files changed, 537 insertions(+), 435 deletions(-) delete mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java rename F-Droid/src/org/fdroid/fdroid/localrepo/{SwapState.java => SwapManager.java} (67%) create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java create mode 100644 F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java diff --git a/F-Droid/AndroidManifest.xml b/F-Droid/AndroidManifest.xml index 9bcc88614..539870340 100644 --- a/F-Droid/AndroidManifest.xml +++ b/F-Droid/AndroidManifest.xml @@ -462,7 +462,7 @@ - + diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java index 6cb5a63ec..07082408c 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java @@ -23,10 +23,8 @@ import android.app.Activity; import android.app.Application; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothManager; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -34,10 +32,6 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.net.Uri; import android.os.Build; -import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; import android.preference.PreferenceManager; import android.widget.Toast; @@ -52,14 +46,12 @@ import org.fdroid.fdroid.compat.PRNGFixes; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.InstalledAppCacheUpdater; import org.fdroid.fdroid.data.Repo; -import org.fdroid.fdroid.localrepo.LocalRepoService; import org.fdroid.fdroid.net.IconDownloader; import org.fdroid.fdroid.net.WifiStateChangeService; import java.io.File; import java.security.Security; import java.util.Locale; -import java.util.Set; public class FDroidApp extends Application { diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 3624aef9e..e18f58e66 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -55,6 +55,12 @@ import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarOutputStream; +/** + * The {@link SwapManager} deals with managing the entire workflow from selecting apps to + * swap, to invoking this class to prepare the webroot, to enabling various communication protocols. + * This class deals specifically with the webroot side of things, ensuring we have a valid index.jar + * and the relevant .apk and icon files available. + */ public class LocalRepoManager { private static final String TAG = "LocalRepoManager"; diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java deleted file mode 100644 index 223acf42a..000000000 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/LocalRepoService.java +++ /dev/null @@ -1,312 +0,0 @@ -package org.fdroid.fdroid.localrepo; - -import android.annotation.SuppressLint; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; -import android.support.v4.app.NotificationCompat; -import android.support.v4.content.LocalBroadcastManager; -import android.util.Log; - -import org.fdroid.fdroid.FDroidApp; -import org.fdroid.fdroid.Preferences; -import org.fdroid.fdroid.Preferences.ChangeListener; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.net.LocalHTTPD; -import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; - -import java.io.IOException; -import java.net.BindException; -import java.util.HashMap; -import java.util.Random; - -import javax.jmdns.JmDNS; -import javax.jmdns.ServiceInfo; - -public class LocalRepoService extends Service { - private static final String TAG = "LocalRepoService"; - - public static final String STATE = "org.fdroid.fdroid.action.LOCAL_REPO_STATE"; - public static final String STARTED = "org.fdroid.fdroid.category.LOCAL_REPO_STARTED"; - public static final String STOPPED = "org.fdroid.fdroid.category.LOCAL_REPO_STOPPED"; - - private NotificationManager notificationManager; - private Notification notification; - // Unique Identification Number for the Notification. - // We use it on Notification start, and to cancel it. - private final int NOTIFICATION = R.string.local_repo_running; - - private Handler webServerThreadHandler = null; - private LocalHTTPD localHttpd; - private JmDNS jmdns; - private ServiceInfo pairService; - - public static final int START = 1111111; - public static final int STOP = 12345678; - public static final int RESTART = 87654; - - final Messenger messenger = new Messenger(new StartStopHandler(this)); - - /** - * This is most likely going to be created on the UI thread, hence all of - * the message handling will take place on a new thread to prevent blocking - * the UI. - */ - static class StartStopHandler extends Handler { - - private final LocalRepoService service; - - public StartStopHandler(LocalRepoService service) { - this.service = service; - } - - @Override - public void handleMessage(final Message msg) { - new Thread() { - public void run() { - switch (msg.arg1) { - case START: - service.startNetworkServices(); - break; - case STOP: - service.stopNetworkServices(); - break; - case RESTART: - service.stopNetworkServices(); - service.startNetworkServices(); - break; - default: - Log.e(TAG, "Unsupported msg.arg1 (" + msg.arg1 + "), ignored"); - break; - } - } - }.start(); - } - } - - private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent i) { - stopNetworkServices(); - startNetworkServices(); - } - }; - - private ChangeListener localRepoBonjourChangeListener = new ChangeListener() { - @Override - public void onPreferenceChange() { - if (localHttpd.isAlive()) - if (Preferences.get().isLocalRepoBonjourEnabled()) - registerMDNSService(); - else - unregisterMDNSService(); - } - }; - - private final ChangeListener localRepoHttpsChangeListener = new ChangeListener() { - @Override - public void onPreferenceChange() { - Log.i(TAG, "onPreferenceChange"); - if (localHttpd.isAlive()) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - stopNetworkServices(); - startNetworkServices(); - return null; - } - }.execute(); - } - } - }; - - private void showNotification() { - notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - // launch LocalRepoActivity if the user selects this notification - Intent intent = new Intent(this, SwapWorkflowActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); - PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - notification = new NotificationCompat.Builder(this) - .setContentTitle(getText(R.string.local_repo_running)) - .setContentText(getText(R.string.touch_to_configure_local_repo)) - .setSmallIcon(R.drawable.ic_swap) - .setContentIntent(contentIntent) - .build(); - startForeground(NOTIFICATION, notification); - } - - @Override - public void onCreate() { - showNotification(); - startNetworkServices(); - Preferences.get().registerLocalRepoBonjourListeners(localRepoBonjourChangeListener); - - LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, - new IntentFilter(WifiStateChangeService.BROADCAST)); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - // We want this service to continue running until it is explicitly - // stopped, so return sticky. - return START_STICKY; - } - - @Override - public void onDestroy() { - new Thread() { - public void run() { - stopNetworkServices(); - } - }.start(); - - notificationManager.cancel(NOTIFICATION); - LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange); - Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener); - } - - @Override - public IBinder onBind(Intent intent) { - return messenger.getBinder(); - } - - private void startNetworkServices() { - Log.d(TAG, "Starting local repo network services"); - startWebServer(); - if (Preferences.get().isLocalRepoBonjourEnabled()) - registerMDNSService(); - Preferences.get().registerLocalRepoHttpsListeners(localRepoHttpsChangeListener); - } - - private void stopNetworkServices() { - Log.d(TAG, "Stopping local repo network services"); - Preferences.get().unregisterLocalRepoHttpsListeners(localRepoHttpsChangeListener); - - Log.d(TAG, "Unregistering MDNS service..."); - unregisterMDNSService(); - - Log.d(TAG, "Stopping web server..."); - stopWebServer(); - } - - private void startWebServer() { - Runnable webServer = new Runnable() { - // Tell Eclipse this is not a leak because of Looper use. - @SuppressLint("HandlerLeak") - @Override - public void run() { - localHttpd = new LocalHTTPD( - LocalRepoService.this, - getFilesDir(), - Preferences.get().isLocalRepoHttpsEnabled()); - - Looper.prepare(); // must be run before creating a Handler - webServerThreadHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj); - localHttpd.stop(); - } - }; - try { - localHttpd.start(); - } catch (BindException e) { - int prev = FDroidApp.port; - FDroidApp.port = FDroidApp.port + new Random().nextInt(1111); - Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!"); - startService(new Intent(LocalRepoService.this, WifiStateChangeService.class)); - } catch (IOException e) { - Log.e(TAG, "Could not start local repo HTTP server: " + e); - Log.e(TAG, Log.getStackTraceString(e)); - } - Looper.loop(); // start the message receiving loop - } - }; - new Thread(webServer).start(); - Intent intent = new Intent(STATE); - intent.putExtra(STATE, STARTED); - LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent); - } - - private void stopWebServer() { - if (webServerThreadHandler == null) { - Log.i(TAG, "null handler in stopWebServer"); - return; - } - Message msg = webServerThreadHandler.obtainMessage(); - msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop"; - webServerThreadHandler.sendMessage(msg); - Intent intent = new Intent(STATE); - intent.putExtra(STATE, STOPPED); - LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent); - } - - private void registerMDNSService() { - new Thread(new Runnable() { - @Override - public void run() { - /* - * a ServiceInfo can only be registered with a single instance - * of JmDNS, and there is only ever a single LocalHTTPD port to - * advertise anyway. - */ - if (pairService != null || jmdns != null) - clearCurrentMDNSService(); - String repoName = Preferences.get().getLocalRepoName(); - HashMap values = new HashMap<>(); - values.put("path", "/fdroid/repo"); - values.put("name", repoName); - values.put("fingerprint", FDroidApp.repo.fingerprint); - String type; - if (Preferences.get().isLocalRepoHttpsEnabled()) { - values.put("type", "fdroidrepos"); - type = "_https._tcp.local."; - } else { - values.put("type", "fdroidrepo"); - type = "_http._tcp.local."; - } - try { - pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values); - jmdns = JmDNS.create(); - jmdns.registerService(pairService); - } catch (IOException e) { - Log.e(TAG, "Error while registering jmdns service: " + e); - Log.e(TAG, Log.getStackTraceString(e)); - } - } - }).start(); - } - - private void unregisterMDNSService() { - if (localRepoBonjourChangeListener != null) { - Preferences.get().unregisterLocalRepoBonjourListeners(localRepoBonjourChangeListener); - localRepoBonjourChangeListener = null; - } - clearCurrentMDNSService(); - } - - private void clearCurrentMDNSService() { - if (jmdns != null) { - if (pairService != null) { - jmdns.unregisterService(pairService); - pairService = null; - } - jmdns.unregisterAllServices(); - Utils.closeQuietly(jmdns); - jmdns = null; - } - } -} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java similarity index 67% rename from F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java rename to F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java index bea440d76..9f5e1d1d1 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapState.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapManager.java @@ -6,11 +6,10 @@ import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.os.IBinder; -import android.os.Message; -import android.os.Messenger; -import android.os.RemoteException; import android.support.annotation.IntDef; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.util.Log; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -18,21 +17,20 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -public class SwapState { +public class SwapManager { + private static final String TAG = "SwapState"; private static final String SHARED_PREFERENCES = "swap-state"; - - private static final String KEY_STEP = "step"; private static final String KEY_APPS_TO_SWAP = "appsToSwap"; - private static SwapState instance; + private static SwapManager instance; @NonNull - public static SwapState load(@NonNull Context context) { + public static SwapManager load(@NonNull Context context) { if (instance == null) { SharedPreferences preferences = context.getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE); Set appsToSwap = deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, "")); - instance = new SwapState(context, appsToSwap); + instance = new SwapManager(context, appsToSwap); } return instance; @@ -44,9 +42,11 @@ public class SwapState { @NonNull private Set appsToSwap; - private SwapState(@NonNull Context context, @NonNull Set appsToSwap) { + private SwapManager(@NonNull Context context, @NonNull Set appsToSwap) { this.context = context.getApplicationContext(); this.appsToSwap = appsToSwap; + + setupService(); } /** @@ -81,7 +81,7 @@ public class SwapState { return step; } - public SwapState setStep(@SwapStep int step) { + public SwapManager setStep(@SwapStep int step) { this.step = step; return this; } @@ -114,7 +114,7 @@ public class SwapState { * which is only available in API >= 11. * Package names are reverse-DNS-style, so they should only have alpha numeric values. Thus, * this uses a comma as the separator. - * @see SwapState#deserializePackages(String) + * @see SwapManager#deserializePackages(String) */ private static String serializePackages(Set packages) { StringBuilder sb = new StringBuilder(); @@ -128,7 +128,7 @@ public class SwapState { } /** - * @see SwapState#deserializePackages(String) + * @see SwapManager#deserializePackages(String) */ private static Set deserializePackages(String packages) { Set set = new HashSet<>(); @@ -164,53 +164,61 @@ public class SwapState { // Local repo stop/start/restart handling // ========================================== - private Messenger localRepoServiceMessenger = null; - private boolean localRepoServiceIsBound = false; + @Nullable + private SwapService service = null; - private final ServiceConnection serviceConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - localRepoServiceMessenger = new Messenger(service); + private void setupService() { + + ServiceConnection serviceConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder binder) { + Log.d(TAG, "Swap service connected, enabling SwapManager to communicate with SwapService."); + service = ((SwapService.Binder)binder).getService(); + } + + @Override + public void onServiceDisconnected(ComponentName className) { + Log.d(TAG, "Swap service disconnected"); + service = null; + } + }; + + // The server should not be doing anything or occupying any (noticable) resources + // until we actually ask it to enable swapping. Therefore, we will start it nice and + // early so we don't have to wait until it is connected later. + Intent service = new Intent(context, SwapService.class); + if (context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE)) { + context.startService(service); } - @Override - public void onServiceDisconnected(ComponentName className) { - localRepoServiceMessenger = null; - } - }; + } - public void startLocalRepoService() { - if (!localRepoServiceIsBound) { - Intent service = new Intent(context, LocalRepoService.class); - localRepoServiceIsBound = context.bindService(service, serviceConnection, Context.BIND_AUTO_CREATE); - if (localRepoServiceIsBound) - context.startService(service); + public void enableSwapping() { + if (service != null) { + service.enableSwapping(); + } else { + Log.e(TAG, "Couldn't enable swap, because service was not running."); } } - public void stopLocalRepoService() { - if (localRepoServiceIsBound) { - context.unbindService(serviceConnection); - localRepoServiceIsBound = false; + public void disableSwapping() { + if (service != null) { + service.disableSwapping(); + } else { + Log.e(TAG, "Couldn't disable swap, because service was not running."); } - context.stopService(new Intent(context, LocalRepoService.class)); } /** - * Handles checking if the {@link LocalRepoService} is running, and only restarts it if it was running. + * Handles checking if the {@link SwapService} is running, and only restarts it if it was running. */ - public void restartLocalRepoServiceIfRunning() { - if (localRepoServiceMessenger != null) { - try { - Message msg = Message.obtain(null, LocalRepoService.RESTART, LocalRepoService.RESTART, 0); - localRepoServiceMessenger.send(msg); - } catch (RemoteException e) { - e.printStackTrace(); - } + public void restartIfEnabled() { + if (service != null) { + service.restartIfEnabled(); } } - public boolean isLocalRepoServiceRunning() { - return localRepoServiceIsBound; + public boolean isEnabled() { + return service != null && service.isEnabled(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java new file mode 100644 index 000000000..d4cfbbec5 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java @@ -0,0 +1,187 @@ +package org.fdroid.fdroid.localrepo; + +import android.app.Notification; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.IBinder; +import android.support.annotation.Nullable; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; + +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.localrepo.type.BonjourType; +import org.fdroid.fdroid.localrepo.type.NfcType; +import org.fdroid.fdroid.localrepo.type.WebServerType; +import org.fdroid.fdroid.net.WifiStateChangeService; +import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; + +import java.util.Timer; +import java.util.TimerTask; + +/** + * Central service which manages all of the different moving parts of swap which are required + * to enable p2p swapping of apps. Currently manages WiFi and NFC. Will manage Bluetooth in + * the future. + * + * TODO: Manage threading correctly. + */ +public class SwapService extends Service { + + private static final String TAG = "SwapService"; + + private static final int NOTIFICATION = 1; + + private final Binder binder = new Binder(); + private final BonjourType bonjourType; + private final WebServerType webServerType; + + // TODO: The NFC type can't really be managed by the service, because it is intrinsically tied + // to a specific _Activity_, and will only be active while that activity is shown. This service + // knows nothing about activities. + private final NfcType nfcType; + + private final static int TIMEOUT = 900000; // 15 mins + + /** + * Used to automatically turn of swapping after a defined amount of time (15 mins). + */ + @Nullable + private Timer timer; + + public class Binder extends android.os.Binder { + public SwapService getService() { + return SwapService.this; + } + } + + public SwapService() { + nfcType = new NfcType(this); + bonjourType = new BonjourType(this); + webServerType = new WebServerType(this); + } + + public void onCreate() { + super.onCreate(); + + Preferences.get().unregisterLocalRepoBonjourListeners(bonjourEnabledListener); + Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener); + + LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, + new IntentFilter(WifiStateChangeService.BROADCAST)); + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + disableSwapping(); + } + + private Notification createNotification() { + Intent intent = new Intent(this, SwapWorkflowActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + return new NotificationCompat.Builder(this) + .setContentTitle(getText(R.string.local_repo_running)) + .setContentText(getText(R.string.touch_to_configure_local_repo)) + .setSmallIcon(R.drawable.ic_swap) + .setContentIntent(contentIntent) + .build(); + } + + private boolean enabled = false; + + public void enableSwapping() { + if (!enabled) { + nfcType.start(); + webServerType.start(); + bonjourType.start(); + startForeground(NOTIFICATION, createNotification()); + enabled = true; + } + + // Regardless of whether it was previously enabled, start the timer again. This ensures that + // if, e.g. a person views the swap activity again, it will attempt to enable swapping if + // appropriate, and thus restart this timer. + initTimer(); + } + + public void disableSwapping() { + if (enabled) { + bonjourType.stop(); + webServerType.stop(); + nfcType.stop(); + stopForeground(true); + if (timer != null) { + timer.cancel(); + } + enabled = false; + } + } + + public boolean isEnabled() { + return enabled; + } + + public void restartIfEnabled() { + if (enabled) { + disableSwapping(); + enableSwapping(); + } + } + + private void initTimer() { + if (timer != null) + timer.cancel(); + + // automatically turn off after 15 minutes + timer = new Timer(); + timer.schedule(new TimerTask() { + @Override + public void run() { + disableSwapping(); + } + }, TIMEOUT); + } + + @SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local... + private final Preferences.ChangeListener bonjourEnabledListener = new Preferences.ChangeListener() { + @Override + public void onPreferenceChange() { + Log.i(TAG, "Use Bonjour while swapping preference changed."); + if (enabled) + if (Preferences.get().isLocalRepoBonjourEnabled()) + bonjourType.start(); + else + bonjourType.stop(); + } + }; + + @SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local... + private final Preferences.ChangeListener httpsEnabledListener = new Preferences.ChangeListener() { + @Override + public void onPreferenceChange() { + Log.i(TAG, "Swap over HTTPS preference changed."); + restartIfEnabled(); + } + }; + + @SuppressWarnings("FieldCanBeLocal") // The constructor will get bloated if these are all local... + private final BroadcastReceiver onWifiChange = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent i) { + restartIfEnabled(); + } + }; + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java new file mode 100644 index 000000000..cfa89803d --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java @@ -0,0 +1,87 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.content.Context; +import android.util.Log; + +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.Utils; + +import java.io.IOException; +import java.util.HashMap; + +import javax.jmdns.JmDNS; +import javax.jmdns.ServiceInfo; + +public class BonjourType implements SwapType { + + private static final String TAG = "BonjourType"; + + private JmDNS jmdns; + private ServiceInfo pairService; + private final Context context; + + public BonjourType(Context context) { + this.context = context; + } + + @Override + public void start() { + + if (Preferences.get().isLocalRepoBonjourEnabled()) + return; + + /* + * a ServiceInfo can only be registered with a single instance + * of JmDNS, and there is only ever a single LocalHTTPD port to + * advertise anyway. + */ + if (pairService != null || jmdns != null) + clearCurrentMDNSService(); + String repoName = Preferences.get().getLocalRepoName(); + HashMap values = new HashMap<>(); + values.put("path", "/fdroid/repo"); + values.put("name", repoName); + values.put("fingerprint", FDroidApp.repo.fingerprint); + String type; + if (Preferences.get().isLocalRepoHttpsEnabled()) { + values.put("type", "fdroidrepos"); + type = "_https._tcp.local."; + } else { + values.put("type", "fdroidrepo"); + type = "_http._tcp.local."; + } + try { + pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values); + jmdns = JmDNS.create(); + jmdns.registerService(pairService); + } catch (IOException e) { + Log.e(TAG, "Error while registering jmdns service: " + e); + Log.e(TAG, Log.getStackTraceString(e)); + } + } + + @Override + public void stop() { + Log.d(TAG, "Unregistering MDNS service..."); + clearCurrentMDNSService(); + } + + private void clearCurrentMDNSService() { + if (jmdns != null) { + if (pairService != null) { + jmdns.unregisterService(pairService); + pairService = null; + } + jmdns.unregisterAllServices(); + Utils.closeQuietly(jmdns); + jmdns = null; + } + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java new file mode 100644 index 000000000..f71130300 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/NfcType.java @@ -0,0 +1,27 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.content.Context; + +public class NfcType implements SwapType { + + private final Context context; + + public NfcType(Context context) { + this.context = context; + } + + @Override + public void start() { + } + + @Override + public void stop() { + + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java new file mode 100644 index 000000000..68161616a --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java @@ -0,0 +1,11 @@ +package org.fdroid.fdroid.localrepo.type; + +public interface SwapType { + + void start(); + + void stop(); + + void restart(); + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java new file mode 100644 index 000000000..18e501577 --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java @@ -0,0 +1,96 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import org.fdroid.fdroid.FDroidApp; +import org.fdroid.fdroid.Preferences; +import org.fdroid.fdroid.net.LocalHTTPD; +import org.fdroid.fdroid.net.WifiStateChangeService; + +import java.io.IOException; +import java.net.BindException; +import java.util.Random; + +public class WebServerType implements SwapType { + + private static final String TAG = "WebServerType"; + + private Handler webServerThreadHandler = null; + private LocalHTTPD localHttpd; + private final Context context; + + public WebServerType(Context context) { + this.context = context; + } + + @Override + public void start() { + + Runnable webServer = new Runnable() { + // Tell Eclipse this is not a leak because of Looper use. + @SuppressLint("HandlerLeak") + @Override + public void run() { + localHttpd = new LocalHTTPD( + context, + context.getFilesDir(), + Preferences.get().isLocalRepoHttpsEnabled()); + + Looper.prepare(); // must be run before creating a Handler + webServerThreadHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + Log.i(TAG, "we've been asked to stop the webserver: " + msg.obj); + localHttpd.stop(); + } + }; + try { + localHttpd.start(); + } catch (BindException e) { + int prev = FDroidApp.port; + FDroidApp.port = FDroidApp.port + new Random().nextInt(1111); + Log.w(TAG, "port " + prev + " occupied, trying on " + FDroidApp.port + "!"); + context.startService(new Intent(context, WifiStateChangeService.class)); + } catch (IOException e) { + Log.e(TAG, "Could not start local repo HTTP server: " + e); + Log.e(TAG, Log.getStackTraceString(e)); + } + Looper.loop(); // start the message receiving loop + } + }; + new Thread(webServer).start(); + + // TODO: Don't think these were ever being received by anyone... + /*Intent intent = new Intent(STATE); + intent.putExtra(STATE, STARTED); + LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);*/ + } + + @Override + public void stop() { + if (webServerThreadHandler == null) { + Log.i(TAG, "null handler in stopWebServer"); + return; + } + Message msg = webServerThreadHandler.obtainMessage(); + msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop"; + webServerThreadHandler.sendMessage(msg); + + // TODO: Don't think these were ever being received by anyone... + /*Intent intent = new Intent(STATE); + intent.putExtra(STATE, STOPPED); + LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);*/ + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java new file mode 100644 index 000000000..2ba29851c --- /dev/null +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WifiType.java @@ -0,0 +1,28 @@ +package org.fdroid.fdroid.localrepo.type; + +import android.content.Context; + +public class WifiType implements SwapType { + + private final Context context; + + public WifiType(Context context) { + this.context = context; + } + + @Override + public void start() { + + } + + @Override + public void stop() { + + } + + @Override + public void restart() { + + } + +} diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java index 9c2330a04..fa55b8dbe 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -17,7 +17,7 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.localrepo.LocalRepoKeyStore; import org.fdroid.fdroid.localrepo.LocalRepoManager; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import java.net.Inet6Address; import java.net.InetAddress; @@ -152,7 +152,7 @@ public class WifiStateChangeService extends Service { Intent intent = new Intent(BROADCAST); LocalBroadcastManager.getInstance(WifiStateChangeService.this).sendBroadcast(intent); WifiStateChangeService.this.stopSelf(); - SwapState.load(WifiStateChangeService.this).restartLocalRepoServiceIfRunning(); + SwapManager.load(WifiStateChangeService.this).restartIfEnabled(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java index 6bb1f7efd..aa682b99a 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/ConnectSwapActivity.java @@ -27,7 +27,7 @@ import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import java.io.IOException; import java.io.UnsupportedEncodingException; @@ -155,7 +155,7 @@ public class ConnectSwapActivity extends ActionBarActivity implements ProgressLi // Only ask server to swap with us, if we are actually running a local repo service. // It is possible to have a swap initiated without first starting a swap, in which // case swapping back is pointless. - if (!newRepoConfig.preventFurtherSwaps() && SwapState.load(this).isLocalRepoServiceRunning()) { + if (!newRepoConfig.preventFurtherSwaps() && SwapManager.load(this).isEnabled()) { askServerToSwapWithUs(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java index 99baf6706..8bf9bd6dc 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/JoinWifiView.java @@ -22,9 +22,8 @@ import android.widget.TextView; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity.InnerView { @@ -122,11 +121,11 @@ public class JoinWifiView extends RelativeLayout implements SwapWorkflowActivity @Override public int getStep() { - return SwapState.STEP_JOIN_WIFI; + return SwapManager.STEP_JOIN_WIFI; } @Override public int getPreviousStep() { - return SwapState.STEP_SELECT_APPS; + return SwapManager.STEP_SELECT_APPS; } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java index 142218aca..5dba1c7c1 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/NfcView.java @@ -15,8 +15,7 @@ import android.widget.RelativeLayout; import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapState; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; +import org.fdroid.fdroid.localrepo.SwapManager; public class NfcView extends RelativeLayout implements SwapWorkflowActivity.InnerView { @@ -70,11 +69,11 @@ public class NfcView extends RelativeLayout implements SwapWorkflowActivity.Inne @Override public int getStep() { - return SwapState.STEP_SHOW_NFC; + return SwapManager.STEP_SHOW_NFC; } @Override public int getPreviousStep() { - return SwapState.STEP_JOIN_WIFI; + return SwapManager.STEP_JOIN_WIFI; } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java index 27ac9a012..dfbf5091e 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java @@ -34,7 +34,7 @@ import android.widget.TextView; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.InstalledAppProvider; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; public class SelectAppsView extends ListView implements SwapWorkflowActivity.InnerView, @@ -62,7 +62,7 @@ public class SelectAppsView extends ListView implements return (SwapWorkflowActivity)getContext(); } - private SwapState getState() { + private SwapManager getState() { return getActivity().getState(); } @@ -136,12 +136,12 @@ public class SelectAppsView extends ListView implements @Override public int getStep() { - return SwapState.STEP_SELECT_APPS; + return SwapManager.STEP_SELECT_APPS; } @Override public int getPreviousStep() { - return SwapState.STEP_INTRO; + return SwapManager.STEP_INTRO; } private void toggleAppSelected(int position) { diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java index 3f71317bf..f46b21eb3 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/StartSwapView.java @@ -5,15 +5,13 @@ import android.content.Context; import android.os.Build; import android.support.annotation.NonNull; import android.util.AttributeSet; -import android.view.ContextThemeWrapper; import android.view.Menu; import android.view.MenuInflater; import android.view.View; import android.widget.LinearLayout; import org.fdroid.fdroid.R; -import org.fdroid.fdroid.localrepo.SwapState; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; +import org.fdroid.fdroid.localrepo.SwapManager; public class StartSwapView extends LinearLayout implements SwapWorkflowActivity.InnerView { @@ -66,7 +64,7 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. @Override public int getStep() { - return SwapState.STEP_INTRO; + return SwapManager.STEP_INTRO; } @Override @@ -75,6 +73,6 @@ public class StartSwapView extends LinearLayout implements SwapWorkflowActivity. // if getStep is STEP_INTRO, don't even bother asking for getPreviousStep. But that is a // bit messy. It would be nicer if this was handled using the same mechanism as everything // else. - return SwapState.STEP_INTRO; + return SwapManager.STEP_INTRO; } } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index 5a185c672..f6d52d9ec 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -10,7 +10,6 @@ import android.os.Bundle; import android.support.annotation.LayoutRes; import android.support.annotation.NonNull; import android.support.v4.app.FragmentActivity; -import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -29,37 +28,40 @@ import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.NewRepoConfig; import org.fdroid.fdroid.localrepo.LocalRepoManager; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; public class SwapWorkflowActivity extends FragmentActivity { private ViewGroup container; + /** + * A UI component (subclass of {@link View}) which forms part of the swap workflow. + * There is a one to one mapping between an {@link org.fdroid.fdroid.views.swap.SwapWorkflowActivity.InnerView} + * and a {@link org.fdroid.fdroid.localrepo.SwapManager.SwapStep}, and these views know what + * the previous view before them should be. + */ public interface InnerView { /** @return True if the menu should be shown. */ boolean buildMenu(Menu menu, @NonNull MenuInflater inflater); /** @return The step that this view represents. */ - @SwapState.SwapStep int getStep(); + @SwapManager.SwapStep int getStep(); - @SwapState.SwapStep int getPreviousStep(); + @SwapManager.SwapStep int getPreviousStep(); } private static final int CONNECT_TO_SWAP = 1; - private SwapState state; + private SwapManager state; private InnerView currentView; private boolean hasPreparedLocalRepo = false; private UpdateAsyncTask updateSwappableAppsTask = null; - private Timer shutdownLocalRepoTimer; @Override public void onBackPressed() { - if (currentView.getStep() == SwapState.STEP_INTRO) { + if (currentView.getStep() == SwapManager.STEP_INTRO) { finish(); } else { int nextStep = currentView.getPreviousStep(); @@ -71,7 +73,7 @@ public class SwapWorkflowActivity extends FragmentActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - state = SwapState.load(this); + state = SwapManager.load(this); setContentView(R.layout.swap_activity); container = (ViewGroup) findViewById(R.id.fragment_container); showRelevantView(); @@ -98,25 +100,25 @@ public class SwapWorkflowActivity extends FragmentActivity { } switch(state.getStep()) { - case SwapState.STEP_INTRO: + case SwapManager.STEP_INTRO: showIntro(); break; - case SwapState.STEP_SELECT_APPS: + case SwapManager.STEP_SELECT_APPS: showSelectApps(); break; - case SwapState.STEP_SHOW_NFC: + case SwapManager.STEP_SHOW_NFC: showNfc(); break; - case SwapState.STEP_JOIN_WIFI: + case SwapManager.STEP_JOIN_WIFI: showJoinWifi(); break; - case SwapState.STEP_WIFI_QR: + case SwapManager.STEP_WIFI_QR: showWifiQr(); break; } } - public SwapState getState() { + public SwapManager getState() { return state; } @@ -197,36 +199,11 @@ public class SwapWorkflowActivity extends FragmentActivity { } private void ensureLocalRepoRunning() { - if (!getState().isLocalRepoServiceRunning()) { - getState().startLocalRepoService(); - initLocalRepoTimer(900000); // 15 mins - } - } - - private void initLocalRepoTimer(long timeoutMilliseconds) { - - // reset the timer if viewing this Activity again - if (shutdownLocalRepoTimer != null) - shutdownLocalRepoTimer.cancel(); - - // automatically turn off after 15 minutes - shutdownLocalRepoTimer = new Timer(); - shutdownLocalRepoTimer.schedule(new TimerTask() { - @Override - public void run() { - getState().stopLocalRepoService(); - } - }, timeoutMilliseconds); - + getState().enableSwapping(); } public void stopSwapping() { - if (getState().isLocalRepoServiceRunning()) { - if (shutdownLocalRepoTimer != null) { - shutdownLocalRepoTimer.cancel(); - } - getState().stopLocalRepoService(); - } + getState().disableSwapping(); finish(); } diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java index 8b215d505..9a0d4f449 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/WifiQrView.java @@ -30,9 +30,8 @@ import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; -import org.fdroid.fdroid.localrepo.SwapState; +import org.fdroid.fdroid.localrepo.SwapManager; import org.fdroid.fdroid.net.WifiStateChangeService; -import org.fdroid.fdroid.views.swap.SwapWorkflowActivity; import java.net.URI; import java.util.List; @@ -114,13 +113,13 @@ public class WifiQrView extends ScrollView implements SwapWorkflowActivity.Inner @Override public int getStep() { - return SwapState.STEP_WIFI_QR; + return SwapManager.STEP_WIFI_QR; } @Override public int getPreviousStep() { // TODO: Find a way to make this optionally go back to the NFC screen if appropriate. - return SwapState.STEP_JOIN_WIFI; + return SwapManager.STEP_JOIN_WIFI; } private void setUIFromWifi() { From 2553b8520cc1a6735aafc866fbf0328eb244d9c7 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 3 Jun 2015 17:51:16 +1000 Subject: [PATCH 29/78] WIP: Move swap starting/stopping to async task. There is now a private [en|dis]ableSwappingSynchronous() method, to go along with the public [en|dis]ableSwapping() method. The public method knows about not spinning up tasks if already enabled, and also about pushing the invocation to a background task. The private method just blindly does what it is asked, without checking if it should be done, and without running on a background task. The same goes for the restartIfEnabled() method, it is responsible for creating a single background task which runs in order: disable, and then enable. This is actually the reason for having the synchronous methods, rather than having, e.g., the public [en|dis]ableSwapping() method know about threads. If that was the case, then restarting would consist of starting, waiting for some form of notification that the background task has completed, and then scheduling the enable task. Now, it is a matter of calling both *SwappingSynchronous methods in succession. --- .../fdroid/fdroid/localrepo/SwapService.java | 81 +++++++++++++++---- 1 file changed, 66 insertions(+), 15 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java index d4cfbbec5..80fd7750c 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java @@ -7,6 +7,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.os.AsyncTask; import android.os.IBinder; import android.support.annotation.Nullable; import android.support.v4.app.NotificationCompat; @@ -101,13 +102,25 @@ public class SwapService extends Service { private boolean enabled = false; + /** + * Ensures that the webserver is running, as are the other services which make swap work. + * Will only do all this if it is not already running, and will run on a background thread.' + * TODO: What about an "enabling" status? Not sure if it will be useful or not. + */ public void enableSwapping() { if (!enabled) { - nfcType.start(); - webServerType.start(); - bonjourType.start(); - startForeground(NOTIFICATION, createNotification()); - enabled = true; + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + enableSwappingSynchronous(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + enabled = true; + } + }.execute(); } // Regardless of whether it was previously enabled, start the timer again. This ensures that @@ -116,27 +129,65 @@ public class SwapService extends Service { initTimer(); } + /** + * The guts of this class - responsible for enabling the relevant services for swapping. + * * Doesn't know anything about enabled/disabled. + * * Runs synchronously on the thread it was called. + */ + private void enableSwappingSynchronous() { + nfcType.start(); + webServerType.start(); + bonjourType.start(); + startForeground(NOTIFICATION, createNotification()); + } + public void disableSwapping() { if (enabled) { - bonjourType.stop(); - webServerType.stop(); - nfcType.stop(); - stopForeground(true); - if (timer != null) { - timer.cancel(); - } - enabled = false; + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + disableSwappingSynchronous(); + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + // TODO: Does this need to be run before the background task, so that the timer + // can't kick in while we are shutting down everything? + if (timer != null) { + timer.cancel(); + } + + enabled = false; + } + }.execute(); } } + /** + * @see SwapService#enableSwappingSynchronous() + */ + private void disableSwappingSynchronous() { + bonjourType.stop(); + webServerType.stop(); + nfcType.stop(); + stopForeground(true); + } + public boolean isEnabled() { return enabled; } public void restartIfEnabled() { if (enabled) { - disableSwapping(); - enableSwapping(); + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + disableSwappingSynchronous(); + enableSwappingSynchronous(); + return null; + } + }.execute(); } } From d9f91da9a66dd74b0114115104ed0850c63516ab Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 3 Jun 2015 23:41:51 +1000 Subject: [PATCH 30/78] WIP: Added logging, removed dead code, fixed minor bugs. Logging to help understand when the service is started, stopped, restarted, and when individual components (e.g. web server, bonjour) are started/stopped. Fixed bug where service was moved to foreground during "restartIfEnabled" because that is unneccesary if it is already in the foreground. --- .../fdroid/fdroid/localrepo/SwapService.java | 27 +++++++++++++++---- .../fdroid/localrepo/type/BonjourType.java | 2 ++ .../fdroid/localrepo/type/WebServerType.java | 11 +------- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java index 80fd7750c..d41ec66e5 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/SwapService.java @@ -69,8 +69,8 @@ public class SwapService extends Service { public void onCreate() { super.onCreate(); - - Preferences.get().unregisterLocalRepoBonjourListeners(bonjourEnabledListener); + Log.d(TAG, "Creating service, will register appropriate listeners."); + Preferences.get().registerLocalRepoBonjourListeners(bonjourEnabledListener); Preferences.get().registerLocalRepoHttpsListeners(httpsEnabledListener); LocalBroadcastManager.getInstance(this).registerReceiver(onWifiChange, @@ -85,7 +85,11 @@ public class SwapService extends Service { @Override public void onDestroy() { super.onDestroy(); + Log.d(TAG, "Destroying service, will disable swapping if required, and unregister listeners."); disableSwapping(); + Preferences.get().unregisterLocalRepoBonjourListeners(bonjourEnabledListener); + Preferences.get().unregisterLocalRepoHttpsListeners(httpsEnabledListener); + LocalBroadcastManager.getInstance(this).unregisterReceiver(onWifiChange); } private Notification createNotification() { @@ -112,12 +116,15 @@ public class SwapService extends Service { new AsyncTask() { @Override protected Void doInBackground(Void... params) { + Log.d(TAG, "Started background task to enable swapping."); enableSwappingSynchronous(); return null; } @Override protected void onPostExecute(Void aVoid) { + Log.d(TAG, "Moving SwapService to foreground so that it hangs around even when F-Droid is closed."); + startForeground(NOTIFICATION, createNotification()); enabled = true; } }.execute(); @@ -138,7 +145,6 @@ public class SwapService extends Service { nfcType.start(); webServerType.start(); bonjourType.start(); - startForeground(NOTIFICATION, createNotification()); } public void disableSwapping() { @@ -146,12 +152,15 @@ public class SwapService extends Service { new AsyncTask() { @Override protected Void doInBackground(Void... params) { + Log.d(TAG, "Started background task to disable swapping."); disableSwappingSynchronous(); return null; } @Override protected void onPostExecute(Void aVoid) { + Log.d(TAG, "Finished background task to disable swapping."); + // TODO: Does this need to be run before the background task, so that the timer // can't kick in while we are shutting down everything? if (timer != null) { @@ -159,6 +168,9 @@ public class SwapService extends Service { } enabled = false; + + Log.d(TAG, "Moving SwapService to background so that it can be GC'ed if required."); + stopForeground(true); } }.execute(); } @@ -168,10 +180,10 @@ public class SwapService extends Service { * @see SwapService#enableSwappingSynchronous() */ private void disableSwappingSynchronous() { + Log.d(TAG, "Disabling SwapService (bonjour, webserver, etc)"); bonjourType.stop(); webServerType.stop(); nfcType.stop(); - stopForeground(true); } public boolean isEnabled() { @@ -183,6 +195,7 @@ public class SwapService extends Service { new AsyncTask() { @Override protected Void doInBackground(Void... params) { + Log.d(TAG, "Restarting swap services."); disableSwappingSynchronous(); enableSwappingSynchronous(); return null; @@ -192,14 +205,18 @@ public class SwapService extends Service { } private void initTimer() { - if (timer != null) + if (timer != null) { + Log.d(TAG, "Cancelling existing timer"); timer.cancel(); + } // automatically turn off after 15 minutes + Log.d(TAG, "Initializing timer to 15 minutes"); timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { + Log.d(TAG, "Disabling swap because " + TIMEOUT + "ms passed."); disableSwapping(); } }, TIMEOUT); diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java index cfa89803d..4c4a87bed 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/BonjourType.java @@ -52,9 +52,11 @@ public class BonjourType implements SwapType { type = "_http._tcp.local."; } try { + Log.d(TAG, "Starting bonjour service..."); pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values); jmdns = JmDNS.create(); jmdns.registerService(pairService); + Log.d(TAG, "... Bounjour service started."); } catch (IOException e) { Log.e(TAG, "Error while registering jmdns service: " + e); Log.e(TAG, Log.getStackTraceString(e)); diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java index 18e501577..9d168c98e 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/WebServerType.java @@ -65,11 +65,6 @@ public class WebServerType implements SwapType { } }; new Thread(webServer).start(); - - // TODO: Don't think these were ever being received by anyone... - /*Intent intent = new Intent(STATE); - intent.putExtra(STATE, STARTED); - LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);*/ } @Override @@ -78,14 +73,10 @@ public class WebServerType implements SwapType { Log.i(TAG, "null handler in stopWebServer"); return; } + Log.d(TAG, "Sending message to swap webserver to stop it."); Message msg = webServerThreadHandler.obtainMessage(); msg.obj = webServerThreadHandler.getLooper().getThread().getName() + " says stop"; webServerThreadHandler.sendMessage(msg); - - // TODO: Don't think these were ever being received by anyone... - /*Intent intent = new Intent(STATE); - intent.putExtra(STATE, STOPPED); - LocalBroadcastManager.getInstance(LocalRepoService.this).sendBroadcast(intent);*/ } @Override From f9bcf33d505ee89dbda96a7bb579a38ed21c8554 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Wed, 3 Jun 2015 23:55:11 +1000 Subject: [PATCH 31/78] WIP: Fixed minor UI issues. "Confirm swap" background was white but should have been blue. SwapWorkflowActivity now extends ActionBarActivity instead of FragmentActivity so older devices have an action bar (though it is not styled with blue action buttons on android-10 devices). --- F-Droid/res/layout/swap_confirm_receive.xml | 1 + F-Droid/res/values/styles.xml | 2 -- .../src/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/F-Droid/res/layout/swap_confirm_receive.xml b/F-Droid/res/layout/swap_confirm_receive.xml index d2e114e0c..899b1af2c 100644 --- a/F-Droid/res/layout/swap_confirm_receive.xml +++ b/F-Droid/res/layout/swap_confirm_receive.xml @@ -5,6 +5,7 @@ android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" + android:background="@color/swap_blue" android:padding="18dp"> diff --git a/F-Droid/res/values/styles.xml b/F-Droid/res/values/styles.xml index 4c49996c2..d6d567356 100644 --- a/F-Droid/res/values/styles.xml +++ b/F-Droid/res/values/styles.xml @@ -153,8 +153,6 @@ - @@ -28,30 +27,50 @@ android:layout_marginLeft="?attr/listPreferredItemPaddingLeft" android:layout_marginStart="?android:attr/listPreferredItemPaddingStart" android:layout_marginTop="6dip" + android:layout_alignParentStart="true" + android:layout_alignParentLeft="true" + tools:src="@drawable/ic_launcher" tools:ignore="ContentDescription" /> + + + android:textAppearance="?android:attr/textAppearanceMedium" + tools:text="F-Droid" /> + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="Application Manager" /> - + diff --git a/F-Droid/res/layout-v17/select_local_apps_list_item.xml b/F-Droid/res/layout-v17/select_local_apps_list_item.xml index 5ac585492..b0ebeac96 100644 --- a/F-Droid/res/layout-v17/select_local_apps_list_item.xml +++ b/F-Droid/res/layout-v17/select_local_apps_list_item.xml @@ -12,46 +12,57 @@ limitations under the License. --> - + android:paddingTop="2dip"> + + + android:textAppearance="?android:attr/textAppearanceMedium" + tools:text="F-Droid" /> + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="Application Manager" /> - + diff --git a/F-Droid/res/layout/select_local_apps_list_item.xml b/F-Droid/res/layout/select_local_apps_list_item.xml index 29b0598e8..653cf4d91 100644 --- a/F-Droid/res/layout/select_local_apps_list_item.xml +++ b/F-Droid/res/layout/select_local_apps_list_item.xml @@ -12,19 +12,15 @@ limitations under the License. --> - - - + + + android:textAppearance="?android:attr/textAppearanceMedium" + tools:text="F-Droid" /> + android:textAppearance="?android:attr/textAppearanceSmall" + tools:text="Application Manager" /> - + \ No newline at end of file diff --git a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java index dfbf5091e..49c8defb3 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java +++ b/F-Droid/src/org/fdroid/fdroid/views/swap/SelectAppsView.java @@ -4,6 +4,7 @@ import android.annotation.TargetApi; import android.content.Context; import android.content.pm.PackageManager; import android.database.Cursor; +import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; @@ -149,9 +150,12 @@ public class SelectAppsView extends ListView implements String packageName = c.getString(c.getColumnIndex(InstalledAppProvider.DataColumns.APP_ID)); if (getState().hasSelectedPackage(packageName)) { getState().deselectPackage(packageName); + adapter.updateCheckedIndicatorView(position, false); } else { getState().selectPackage(packageName); + adapter.updateCheckedIndicatorView(position, true); } + } @Override @@ -275,6 +279,8 @@ public class SelectAppsView extends ListView implements labelView.setText(appLabel); iconView.setImageDrawable(icon); + final int listPosition = cursor.getPosition() + 1; // To account for the header view. + // Since v11, the Android SDK provided the ability to show selected list items // by highlighting their background. Prior to this, we need to handle this ourselves // by adding a checkbox which can toggle selected items. @@ -283,8 +289,6 @@ public class SelectAppsView extends ListView implements CheckBox checkBox = (CheckBox)checkBoxView; checkBox.setOnCheckedChangeListener(null); - final int listPosition = cursor.getPosition() + 1; // To account for the header view. - checkBox.setChecked(listView.isItemChecked(listPosition)); checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override @@ -294,6 +298,35 @@ public class SelectAppsView extends ListView implements } }); } + + updateCheckedIndicatorView(view, listView.isItemChecked(listPosition)); + } + + public void updateCheckedIndicatorView(int position, boolean checked) { + final int firstListItemPosition = listView.getFirstVisiblePosition(); + final int lastListItemPosition = firstListItemPosition + listView.getChildCount() - 1; + + if (position >= firstListItemPosition && position <= lastListItemPosition ) { + final int childIndex = position - firstListItemPosition; + updateCheckedIndicatorView(listView.getChildAt(childIndex), checked); + } + } + + private void updateCheckedIndicatorView(View view, boolean checked) { + ImageView imageView = (ImageView)view.findViewById(R.id.checked); + if (imageView != null) { + int resource; + int colour; + if (checked) { + resource = R.drawable.ic_check_circle_white; + colour = getResources().getColor(R.color.fdroid_blue); + } else { + resource = R.drawable.ic_add_circle_outline_white; + colour = 0xFFD0D0D4; + } + imageView.setImageDrawable(getResources().getDrawable(resource)); + imageView.setColorFilter(colour, PorterDuff.Mode.MULTIPLY); + } } } diff --git a/F-Droid/tools/download-material-icon.sh b/F-Droid/tools/download-material-icon.sh index fc5ce8639..6bd8e128b 100755 --- a/F-Droid/tools/download-material-icon.sh +++ b/F-Droid/tools/download-material-icon.sh @@ -48,7 +48,7 @@ function download { RES_DIR=$1 CATEGORY=$2 -ICON="${3}_white" +ICON="${3}_black" BASE_URL="https://raw.githubusercontent.com/google/material-design-icons/master" SCREENS="mdpi hdpi xhdpi xxhdpi xxxhdpi" From 4a8ff47fce34779a9017f79a6e1d9b273978ad14 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Thu, 11 Jun 2015 23:35:28 +1000 Subject: [PATCH 33/78] WIP: Implementing mockups from Carrie, added "Toolbar" The Toolbar is the new thing from Google which acts as an ActionBar. It is not a special view like the action bar is, it is implemented and added to your layout the same as any view. The InnerView classes of the swap workflow have the choice of what colour to make the toolbar, so that they can distinguish themselves as per the mockups (some deep blue, others bright blue). Added icons for close, but they don't do anything yet. Minor tweaks to layout so that it looks more like the latest mockups. --- .../drawable-hdpi/ic_arrow_forward_white.png | Bin 0 -> 195 bytes F-Droid/res/drawable-hdpi/ic_close_white.png | Bin 0 -> 634 bytes .../drawable-mdpi/ic_arrow_forward_white.png | Bin 0 -> 160 bytes F-Droid/res/drawable-mdpi/ic_close_white.png | Bin 0 -> 495 bytes .../drawable-xhdpi/ic_arrow_forward_white.png | Bin 0 -> 235 bytes F-Droid/res/drawable-xhdpi/ic_close_white.png | Bin 0 -> 725 bytes .../ic_arrow_forward_white.png | Bin 0 -> 308 bytes .../res/drawable-xxhdpi/ic_close_white.png | Bin 0 -> 983 bytes .../ic_arrow_forward_white.png | Bin 0 -> 372 bytes .../res/drawable-xxxhdpi/ic_close_white.png | Bin 0 -> 1094 bytes .../res/drawable/ic_arrow_forward_white.png | Bin 0 -> 160 bytes F-Droid/res/drawable/ic_close_white.png | Bin 0 -> 495 bytes F-Droid/res/layout/swap_activity.xml | 19 +++++++---- F-Droid/res/layout/swap_wifi_qr.xml | 17 ++++++---- F-Droid/res/menu/swap_next.xml | 1 + F-Droid/res/menu/swap_next_search.xml | 11 ++++--- F-Droid/res/menu/swap_skip.xml | 1 + F-Droid/res/values-v11/styles.xml | 8 +++++ F-Droid/res/values-v21/styles.xml | 4 +++ F-Droid/res/values/colors.xml | 1 + F-Droid/res/values/strings.xml | 13 +++++--- F-Droid/res/values/styles.xml | 27 +++++++++------ .../fdroid/fdroid/localrepo/SwapManager.java | 1 + .../fdroid/views/swap/JoinWifiView.java | 11 +++++++ .../org/fdroid/fdroid/views/swap/NfcView.java | 11 +++++++ .../fdroid/views/swap/SelectAppsView.java | 31 ++++++++++-------- .../fdroid/views/swap/StartSwapView.java | 12 +++++++ .../views/swap/SwapWorkflowActivity.java | 26 +++++++++++++++ .../fdroid/fdroid/views/swap/WifiQrView.java | 11 +++++++ F-Droid/tools/download-material-icon.sh | 2 +- 30 files changed, 160 insertions(+), 47 deletions(-) create mode 100644 F-Droid/res/drawable-hdpi/ic_arrow_forward_white.png create mode 100644 F-Droid/res/drawable-hdpi/ic_close_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_arrow_forward_white.png create mode 100644 F-Droid/res/drawable-mdpi/ic_close_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_arrow_forward_white.png create mode 100644 F-Droid/res/drawable-xhdpi/ic_close_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_arrow_forward_white.png create mode 100644 F-Droid/res/drawable-xxhdpi/ic_close_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_arrow_forward_white.png create mode 100644 F-Droid/res/drawable-xxxhdpi/ic_close_white.png create mode 100644 F-Droid/res/drawable/ic_arrow_forward_white.png create mode 100644 F-Droid/res/drawable/ic_close_white.png create mode 100644 F-Droid/res/values-v11/styles.xml diff --git a/F-Droid/res/drawable-hdpi/ic_arrow_forward_white.png b/F-Droid/res/drawable-hdpi/ic_arrow_forward_white.png new file mode 100644 index 0000000000000000000000000000000000000000..8c4c394e4eb1317612782a2943e4fbee8dc8d7f7 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawx;$MRLo)8Yy?mS3!GXagP&RznYnLV4=JylzIMWb?Jmd6AI z$0im|p(Xi~zQi2lo#Hm>jK_bbnZDCL|N8h%SjNJ04jbQ`lyf}nenQJjAL|s{JG63* ug|%ixVV(;6xz;C#EIs^Wl#~|rtX=Tt#jmu@OkaSmV(@hJb6Mw<&;$V25lZU- literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-hdpi/ic_close_white.png b/F-Droid/res/drawable-hdpi/ic_close_white.png new file mode 100644 index 0000000000000000000000000000000000000000..a7486ae4f78df53730d1ed6c61bdcba3c2a1f56d GIT binary patch literal 634 zcmeAS@N?(olHy`uVBq!ia0vp^W+2SL0wmRZ7KH&RmUKs7M+SzC{oH>NS%G|}ByV>Y zhX3vTXZ8bm>?NMQuI%^OxCONsi%OlD7#JAWdAc};L>zuQ-EmU0f`DsvQCm+1^97ANWH)lMOxvPt zI9EepVz8@gOp7SjA^vp%69g14#25V8-+7|2;gF>2j5O7C8H^fldS#s!_fBnLyYQ%~ zDLu^bpoe49>+RK1wTs){DVNz8Gv2&?_m4q!gK6HiM=a&%S-ypp`OQ#nXr5?e+$6DP zyWHUthBuN1XFA@cEz-^6PRl*~h2c(}l!C6(=f2^E`;2p+0BX@(;<{N{iBVIrGjkJdnnzt-brssxwO&Z(Wi-6Fgt|!h;ST zmY|87bCiy+T`p?4?X_q}+1*C>KYrf!WzUlsZn;}Hf7{1c%lDzQh4smoU9)&Ea|T3- zZ`{mywn;Ki=^@(|1&7ULq8Sc4GZY)vcg}KPPCC%@_Ru`GBXu91So~nPRdnErnswjI z#}i-O<7D?~?Me@OVD$TE?K8XmoT_^3i}>;@;sc%?UzG4R^7hTQJy(ARHKIE4Y~O#nQ4`{HCX>pe-6~Z;OXk;vd$@?2>{!-__hE5 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_arrow_forward_white.png b/F-Droid/res/drawable-mdpi/ic_arrow_forward_white.png new file mode 100644 index 0000000000000000000000000000000000000000..878b6e5e8844faf9723a2460d56d53af7c74babb GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DbWaz@kch)?r&@D0IPkC*>)MM8 z>nw7=#%#lV{gUfNb|vH5v@RAQMh5eXOG;j)du~!Sd39iNz~WXGP9c?V6V;bk7pQ)G zd*F9>SoU7!?fabLFRy=b{)_Fes00K1=^RT#zopr0I2>qsQ>@~ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-mdpi/ic_close_white.png b/F-Droid/res/drawable-mdpi/ic_close_white.png new file mode 100644 index 0000000000000000000000000000000000000000..24b2e4d2d56b7104304fecca08134fd7657c5be5 GIT binary patch literal 495 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8JTOS+@4BLl<6e(pbstUx|flDE4H z!~gdFGy8!&_7YEDSN8jC+=5z+MWxP63=E9Qo-U3d9>?EKb)3|!AmCcPDQW%1-v#qL z&n-IU`sl4qn@&Zfha}H}AmiB~OT#00_y22t7xqMYzPSBs|Devhvda@D3+YbfxHmCe zPH7LCp)e|x*Mm3dRWI*etN#jk9ekjBEZJ>b;IFA@nZ`P`vfBo15%*vN2BTg%FR zx2npNI|c__eivwVJs&K=1$~cf(V^jjBbbE}ZLEiJID(_V?559iKnw-%k7a@$~uy z3nHgy@Lk#U=aA6ZhNx5e*P6?&`)^r(uWG~m+C0vyALnsZZCGq)c4OCmKC`1wFRb`` zS9)uc)AD;q8Lwqrss4Ar&i($6BNa?86Zqy`Y% literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_arrow_forward_white.png b/F-Droid/res/drawable-xhdpi/ic_arrow_forward_white.png new file mode 100644 index 0000000000000000000000000000000000000000..5e93f8862b879d31e51adaf46b3a0ebd2063c5a1 GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%xcgHhH=@hE&{od-WkNqa#C0AnyXt zUlw25*12(Xp7<;H#OGZt@0&d-=DQ<2CaHK{@;^0UVdX!)>rtFHp9d;G%&b#ZY~ORu z<%C)CSAk7u1Q}7m!^6tUvNG6fx+hE#o>atipl5x+Pvv(%&TLxsuluZd{jtyf&t~WE z`}u6Y`Tv}KKR3_WZ+5@t^V#_f8&=M%SLV6;*_|Wzb38}y=lL9KKfiBz_7muWN&j|n Y%N<#k$X1qa1#~@wr>mdKI;Vst0GCi?UH||9 literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xhdpi/ic_close_white.png b/F-Droid/res/drawable-xhdpi/ic_close_white.png new file mode 100644 index 0000000000000000000000000000000000000000..04fa6efe64327637235f13acca643afdd0606867 GIT binary patch literal 725 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY0wn)GsXhawSkfJR9T^xl_H+M9WCik>lDyqr z82-2SpV<%Ov6p!Iy0YJA;}+CndR-&511Qhw>EalYaqsO+>#}A80awwCh!*>s(>yoL zU$XhSfb-M}|Gw>lJ2XG=b0*eKa9a5MoV(n-XGT`Wn*=-|VDhV%7nyRz`~{SayJmDd z@#W+$H>keW?3%lMnaKO3w=?GXoqyz<<5Jf5LT#77-^tE+EwQ`ie1_AmnEVNtbo=-V z_R|`cOcy?6X1~l@zKHdKhv&0ojpx0>;-(>M*6^-falVYfZ_lLN(ponz6-;K`VI8r5 zFGn<&d`kAJd8*C@Tx+8HuAe(GOYcRK|H(;=I~XI%XJ_8wiT%EF!K6!eTz=fASNfG! zPd)fHl8;@!F*Q^mYU_0NH)jv@9{yw@#{OMLdBP?UiD{NQw#`2su%YL`)$Q%B4W=A^ z#{%OIxHg$Gth*KxBNK7#!J~sgMJ6rJ4k&8r9Gx?!B^EtNQK1A4k92%k)vW33ic(~5POucO zZ^(YwdwuH%(}ZKv(chNMB?ajX$PcJZ-6;Xd9o=IO?3^-bPWwd49%@fjI0cdv<(fc3=FD$TaTe= c$jwj5OsmALflI))3aEj>)78&qol`;+076ABr~m)} literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_arrow_forward_white.png b/F-Droid/res/drawable-xxhdpi/ic_arrow_forward_white.png new file mode 100644 index 0000000000000000000000000000000000000000..f8cf79f9dc0f29130bd6e84fd9a7b5f5877a81cc GIT binary patch literal 308 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=R04cLT!Hj|LSPGL?@ORM?vfzC zV1|a8`nuZ2<|ccG1t)s%1BJJEx;TbZ+rvF=^WD~e3x_m~4zY99}!XLOk z>@SCxaOtcSmtE$ny(-mSz}mm~UsPoe_q>&JD}X?)^vT1A4`*I!YzKnpp&vhd_~7$O z?vZ3@M|z0jyuj89H&YwMs~=Q4Peo)Vo>K)mFVdQ&MBb@ E048>ifB*mh literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable-xxhdpi/ic_close_white.png b/F-Droid/res/drawable-xxhdpi/ic_close_white.png new file mode 100644 index 0000000000000000000000000000000000000000..279cce3fea150d11dc715bc7b1340cd0b48aff0b GIT binary patch literal 983 zcmeAS@N?(olHy`uVBq!ia0vp^IUvlz0wh)Q=eq$ZmUKs7M+SzC{oH>NS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^> z;_2(kexHq7P>bnxjnEFD{C!Us$B>F!Z)XP<-7*kp6JH*F@Tg^Db>oaUiQD}q<_nBV zPDFP7w~?^nTC-6nv-8Q#_nl`P!uJ0VaLX3d>{_G&M<+KFRSLGbr~gtk+3@IYVhGsPz@Elk<^_Um0E6m?ZZer$Wf zE*Rhu%{jmLt)S0|{OJ>BK6ZC{R@U6<z7N)NqQC~w(sHm816gT4J^v!AY zadA?+rL1VKF=^t3l%Ny#75*MhlRXtLPnyvA&8o_D(zKSGE!VUn=6dKT-`X-E)8m9o zwaybuNzR!WtoPiT-=-#8emVZ8skJ_hMJMEFtDfB4%SG~UO^;7)t#j1>GAYaHkny)0 z-X&Qr8-JDV;k>`H(r?Yl_~Q$$O%L73zj$IRAM1wuiza5vo;o^f+J&03KThZFtNS>4 zY_C1^qIHhCqV(C_r5X+Q(@JMtTgdQl_SHbHmwJlQx?+llOMMmz8RZ;5vM>1aoQQcs zn=Y=2Zf82NUE)B}7%T!Hj|BEULZFH4|Oo{}KH zV1|bGCLc`A%^I7UrIo%4IRnK%c)B=-RK&f#z0tSXP=vkEJy2*t6>~*P$QegbLndVd zH@5^9ts_kF|K+uUq`X5oN{&u`Csy(6`3($=OmPVZvcZgdi3e7iI{<~;r9NDfR&Z!&$d@oE z6G$+PJfO6M?Me*8lmO;zJqC{zjAr5t$|23izn=KcUNS%G|oWRD45bDP46hOx7_4S6Fo+k-*%fF5lweBoc6VX;-`;;_Kaj^> z;_2(kexHq7P>Xp^wUQnK1GAr}i(^Q|t+#VMn`RpbxK4djqpD1S_ISww5Zz%{0-o5HFJO531l-ll$?xIMS%N!Z*%xfoy zZVBCK!Th#_$>@t~x_jYGm*P`j4f{)(K6PqzXT%kCX6!p;(^kF9v-_Fu^dG`EgIpFZ zVm+}`y5CT0QK+K0d)St&C!5@+#BN#jgFuHV?@YJH?PcWP0j{sG-67niH2 zj-FJHx>^*ecO=?mKleh1on5`hXUHrvRTNiWn{oC;>dqA`i%eC;)q69}o;><7EV+<% zwdYP9@fULcyM4E$J$+PR|NEZ3Yj%{L-<*uIr&BvM9Jc5^dDPN%tutm$!o^<`P76(( zpYiv^(T`$p&Huji(CgctQF}7A^Mr0xd{N;Z*Vo>9=h!YrO*<_VxFTci>7$(jYuR^N zG%l(wSDLQ6sQL5C_7@Aa!xybrYGI%9-&~8W>*o>SkM6s=JNDcci^~6e_Wtn?3tkAH zKh`gG)=fk+Tz}ES(sqBFyq$7iUIdur@7$NEe>WtC;o$2Sue-l@ePr3w#hVJO-!BD#jQ{$xRd+hw$cm}{afGodY8F+UtTzI`N|y(m$uqJeS0rlDff49 z@uH>q^;uq9Z1%RE?U=TH`v%U7pT3EO=|}xLc6XKQxvg7aJWXNNoS18yr|HeQ7w)%8z&CE)ip{S(pG78YyLX7;VqX+vVz-{VLz~~~ zqfe4RL!%Y6}9duFW{cU8!iyfW?Z8KEY= zJHxsUm!0GmOgx;MUbtTOb}3i2tBHshzjog8-(86*p-Z~h|C=$PX8|C8)gM-ce_q>I zCgofM@>EM)BT7;dOH!?pi&B9UgOP!usjh*UuAyOwp}Ccbk(Gh5wxNNQfx%s?z9JM2 bx%nxXX_dG&tVuUJ2eRMO)z4*}Q$iB}igwZY literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_arrow_forward_white.png b/F-Droid/res/drawable/ic_arrow_forward_white.png new file mode 100644 index 0000000000000000000000000000000000000000..878b6e5e8844faf9723a2460d56d53af7c74babb GIT binary patch literal 160 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA0wn)(8}b0DbWaz@kch)?r&@D0IPkC*>)MM8 z>nw7=#%#lV{gUfNb|vH5v@RAQMh5eXOG;j)du~!Sd39iNz~WXGP9c?V6V;bk7pQ)G zd*F9>SoU7!?fabLFRy=b{)_Fes00K1=^RT#zopr0I2>qsQ>@~ literal 0 HcmV?d00001 diff --git a/F-Droid/res/drawable/ic_close_white.png b/F-Droid/res/drawable/ic_close_white.png new file mode 100644 index 0000000000000000000000000000000000000000..24b2e4d2d56b7104304fecca08134fd7657c5be5 GIT binary patch literal 495 zcmeAS@N?(olHy`uVBq!ia0vp^Dj>|k0wldT1B8JTOS+@4BLl<6e(pbstUx|flDE4H z!~gdFGy8!&_7YEDSN8jC+=5z+MWxP63=E9Qo-U3d9>?EKb)3|!AmCcPDQW%1-v#qL z&n-IU`sl4qn@&Zfha}H}AmiB~OT#00_y22t7xqMYzPSBs|Devhvda@D3+YbfxHmCe zPH7LCp)e|x*Mm3dRWI*etN#jk9ekjBEZJ>b;IFA@nZ`P`vfBo15%*vN2BTg%FR zx2npNI|c__eivwVJs&K=1$~cf(V^jjBbbE}ZLEiJID(_V?559iKnw-%k7a@$~uy z3nHgy@Lk#U=aA6ZhNx5e*P6?&`)^r(uWG~m+C0vyALnsZZCGq)c4OCmKC`1wFRb`` zS9)uc)AD;q8Lwqrss4Ar&i($6BNa?86Zqy`Y% literal 0 HcmV?d00001 diff --git a/F-Droid/res/layout/swap_activity.xml b/F-Droid/res/layout/swap_activity.xml index 4f8096856..8349507a5 100644 --- a/F-Droid/res/layout/swap_activity.xml +++ b/F-Droid/res/layout/swap_activity.xml @@ -1,14 +1,21 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> + + + android:layout_height="fill_parent" /> - - - \ No newline at end of file + \ No newline at end of file diff --git a/F-Droid/res/layout/swap_wifi_qr.xml b/F-Droid/res/layout/swap_wifi_qr.xml index da0ba3a3b..38dba512b 100644 --- a/F-Droid/res/layout/swap_wifi_qr.xml +++ b/F-Droid/res/layout/swap_wifi_qr.xml @@ -12,13 +12,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content"> - - + +