diff --git a/README.md b/README.md index c3dc4acea411e2d221a3693b5634cedc3a177baa..3f76a84498e57d510c66a7210ed0e72ab8ffc72a 100644 --- a/README.md +++ b/README.md @@ -1 +1,83 @@ -Briar is a messaging app designed for activists, journalists, and anyone else who needs a safe, easy and robust way to communicate. Unlike traditional messaging tools such as email, Twitter or Telegram, Briar doesn't rely on a central server - messages are synchronized directly between the users' devices. If the Internet's down, Briar can sync via Bluetooth or Wi-Fi, keeping the information flowing in a crisis. If the Internet's up, Briar can sync via the Tor network, protecting users and their relationships from surveillance. +# Briar Mailbox + +This project aims to develop an easy way for Briar users to increase their +reachability and lower the battery drain of their phone. + +In Briar messages are exchanged directly between contacts (peer-to-peer). +This kind of synchronous message exchange requires contacts to be online and +connected to each other. +While this is great for privacy (no central server which +can log things or be censored) it's bad for reachability, especially in +mobile networks where connectivity can be limited. + +```mermaid +graph LR + A[Alice] + B[Bob] + A1[Alice] + B1[Bob] + style B fill:#8db600 + style A1 fill:#8db600 + subgraph Alice offline + B-. can't send message .-> A + end + subgraph Bob offline + B1-. can't send message .-> A1 + end +``` + +Message delivery could be delayed for an arbitrary time (or even indefinitely) +until both Bob and Alice are online at the same time. +The repeater solves this problem by providing +mailbox-like message buffer where contacts can leave messages for the owner +of the repeater and which is connected to a stable internet connection +(e.g. the wifi at home, cable internet) and a power source. + + +```mermaid +graph LR + A[Alice] + A1[Alice] + B[Bob] + B1[Bob] + RA["Mailbox (always online)"] + style B fill:#8db600 + style RA fill:#8db600 + style A1 fill:#8db600 + subgraph Alice offline + B-. can't send message .-> A + end + subgraph Alices' Mailbox + B-- send message --> RA + end + subgraph Alice online + B1-. can't send message .-> A1 + A1-- get message --> RA + end +``` + +## Hardware + +We want the repeater to be as easy to deploy as possible. The first version +will come as Android application since it will be easy to setup and besides a +spare phone no special hardware is required. Once this is done support for +any hardware supporting Java (e.g. unix server, raspberry pi) will be added. + +## Features + +### Core features + +* Allow contacts to store messages for the owner of the repeater +* Allow the owner to store messages for her contacts. Contacts can pick them up + when syncing with the repeater. +* Owner and contacts connect to the repeater via Tor. + +### Extended features/components + +* The repeater can sync group messages (from groups the owner is part of) with + other group members (increases message circulation) +* Contacts and the owner can connect to the repeater via other transports (Bluetooth, Wifi-Direct, Lan) +* Push-like message notification for the owner to decrease battery drain + + + diff --git a/briar-android/src/main/AndroidManifest.xml b/briar-android/src/main/AndroidManifest.xml index f105d57e9706e53679d3ba75d2c8baed390c9497..8a1f069e16eed8357d5fc5e2f572979f9775fb2f 100644 --- a/briar-android/src/main/AndroidManifest.xml +++ b/briar-android/src/main/AndroidManifest.xml @@ -4,17 +4,18 @@ xmlns:android="http://schemas.android.com/apk/res/android"> <uses-feature android:name="android.hardware.bluetooth"/> - <uses-feature android:name="android.hardware.camera" /> - - <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> - <uses-permission android:name="android.permission.BLUETOOTH" /> - <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> - <uses-permission android:name="android.permission.CAMERA" /> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.VIBRATE" /> - <uses-permission android:name="android.permission.WAKE_LOCK" /> + <uses-feature android:name="android.hardware.camera"/> + + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.CAMERA"/> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.VIBRATE"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> + <uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> <application @@ -76,8 +77,8 @@ <activity android:name="org.briarproject.briar.android.splash.SplashScreenActivity" - android:theme="@style/BriarTheme.NoActionBar" - android:label="@string/app_name"> + android:label="@string/app_name" + android:theme="@style/BriarTheme.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -91,15 +92,15 @@ <activity android:name="org.briarproject.briar.android.navdrawer.NavDrawerActivity" - android:theme="@style/BriarTheme.NoActionBar" - android:launchMode="singleTop"> + android:launchMode="singleTop" + android:theme="@style/BriarTheme.NoActionBar"> </activity> <activity android:name="org.briarproject.briar.android.contact.ConversationActivity" android:label="@string/app_name" - android:theme="@style/BriarTheme.NoActionBar" android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity" + android:theme="@style/BriarTheme.NoActionBar" android:windowSoftInputMode="stateHidden|adjustResize"> <meta-data android:name="android.support.PARENT_ACTIVITY" @@ -139,7 +140,7 @@ android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/> </activity> - <activity + <activity android:name="org.briarproject.briar.android.privategroup.memberlist.GroupMemberListActivity" android:label="@string/groups_member_list" android:parentActivityName="org.briarproject.briar.android.privategroup.conversation.GroupActivity" @@ -311,13 +312,34 @@ <activity android:name="org.briarproject.briar.android.keyagreement.ContactExchangeActivity" android:label="@string/add_contact_title" - android:theme="@style/BriarTheme.NoActionBar" - android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity"> + android:parentActivityName="org.briarproject.briar.android.navdrawer.NavDrawerActivity" + android:theme="@style/BriarTheme.NoActionBar"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="org.briarproject.briar.android.navdrawer.NavDrawerActivity"/> </activity> + <activity + android:name="org.briarproject.briar.android.keyagreement.MailboxExchangeActivity" + android:label="@string/mailbox_add" + android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity" + android:theme="@style/BriarTheme.NoActionBar"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="org.briarproject.briar.android.settings.SettingsActivity"/> + </activity> + + + <activity + android:name="org.briarproject.briar.android.repeater.RepeaterActivity" + android:label="@string/mailbox" + android:parentActivityName="org.briarproject.briar.android.settings.SettingsActivity" + android:theme="@style/BriarTheme.NoActionBar"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="org.briarproject.briar.android.settings.SettingsActivity"/> + </activity> + <activity android:name="org.briarproject.briar.android.introduction.IntroductionActivity" android:label="@string/introduction_activity_title" diff --git a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java index 778a6f2b95c6454adaf4ddad3d174eb5239f13cb..9c140a0217b8773389573974efd94632f1c7eddd 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/AndroidComponent.java @@ -7,6 +7,7 @@ import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.api.contact.ContactExchangeTask; import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.MailboxExchangeTask; import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoExecutor; import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; @@ -129,6 +130,8 @@ public interface AndroidComponent ContactExchangeTask contactExchangeTask(); + MailboxExchangeTask mailboxExchangeTask(); + KeyAgreementTask keyAgreementTask(); PayloadEncoder payloadEncoder(); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java index 1d92a31f30348af7b9bf73f3d25e6cfd1469af7e..ebd1cbffa350db449396902b4692f026383493b7 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/activity/ActivityComponent.java @@ -30,6 +30,7 @@ import org.briarproject.briar.android.keyagreement.ContactExchangeActivity; import org.briarproject.briar.android.keyagreement.IntroFragment; import org.briarproject.briar.android.keyagreement.KeyAgreementActivity; import org.briarproject.briar.android.keyagreement.KeyAgreementFragment; +import org.briarproject.briar.android.keyagreement.MailboxExchangeActivity; import org.briarproject.briar.android.login.AuthorNameFragment; import org.briarproject.briar.android.login.ChangePasswordActivity; import org.briarproject.briar.android.login.DozeFragment; @@ -57,6 +58,7 @@ import org.briarproject.briar.android.privategroup.memberlist.GroupMemberModule; import org.briarproject.briar.android.privategroup.reveal.GroupRevealModule; import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity; import org.briarproject.briar.android.privategroup.reveal.RevealContactsFragment; +import org.briarproject.briar.android.repeater.RepeaterActivity; import org.briarproject.briar.android.settings.SettingsActivity; import org.briarproject.briar.android.settings.SettingsFragment; import org.briarproject.briar.android.sharing.BlogInvitationActivity; @@ -103,10 +105,14 @@ public interface ActivityComponent { void inject(ContactExchangeActivity activity); + void inject(MailboxExchangeActivity activity); + void inject(KeyAgreementActivity activity); void inject(ConversationActivity activity); + void inject(RepeaterActivity activity); + void inject(ForumInvitationActivity activity); void inject(BlogInvitationActivity activity); diff --git a/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/MailboxExchangeActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/MailboxExchangeActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f2764266a174baaad1a287fb55a9485e775012c7 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/keyagreement/MailboxExchangeActivity.java @@ -0,0 +1,138 @@ +package org.briarproject.briar.android.keyagreement; + +import android.os.Bundle; +import android.support.annotation.UiThread; +import android.widget.Toast; + +import org.briarproject.bramble.api.contact.ContactExchangeListener; +import org.briarproject.bramble.api.contact.MailboxExchangeTask; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.widget.Toast.LENGTH_LONG; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.contact.ContactTypes.MAILBOX_OWNER; +import static org.briarproject.bramble.api.contact.ContactTypes.PRIVATE_MAILBOX; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.briar.R.string; + +public class MailboxExchangeActivity extends KeyAgreementActivity implements + ContactExchangeListener { + + private static final Logger LOG = + Logger.getLogger(MailboxExchangeActivity.class.getName()); + + @Inject + volatile MailboxExchangeTask mailboxExchangeTask; + @Inject + volatile IdentityManager identityManager; + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + getSupportActionBar().setTitle(string.mailbox_add); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + protected void startContactExchange(KeyAgreementResult result) { + runOnDbThread(() -> { + LocalAuthor localAuthor; + // Load the local pseudonym + try { + localAuthor = identityManager.getLocalAuthor(); + } catch (DbException e) { + logException(LOG, WARNING, e); + contactExchangeFailed(); + return; + } + + // Exchange contact details + mailboxExchangeTask.startExchange(MailboxExchangeActivity.this, + localAuthor, result.getMasterKey(), + result.getConnection(), result.getTransportId(), + result.wasAlice(), MAILBOX_OWNER, PRIVATE_MAILBOX); + }); + } + + @Override + public void contactExchangeSucceeded(Author remoteAuthor) { + runOnUiThreadUnlessDestroyed(() -> { + Toast.makeText(MailboxExchangeActivity.this, + string.mailbox_paired, LENGTH_LONG) + .show(); + supportFinishAfterTransition(); + }); + } + + @Override + public void duplicateContact(Author remoteAuthor) { + runOnUiThreadUnlessDestroyed(() -> { + Toast.makeText(MailboxExchangeActivity.this, + string.mailbox_already_paired, LENGTH_LONG) + .show(); + finish(); + }); + } + + @Override + public void contactExchangeFailed() { + runOnUiThreadUnlessDestroyed(() -> { + Toast.makeText(MailboxExchangeActivity.this, + string.contact_exchange_failed, LENGTH_LONG).show(); + finish(); + }); + } + + @UiThread + @Override + public void keyAgreementFailed() { + // TODO show failure somewhere persistent? + Toast.makeText(this, R.string.connection_failed, + LENGTH_LONG).show(); + } + + @UiThread + @Override + public String keyAgreementWaiting() { + return getString(R.string.waiting_for_contact_to_scan); + } + + @UiThread + @Override + public String keyAgreementStarted() { + return getString(R.string.authenticating_with_device); + } + + @UiThread + @Override + public String keyAgreementAborted(boolean remoteAborted) { + // TODO show abort somewhere persistent? + Toast.makeText(this, + remoteAborted ? R.string.connection_aborted_remote : + R.string.connection_aborted_local, LENGTH_LONG) + .show(); + return null; + } + + @UiThread + @Override + public String keyAgreementFinished(KeyAgreementResult result) { + startContactExchange(result); + return getString(string.exchanging_contact_details); + } + +} diff --git a/briar-android/src/main/java/org/briarproject/briar/android/repeater/RepeaterActivity.java b/briar-android/src/main/java/org/briarproject/briar/android/repeater/RepeaterActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..1f878c8808406e5c6ef90f0e45562e178abcaa97 --- /dev/null +++ b/briar-android/src/main/java/org/briarproject/briar/android/repeater/RepeaterActivity.java @@ -0,0 +1,97 @@ +package org.briarproject.briar.android.repeater; + + +import android.os.Bundle; +import android.support.v7.widget.Toolbar; +import android.widget.TextView; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.briar.R; +import org.briarproject.briar.android.activity.ActivityComponent; +import org.briarproject.briar.android.activity.BriarActivity; + +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import de.hdodenhof.circleimageview.CircleImageView; + +import static android.support.v4.view.ViewCompat.setTransitionName; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.contact.ContactTypes.PRIVATE_MAILBOX; +import static org.briarproject.bramble.util.LogUtils.logException; + +public class RepeaterActivity extends BriarActivity { + + private static final Logger LOG = + Logger.getLogger(RepeaterActivity.class.getName()); + @Inject + @DatabaseExecutor + Executor databaseExecutor; + @Inject + ContactManager contactManager; + + private Toolbar toolbar; + private CircleImageView toolbarAvatar; + private TextView toolbarTitle; + + @SuppressWarnings("ConstantConditions") + @Override + public void onCreate(@Nullable Bundle state) { + setSceneTransitionAnimation(); + super.onCreate(state); + + setContentView(R.layout.activity_repeater); + // Custom Toolbar + toolbar = setUpCustomToolbar(true); + if (toolbar != null) { + toolbarAvatar = toolbar.findViewById(R.id.contactAvatar); + toolbarTitle = toolbar.findViewById(R.id.repeaterName); + } + + setTransitionName(toolbarAvatar, "avatarRepeater"); + } + + @Override + public void onStart() { + super.onStart(); + loadRepeater(); + } + + private void loadRepeater() { + databaseExecutor.execute(() -> { + try { + Collection<Contact> repeaters = + contactManager.getContactsByType(PRIVATE_MAILBOX); + if (repeaters.size() > 1) + throw new IllegalStateException( + "More than one repeater found"); + if (repeaters.isEmpty()) + displayRepeater(null); + displayRepeater(repeaters.iterator().next()); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void displayRepeater(@Nullable Contact repeater) { + runOnUiThreadUnlessDestroyed(() -> { + // TODO: Repeater Image + toolbarAvatar.setImageResource(R.mipmap.ic_launcher_round); + if (repeater != null) + toolbarTitle.setText(repeater.getAuthor().getName()); + }); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } +} diff --git a/briar-android/src/main/res/layout/activity_repeater.xml b/briar-android/src/main/res/layout/activity_repeater.xml new file mode 100644 index 0000000000000000000000000000000000000000..a7fd5a60f186f345fb767e82faf947ee00da5c43 --- /dev/null +++ b/briar-android/src/main/res/layout/activity_repeater.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".android.repeater.RepeaterActivity"> + + <android.support.design.widget.AppBarLayout + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + style="@style/BriarToolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="match_parent"> + + <include layout="@layout/repeater_avatar_status"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiTextView + android:id="@+id/repeaterName" + style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse" + android:textColor="@color/action_bar_text" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginLeft="@dimen/margin_medium" + android:layout_marginStart="@dimen/margin_medium" + android:gravity="center" + tools:text="Contact Name"/> + + </LinearLayout> + + </android.support.v7.widget.Toolbar> + + </android.support.design.widget.AppBarLayout> +</LinearLayout> \ No newline at end of file diff --git a/briar-android/src/main/res/layout/repeater_avatar_status.xml b/briar-android/src/main/res/layout/repeater_avatar_status.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d47e786e6451d0231a944b4e81da7827fb220c2 --- /dev/null +++ b/briar-android/src/main/res/layout/repeater_avatar_status.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="32dp" + android:layout_height="32dp" + tools:showIn="@layout/activity_conversation"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/contactAvatar" + style="@style/BriarAvatar" + android:layout_width="30dp" + android:layout_height="30dp" + app:civ_border_color="@color/action_bar_text" + tools:src="@mipmap/ic_launcher_round"/> + + <ImageView + android:id="@+id/contactStatus" + android:layout_width="15dp" + android:layout_height="15dp" + android:layout_gravity="bottom|right" + android:scaleType="fitCenter" + tools:ignore="ContentDescription" + tools:src="@drawable/contact_online"/> + +</FrameLayout> \ No newline at end of file diff --git a/briar-android/src/main/res/values/strings.xml b/briar-android/src/main/res/values/strings.xml index 194856e898b19f95da1dc1402b499f1623f28fd9..352eb1caf6e173730d110ca586bacaef928d616d 100644 --- a/briar-android/src/main/res/values/strings.xml +++ b/briar-android/src/main/res/values/strings.xml @@ -436,4 +436,11 @@ <string name="permission_camera_denied_toast">Camera permission was not granted</string> <string name="qr_code">QR code</string> <string name="show_qr_code_fullscreen">Show QR code fullscreen</string> + + <!-- Mailbox --> + <string name="mailbox">Repeater</string> + <string name="mailbox_add">Add Mailbox</string> + <string name="mailbox_paired">Successfully paired with mailbox</string> + <string name="mailbox_already_paired">Already paired with this mailbox</string> + <string name="mailbox_info">Show mailbox</string> </resources> diff --git a/briar-android/src/main/res/xml/settings.xml b/briar-android/src/main/res/xml/settings.xml index c74c7e3c3ec69ab980320ec5ce5a0ceacbb8ac1e..1b3f9fdb907538bbc12502db6408084aea90f802 100644 --- a/briar-android/src/main/res/xml/settings.xml +++ b/briar-android/src/main/res/xml/settings.xml @@ -19,7 +19,7 @@ android:key="pref_key_theme" android:summary="%s" android:title="@string/pref_theme_title"/> - + </PreferenceCategory> <PreferenceCategory @@ -129,6 +129,28 @@ </PreferenceCategory> + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="@string/mailbox"> + + <Preference + android:key="pref_key_repeater" + android:title="@string/mailbox_add"> + + <intent + android:targetClass="org.briarproject.briar.android.keyagreement.MailboxExchangeActivity" + android:targetPackage="@string/app_package"/> + </Preference> + <Preference + android:key="pref_key_repeater_list" + android:title="@string/mailbox_info"> + + <intent + android:targetClass="org.briarproject.briar.android.repeater.RepeaterActivity" + android:targetPackage="@string/app_package"/> + </Preference> + </PreferenceCategory> + <PreferenceCategory android:layout="@layout/preferences_category" android:title="@string/feedback_settings_title"/> diff --git a/mailbox-android/.gitignore b/mailbox-android/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..4d702fcd882eb5ddecacedeb92bfddcb6608ca0d --- /dev/null +++ b/mailbox-android/.gitignore @@ -0,0 +1,7 @@ +bin +gen +build +local.properties +.settings +src/main/assets/*.zip +src/main/res/values-iw diff --git a/mailbox-android/.tx/config b/mailbox-android/.tx/config new file mode 100644 index 0000000000000000000000000000000000000000..67e77d7d4977c12cf0cbfad979874c527b8091e4 --- /dev/null +++ b/mailbox-android/.tx/config @@ -0,0 +1,11 @@ +[main] +host = https://www.transifex.com +lang_map = pt_BR: pt-rBR, nb_NO: nb, zh-Hans: zh-rCN + +[briar.stringsxml-5] +file_filter = src/main/res/values-<lang>/strings.xml +source_file = src/main/res/values/strings.xml +source_lang = en +type = ANDROID +minimum_perc = 25 + diff --git a/mailbox-android/artwork/bluetooth.svg b/mailbox-android/artwork/bluetooth.svg new file mode 100644 index 0000000000000000000000000000000000000000..ed248d1daa4b58ecdfe2738cdff02cdb0be05188 --- /dev/null +++ b/mailbox-android/artwork/bluetooth.svg @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + width="499.24374" + height="175.49413" + viewBox="0 0 499.24373 175.49413" + id="svg2" + version="1.1"> + <defs + id="defs4" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <path + id="path4201" + d="m 459.80937,171.15983 -4.32657,-4.3343 -7.06956,-2.42737 c -3.88825,-1.33505 -8.72285,-2.99587 -10.74354,-3.6907 l -3.674,-1.26333 3.524,-0.1726 c 8.24183,-0.40367 12.98778,-4.00671 14.33634,-10.88389 0.79628,-4.06078 1.12887,-17.29805 0.83016,-33.04122 -0.15338,-8.08375 -0.16617,-15.41641 -0.0284,-16.29481 0.13775,-0.8784 0.53527,-2.42011 0.88339,-3.42602 1.22247,-3.53243 0.33996,-11.90828 -1.8577,-17.63146 -0.34848,-0.9075 -1.93434,-4.215 -3.52415,-7.35 -4.15849,-8.2003 -4.50458,-8.94188 -4.89292,-10.4841 -0.45865,-1.82145 -0.21161,-5.43531 0.4625,-6.7659 0.66625,-1.31505 2.15695,-2.91616 3.24622,-3.48662 1.29885,-0.68024 2.61568,0.12202 4.6864,2.85512 3.42153,4.51599 14.00135,19.44095 15.73191,22.19301 3.74551,5.95636 5.95558,11.16496 7.9722,18.78849 0.6547,2.475 1.88525,6.9975 2.73456,10.05 0.84931,3.0525 2.71404,10.15792 4.14385,15.78983 l 2.59964,10.23983 4.65,5.19319 c 2.5575,2.85626 5.7975,6.46371 7.2,8.01657 1.4025,1.55286 2.55,2.97543 2.55,3.16127 0,0.33546 -34.49955,29.29931 -34.89913,29.29931 -0.11475,0 -2.15559,-1.95044 -4.5352,-4.3343 z M 365.9443,154.77206 c -1.25762,-0.62844 -2.20557,-1.3788 -2.91402,-2.30663 -2.08931,-2.73629 -1.95034,2.36868 -1.86433,-68.48249 l 0.0777,-64.03881 0.66066,-1.23494 c 1.0152,-1.89767 1.99201,-2.91087 3.73952,-3.87887 l 1.59982,-0.88619 37.78387,-0.0796 c 42.45592,-0.0894 39.40239,-0.2483 42.11646,2.19188 0.87544,0.78709 1.75715,1.95946 2.18393,2.90385 0.71264,1.57698 0.71613,1.63839 0.80561,14.20405 l 0.0899,12.62022 -1.79817,-0.13007 c -1.42577,-0.10313 -2.08143,0.007 -3.16601,0.5321 -2.01294,0.97445 -3.93993,2.89871 -5.11476,5.10753 l -1.03717,1.95 -0.007,-12.825 -0.007,-12.825 -33.6,0 -33.6,0 0,51.3 0,51.3 33.59873,0 33.59874,0 0.0763,-34.425 c 0.073,-32.96021 0.0982,-34.36117 0.59098,-32.925 0.28309,0.825 1.80562,3.9975 3.3834,7.05 5.49252,10.62624 5.40494,9.86009 5.39597,47.20335 -0.007,27.62122 -0.12358,29.95084 -1.66204,33.10906 -1.07144,2.19949 -2.71143,3.71042 -5.05823,4.66019 l -1.67381,0.6774 -36.1677,0.0797 -36.16769,0.0797 -1.864,-0.93145 z m 42.39939,-5.03813 c 2.87119,-1.30885 4.45771,-3.6784 4.43003,-6.61652 -0.0388,-4.11587 -3.1088,-7.22328 -7.1364,-7.22328 -2.11956,0 -3.56727,0.60889 -5.16364,2.17177 -2.24518,2.19807 -2.75398,5.43897 -1.30101,8.28704 0.71312,1.39782 2.52137,3.00905 3.96214,3.53045 1.49707,0.54176 3.84003,0.47454 5.20888,-0.14946 z" + style="fill:#000000" /> + <path + id="path4201-1" + d="m 39.434334,171.15983 4.32657,-4.3343 7.06956,-2.42737 c 3.88825,-1.33505 8.72285,-2.99587 10.74354,-3.6907 l 3.674,-1.26333 -3.524,-0.1726 c -8.24183,-0.40367 -12.98778,-4.00671 -14.33634,-10.88389 -0.79628,-4.06078 -1.12887,-17.29805 -0.83016,-33.04122 0.15338,-8.08375 0.16617,-15.41641 0.0284,-16.29481 -0.13775,-0.8784 -0.53527,-2.42011 -0.88339,-3.42602 -1.22247,-3.53243 -0.33996,-11.90828 1.8577,-17.63146 0.34848,-0.9075 1.93434,-4.215 3.52415,-7.35 4.15849,-8.2003 4.50458,-8.94188 4.89292,-10.4841 0.45865,-1.82145 0.21161,-5.43531 -0.4625,-6.7659 -0.66625,-1.31505 -2.15695,-2.91616 -3.24622,-3.48662 -1.29885,-0.68024 -2.61568,0.12202 -4.6864,2.85512 -3.42153,4.51599 -14.00135,19.44095 -15.73191,22.19301 -3.74551,5.95636 -5.955584,11.16496 -7.972204,18.78849 -0.6547,2.475 -1.88525,6.9975 -2.73456,10.05 -0.84931,3.0525 -2.71404,10.15792 -4.14385,15.78983 L 14.4,129.82379 9.75,135.01698 c -2.5575,2.85626 -5.7975,6.46371 -7.2,8.01657 -1.4025,1.55286 -2.55,2.97543 -2.55,3.16127 0,0.33546 34.499554,29.29931 34.899134,29.29931 0.11475,0 2.15559,-1.95044 4.53519,-4.3343 z m 93.865056,-16.38777 c 1.25762,-0.62844 2.20557,-1.3788 2.91402,-2.30663 2.08931,-2.73629 1.95034,2.36868 1.86433,-68.48249 l -0.0777,-64.03881 -0.66066,-1.23494 c -1.0152,-1.89767 -1.99201,-2.91087 -3.73952,-3.87887 l -1.59982,-0.88619 -37.783856,-0.0796 c -42.45592,-0.0894 -39.40239,-0.2483 -42.11646,2.19188 -0.87544,0.78709 -1.75715,1.95946 -2.18393,2.90385 -0.71264,1.57698 -0.71613,1.63839 -0.80561,14.20405 l -0.0899,12.62022 1.79817,-0.13007 c 1.42577,-0.10313 2.08143,0.007 3.16601,0.5321 2.01294,0.97445 3.93993,2.89871 5.11476,5.10753 l 1.03717,1.95 0.007,-12.825 0.007,-12.825 33.6,0 33.599986,0 0,51.3 0,51.3 -33.598716,0 -33.59874,0 -0.0763,-34.425 c -0.073,-32.96021 -0.0982,-34.36117 -0.59098,-32.925 -0.28309,0.825 -1.80562,3.9975 -3.3834,7.05 -5.49252,10.62624 -5.40494,9.86009 -5.39597,47.20335 0.007,27.62122 0.12358,29.95084 1.66204,33.10906 1.07144,2.19949 2.71143,3.71042 5.05823,4.66019 l 1.67381,0.6774 36.1677,0.0797 36.167676,0.0797 1.864,-0.93145 z m -42.399376,-5.03813 c -2.87119,-1.30885 -4.45771,-3.6784 -4.43003,-6.61652 0.0388,-4.11587 3.1088,-7.22328 7.1364,-7.22328 2.11956,0 3.56727,0.60889 5.16364,2.17177 2.245176,2.19807 2.753976,5.43897 1.301006,8.28704 -0.713116,1.39782 -2.521366,3.00905 -3.962136,3.53045 -1.49707,0.54176 -3.84003,0.47454 -5.20888,-0.14946 z" + style="fill:#000000" /> + <path + id="rect4270" + d="m 247.25369,75.97921 4.73637,0 c 13.16497,0 23.76348,10.598514 23.76348,23.763485 l 0,32.174615 c 0,13.16497 -10.59851,23.76348 -23.76348,23.76348 l -4.73637,0 c -13.16497,0 -23.76349,-10.59851 -23.76349,-23.76348 l 0,-32.174615 c 0,-13.164971 10.59852,-23.763485 23.76349,-23.763485 z" + style="fill:#0a3d91" /> + <path + id="path4272" + d="m 236.31105,102.92749 24.90674,25.07007 -12.00423,14.53574 0,-51.936691 12.00423,13.882451 -24.90674,24.41678" + style="fill:none;stroke:#ffffff;stroke-width:4.32805729" /> + <path + id="path4844" + d="m 143.67921,27.5571 c -1.59043,2.623831 0.18153,5.574737 2.78461,6.642714 2.69504,1.572445 6.1706,4.987176 9.14793,2.232151 2.23313,-2.039515 0.60129,-5.727894 -2.04109,-6.67035 -3.06433,-1.676733 -6.55172,-5.514807 -9.89145,-2.204515 z M 351.7046,27.12937 c -2.76779,1.960623 -7.06819,2.694997 -8.37782,6.042717 -0.64195,2.73095 1.93572,4.99255 4.58419,4.426453 3.2021,-1.637914 7.05658,-2.8424 9.17849,-5.94769 0.56931,-2.60435 -1.49324,-5.35066 -4.26769,-4.745697 -0.41558,-0.107679 -0.76003,0.03505 -1.11717,0.224217 z m -26.76172,13.17187 c -2.93431,1.708015 -7.32039,1.922759 -8.93076,5.153004 -0.90775,2.654328 1.43202,5.159236 4.12365,4.861044 3.35587,-1.304271 7.31954,-2.112696 9.73662,-5.002268 0.81368,-2.537666 -0.97658,-5.469583 -3.79644,-5.128908 -0.4035,-0.14574 -0.75969,-0.03824 -1.13307,0.117128 z m -155.23633,2.46289 c -1.21948,2.711869 0.75683,5.460507 3.4437,6.14538 2.85847,1.080772 6.66937,4.11785 9.31224,1.23392 2.3151,-2.03652 0.33929,-5.835383 -2.40983,-6.41823 -3.32833,-1.175814 -7.38479,-4.713633 -10.34611,-0.96107 z m 127.27344,7.61719 c -3.12289,1.309174 -7.49226,0.970678 -9.49244,3.970097 -1.23184,2.518748 0.7727,5.298264 3.48037,5.339749 3.48706,-0.907308 7.56742,-1.1128 10.30314,-3.739116 1.11397,-2.422222 -0.31199,-5.546068 -3.1516,-5.551016 -0.3827,-0.195662 -0.75023,-0.126964 -1.13947,-0.01971 z m -97.49023,1.54492 c -2.22858,1.461838 -2.68005,4.865948 -0.27553,6.376909 2.5869,1.470583 5.91459,1.954941 8.83971,2.495346 2.78485,0.332188 4.74603,-2.479173 3.89118,-5.048668 -2.04264,-3.279676 -6.47206,-2.73364 -9.71704,-4.089227 -0.91277,0.08855 -1.82555,0.177093 -2.73832,0.26564 z m 68.51367,4.50782 c -3.27623,0.788856 -7.46081,-0.187875 -9.93635,2.381992 -1.64972,2.268076 -0.15709,5.353662 2.50432,5.86185 3.61172,-0.292365 7.71764,0.09072 10.84009,-2.093962 1.46245,-2.227649 0.52336,-5.531725 -2.28478,-5.959415 -0.34949,-0.249225 -0.72211,-0.239347 -1.12328,-0.190465 z m -39.44922,1.41796 c -1.93309,1.83595 -1.76537,5.266611 0.87081,6.321491 2.80857,0.988807 6.17065,0.846733 9.1444,0.861366 2.79909,-0.172766 4.22635,-3.289835 2.92376,-5.664738 -2.60365,-2.851635 -6.84699,-1.541752 -10.29251,-2.269899 -0.88215,0.250593 -1.76431,0.501187 -2.64646,0.75178 z" + style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;direction:ltr;block-progression:tb;writing-mode:lr-tb;baseline-shift:baseline;text-anchor:start;white-space:normal;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;fill:#0a3d91;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:7.55000019;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:7.55, 22.65;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate" /> +</svg> diff --git a/mailbox-android/artwork/ic_emoji_emoticons.svg b/mailbox-android/artwork/ic_emoji_emoticons.svg new file mode 100644 index 0000000000000000000000000000000000000000..7ef60e16b09175f557649d9639d94107f41b7d78 --- /dev/null +++ b/mailbox-android/artwork/ic_emoji_emoticons.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + width="24" + height="24" + viewBox="0 0 24 24" + sodipodi:docname="ic_emoji_emoticons.svg"> + <metadata + id="metadata8"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <defs + id="defs6" /> + <sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1021" + id="namedview4" + showgrid="false" + inkscape:zoom="4.6354778" + inkscape:cx="47.926788" + inkscape:cy="24.127496" + inkscape:window-x="1440" + inkscape:window-y="23" + inkscape:window-maximized="0" + inkscape:current-layer="svg2" /> + <path + style="fill:#000000;fill-opacity:1" + d="m 15.483903,3.8556996 c -0.661546,0.040406 -0.536253,1.2125273 -0.08054,1.6240791 1.361771,1.4519837 1.747379,3.5080793 1.895646,5.4253553 0.109142,2.216286 -0.0846,4.555699 -1.171466,6.533591 -0.361828,0.731167 -1.339597,1.273078 -1.15283,2.195835 0.287109,1.037426 1.187031,0.242862 1.620751,-0.183708 1.991711,-1.742024 2.867744,-4.428018 2.93133,-7.013492 0.02009,-1.918049 -0.231841,-3.9213035 -1.212735,-5.6044037 -0.664187,-1.0906817 -1.39072,-2.2339438 -2.497355,-2.9193489 -0.127976,-0.045915 -0.238296,-0.06368 -0.332802,-0.057908 z M 5.9118212,7.6583077 A 1.3631614,1.3631614 0 0 0 4.54866,9.0214691 1.3631614,1.3631614 0 0 0 5.9118212,10.38463 1.3631614,1.3631614 0 0 0 7.2749824,9.0214691 1.3631614,1.3631614 0 0 0 5.9118212,7.6583077 Z m 3.0731032,3.0012183 0,2.044742 4.7710646,0 0,-2.044742 -4.7710646,0 z m -3.1496485,3.471136 a 1.3631614,1.3631614 0 0 0 -1.3631612,1.363161 1.3631614,1.3631614 0 0 0 1.3631612,1.363161 1.3631614,1.3631614 0 0 0 1.3631612,-1.363161 1.3631614,1.3631614 0 0 0 -1.3631612,-1.363161 z" + id="path4142" + inkscape:connector-curvature="0" /> +</svg> diff --git a/mailbox-android/artwork/logo_circle.svg b/mailbox-android/artwork/logo_circle.svg new file mode 100644 index 0000000000000000000000000000000000000000..3acaa5ea9506680685a7d2073053d2b63f4245e7 --- /dev/null +++ b/mailbox-android/artwork/logo_circle.svg @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_1" + x="0px" + y="0px" + viewBox="0 0 330.00001 330.00001" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="logo_circle.svg" + width="330" + height="330"><metadata + id="metadata61"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs59" /><sodipodi:namedview + pagecolor="#000000" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1021" + id="namedview57" + showgrid="false" + inkscape:zoom="1.4333435" + inkscape:cx="137.64067" + inkscape:cy="223.06028" + inkscape:window-x="1440" + inkscape:window-y="23" + inkscape:window-maximized="0" + inkscape:current-layer="Ebene_1" /><style + type="text/css" + id="style3"> + .st0{fill:#FFFFFF;} + .st1{display:none;fill:#87C214;} + .st2{fill:#87C214;} + .st3{display:none;fill:#FFFFFF;} + .st4{fill:#95D220;} + .st5{display:none;fill:#95D220;} +</style><circle + style="fill:#ffffff" + id="circle7" + cy="165" + cx="165" + class="st0" + r="165" /><g + id="g4214" + transform="translate(0.2999939,1.2000061)"><g + id="g9"><g + id="g11"><rect + x="94" + y="93.800003" + class="st1" + width="43.700001" + height="43.700001" + id="rect13" + style="display:none;fill:#87c214" /><path + class="st2" + d="M 94,144.5 94,264 c 0,9.7 7.9,17.7 17.7,17.7 l 8.3,0 c 9.7,0 17.7,-8 17.7,-17.7 l 0,-119.5 -43.7,0 z" + id="path15" + inkscape:connector-curvature="0" + style="fill:#87c214" /><path + class="st2" + d="m 137.7,86.8 0,-22.5 c 0,-9.7 -8,-17.7 -17.7,-17.7 l -8.3,0 C 102,46.6 94,54.6 94,64.3 l 0,22.5 43.7,0 z" + id="path17" + inkscape:connector-curvature="0" + style="fill:#87c214" /></g><path + class="st3" + d="m 120,46.7 c 9.7,0 17.7,8 17.7,17.7 l 0,199.6 c 0,9.7 -8,17.7 -17.7,17.7 l -8.3,0 C 102,281.7 94,273.7 94,264 L 94,64.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 8.3,0 m 0,-6.9 -8.3,0 C 98.1,39.7 87,50.7 87,64.3 L 87,264 c 0,13.6 11.1,24.7 24.7,24.7 l 8.3,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-199.7 C 144.7,50.7 133.6,39.7 120,39.7 l 0,0 z" + id="path19" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g><g + id="g21"><g + id="g23"><path + class="st2" + d="m 234.7,183.8 0,-119.5 c 0,-9.7 -7.9,-17.7 -17.7,-17.7 l -8.3,0 c -9.7,0 -17.7,8 -17.7,17.7 l 0,119.5 43.7,0 z" + id="path25" + inkscape:connector-curvature="0" + style="fill:#87c214" /><rect + x="191" + y="190.8" + class="st1" + width="43.700001" + height="43.700001" + id="rect27" + style="display:none;fill:#87c214" /><path + class="st2" + d="m 191,241.5 0,22.5 c 0,9.7 8,17.7 17.7,17.7 l 8.3,0 c 9.7,0 17.7,-8 17.7,-17.7 l 0,-22.5 -43.7,0 z" + id="path29" + inkscape:connector-curvature="0" + style="fill:#87c214" /></g><path + class="st3" + d="m 217,46.7 c 9.7,0 17.7,8 17.7,17.7 l 0,199.6 c 0,9.7 -7.9,17.7 -17.7,17.7 l -8.3,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-199.7 c 0,-9.7 8,-17.7 17.7,-17.7 l 8.3,0 m 0,-6.9 -8.3,0 C 195.1,39.7 184,50.8 184,64.4 l 0,199.6 c 0,13.6 11.1,24.7 24.7,24.7 l 8.3,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-199.7 C 241.7,50.7 230.6,39.7 217,39.7 l 0,0 z" + id="path31" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g><g + id="g33"><g + id="g35"><path + class="st4" + d="m 87,190.8 -22.5,0 c -9.7,0 -17.7,7.9 -17.7,17.7 l 0,8.3 c 0,9.7 7.9,17.7 17.7,17.7 l 22.5,0 0,-43.7 z" + id="path37" + inkscape:connector-curvature="0" + style="fill:#95d220" /><rect + x="94" + y="190.8" + class="st5" + width="43.700001" + height="43.700001" + id="rect39" + style="display:none;fill:#95d220" /><path + class="st4" + d="m 264.2,190.8 -119.5,0 0,43.7 119.5,0 c 9.7,0 17.7,-8 17.7,-17.7 l 0,-8.3 c -0.1,-9.7 -8,-17.7 -17.7,-17.7 z" + id="path41" + inkscape:connector-curvature="0" + style="fill:#95d220" /></g><path + class="st3" + d="m 264.2,190.8 c 9.7,0 17.7,7.9 17.7,17.7 l 0,8.3 c 0,9.7 -8,17.7 -17.7,17.7 l -199.7,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 199.7,0 m 0,-7 -199.7,0 c -13.6,0 -24.7,11.1 -24.7,24.7 l 0,8.3 c 0,13.6 11.1,24.7 24.7,24.7 l 199.7,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-8.3 c -0.1,-13.6 -11.1,-24.7 -24.7,-24.7 l 0,0 z" + id="path43" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g><g + id="g45"><g + id="g47"><rect + x="191" + y="93.800003" + class="st5" + width="43.700001" + height="43.700001" + id="rect49" + style="display:none;fill:#95d220" /><path + class="st4" + d="m 184,93.8 -119.5,0 c -9.7,0 -17.7,7.9 -17.7,17.7 l 0,8.3 c 0,9.7 7.9,17.7 17.7,17.7 l 119.5,0 0,-43.7 z" + id="path51" + inkscape:connector-curvature="0" + style="fill:#95d220" /><path + class="st4" + d="m 264.2,93.8 -22.5,0 0,43.7 22.5,0 c 9.7,0 17.7,-7.9 17.7,-17.7 l 0,-8.3 c -0.1,-9.7 -8,-17.7 -17.7,-17.7 z" + id="path53" + inkscape:connector-curvature="0" + style="fill:#95d220" /></g><path + class="st3" + d="m 264.2,93.8 c 9.7,0 17.7,7.9 17.7,17.7 l 0,8.3 c 0,9.7 -8,17.7 -17.7,17.7 l -199.7,0 c -9.7,0 -17.7,-7.9 -17.7,-17.7 l 0,-8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 199.7,0 m 0,-7 -199.7,0 c -13.6,0 -24.7,11.1 -24.7,24.7 l 0,8.3 c 0,13.6 11.1,24.7 24.7,24.7 l 199.7,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-8.3 C 288.8,97.9 277.8,86.8 264.2,86.8 l 0,0 z" + id="path55" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g></g></svg> \ No newline at end of file diff --git a/mailbox-android/artwork/logo_horizontal_white.svg b/mailbox-android/artwork/logo_horizontal_white.svg new file mode 100644 index 0000000000000000000000000000000000000000..8aa45874cccb4f18e4458980a30b9ef36dfa0927 --- /dev/null +++ b/mailbox-android/artwork/logo_horizontal_white.svg @@ -0,0 +1,118 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_1" + x="0px" + y="0px" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="logo_horizontal_white.svg" + width="138" + height="50"><metadata + id="metadata71"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs + id="defs69" /><sodipodi:namedview + pagecolor="#000000" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1021" + id="namedview67" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:zoom="1.410079" + inkscape:cx="96.786606" + inkscape:cy="117.77539" + inkscape:window-x="1443" + inkscape:window-y="23" + inkscape:window-maximized="0" + inkscape:current-layer="Ebene_1" /><style + type="text/css" + id="style3"> + .st0{display:none;fill:#87C214;} + .st1{fill:#87C214;} + .st2{display:none;fill:#FFFFFF;} + .st3{fill:#95D220;} + .st4{display:none;fill:#95D220;} + .st5{fill:#FFFFFF;} +</style><rect + style="display:none;fill:#87c214" + id="rect11" + height="9.279274" + width="9.279274" + class="st0" + y="214.00124" + x="230.02246" /><path + class="st2" + d="m 235.54331,203.97877 c 2.05971,0 3.75843,1.69872 3.75843,3.75842 l 0,42.40437 c 0,2.05971 -1.69872,3.75843 -3.75843,3.75843 l -1.76242,0 c -2.0597,0 -3.75843,-1.69872 -3.75843,-3.75843 l 0,-42.40437 c 0,-2.0597 1.67749,-3.75842 3.73719,-3.75842 l 1.78366,0 m 0,-1.48638 -1.76242,0 c -2.90906,0 -5.24481,2.35697 -5.24481,5.2448 l 0,42.40437 c 0,2.88783 2.35698,5.24481 5.24481,5.24481 l 1.76242,0 c 2.88783,0 5.24481,-2.35698 5.24481,-5.24481 l 0,-42.40437 c -0.0212,-2.88783 -2.35698,-5.2448 -5.24481,-5.2448 l 0,0 z" + id="path17" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><rect + style="display:none;fill:#87c214" + id="rect25" + height="9.279274" + width="9.279274" + class="st0" + y="234.59825" + x="250.61948" /><path + class="st2" + d="m 256.14033,203.97877 c 2.0597,0 3.75842,1.69872 3.75842,3.75842 l 0,42.40437 c 0,2.05971 -1.67749,3.75843 -3.75842,3.75843 l -1.76243,0 c -2.0597,0 -3.75842,-1.69872 -3.75842,-3.75843 l 0,-42.40437 c 0,-2.0597 1.69872,-3.75842 3.75842,-3.75842 l 1.76243,0 m 0,-1.48638 -1.76243,0 c -2.88783,0 -5.2448,2.35697 -5.2448,5.2448 l 0,42.40437 c 0,2.88783 2.35697,5.24481 5.2448,5.24481 l 1.76243,0 c 2.88783,0 5.24481,-2.35698 5.24481,-5.24481 l 0,-42.40437 c -0.0212,-2.88783 -2.35698,-5.2448 -5.24481,-5.2448 l 0,0 z" + id="path29" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><rect + style="display:none;fill:#95d220" + id="rect37" + height="9.279274" + width="9.279274" + class="st4" + y="234.59825" + x="230.02246" /><path + class="st2" + d="m 266.14156,234.59825 c 2.0597,0 3.75842,1.67749 3.75842,3.75842 l 0,1.76243 c 0,2.0597 -1.69872,3.75842 -3.75842,3.75842 l -42.38314,0 c -2.0597,0 -3.75842,-1.69872 -3.75842,-3.75842 l 0,-1.76243 c 0,-2.0597 1.67749,-3.75842 3.75842,-3.75842 l 42.38314,0 m 0,-1.48638 -42.38314,0 c -2.88783,0 -5.2448,2.33574 -5.2448,5.22357 l 0,1.76242 c 0,2.88783 2.35697,5.24481 5.2448,5.24481 l 42.40437,0 c 2.88783,0 5.24481,-2.35698 5.24481,-5.24481 l 0,-1.76242 c -0.0212,-2.88783 -2.37821,-5.22357 -5.26604,-5.22357 l 0,0 z" + id="path41" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><rect + style="display:none;fill:#95d220" + id="rect47" + height="9.279274" + width="9.279274" + class="st4" + y="214.00124" + x="250.61948" /><path + class="st2" + d="m 266.14156,214.00123 c 2.0597,0 3.75842,1.67749 3.75842,3.75843 l 0,1.76242 c 0,2.05971 -1.69872,3.75843 -3.75842,3.75843 l -42.38314,0 C 221.67749,223.25927 220,221.58179 220,219.52208 l 0,-1.76242 c 0,-2.0597 1.67749,-3.75843 3.75842,-3.75843 l 42.38314,0 m 0,-1.48638 -42.38314,0 c -2.88783,0 -5.2448,2.33575 -5.2448,5.22357 l 0,1.76243 c 0,2.88783 2.35697,5.24481 5.2448,5.24481 l 42.40437,0 c 2.88783,0 5.24481,-2.35698 5.24481,-5.24481 l 0,-1.76243 c -0.0212,-2.88782 -2.37821,-5.22357 -5.26604,-5.22357 l 0,0 z" + id="path53" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><path + style="fill:#ffffff" + d="m 57.097656,30.69922 0,19.30078 9.06836,0 c 4.22557,0 6.474893,-2.12355 6.496093,-5.47852 0,-2.14464 -1.017672,-3.78004 -3.013671,-4.67187 l 0,-0.041 c 1.507609,-0.9343 2.166015,-2.10331 2.166015,-3.9082 0,-2.73919 -1.848098,-5.20117 -5.861328,-5.20117 l -8.855469,0 z m 18.75,0 0,19.30078 2.271485,0 0,-7.72852 -0.232422,-0.23437 4.585937,0 c 2.54808,0 4.012966,0.91391 4.947266,2.88867 L 89.820312,50 92.367188,50 89.4375,43.96875 c -0.63702,-1.35898 -1.614284,-2.20763 -2.527344,-2.58984 l 0,-0.043 c 2.1234,-0.55208 3.865235,-2.42042 3.865235,-4.94727 0,-3.80089 -2.951713,-5.68945 -6.476563,-5.68945 l -8.451172,0 z m 18.876953,0 0,19.30078 2.273438,0 0,-19.30078 -2.273438,0 z m 13.419921,0 L 99.650391,50 l 2.484379,0 2.03906,-4.65039 -0.12695,-0.23438 10.57421,0 -0.12695,0.23438 2.03906,4.65039 2.48438,0 -8.47266,-19.30078 -2.40039,0 z m 13.33594,0 0,19.30078 2.27148,0 0,-7.72852 -0.23437,-0.23437 4.58789,0 c 2.54808,0 4.01296,0.91391 4.94726,2.88867 L 135.45117,50 138,50 135.07031,43.96875 c -0.63702,-1.35898 -1.61427,-2.20763 -2.52734,-2.58984 l 0,-0.043 c 2.12341,-0.55208 3.86523,-2.42042 3.86523,-4.94727 0,-3.80089 -2.95171,-5.68945 -6.47656,-5.68945 l -8.45117,0 z m -62.322267,2.14453 6.560547,0 c 2.46315,0 3.759766,0.9967 3.759766,3.03516 0,1.71996 -0.999336,3.10156 -3.759766,3.10156 l -6.560547,0 0.234375,-0.23438 0,-5.66992 -0.234375,-0.23242 z m 18.728516,0 6.433593,0 c 2.378211,0 4.14091,0.97535 4.16211,3.52344 0,2.03846 -1.634356,3.5039 -4.416016,3.5039 l -6.179687,0 0.232422,-0.23242 0,-6.5625 -0.232422,-0.23242 z m 45.652341,0 6.4336,0 c 2.35698,0 4.14062,0.97535 4.14062,3.52344 0,2.03846 -1.61288,3.5039 -4.39453,3.5039 l -6.17969,0 0.23438,-0.23242 0,-6.5625 -0.23438,-0.23242 z m -14.20508,0.21094 0.043,0 0.57227,1.93359 3.39844,7.75 0.23242,0.23242 -8.4707,0 0.23242,-0.23242 3.39843,-7.75 0.59375,-1.93359 z m -50.197261,8.07031 7.007812,0 c 2.84536,0 4.16211,1.3153 4.16211,3.375 0,2.14464 -1.189095,3.33398 -4.140625,3.33398 l -7.029297,0 0.234375,-0.23437 0,-6.24219 -0.234375,-0.23242 z" + id="path57" + inkscape:connector-curvature="0" /><g + id="g4770" + transform="matrix(0.21276595,0,0,0.21276595,32.24269,18.624329)"><path + id="path13-3" + d="m -86.640255,-87.534339 c -9.7,0 -17.701175,7.999219 -17.701175,17.699219 l 0,22.5 43.601565,0 0,-22.5 c 0,-9.7 -7.901562,-17.699219 -17.601562,-17.699219 l -8.298828,0 z m 96.999999,0 c -9.69999995,0 -17.7011699,7.999219 -17.7011699,17.699219 l 0,119.500001 43.6015599,0 0,-119.500001 c 0,-9.7 -7.90156,-17.699219 -17.60156,-17.699219 l -8.29883,0 z m -114.701174,97.800781 0,119.499998 c 0,9.7 7.901175,17.69922 17.701175,17.69922 l 8.298828,0 c 9.7,0 17.701172,-7.99922 17.701172,-17.69922 l 0,-119.499998 -43.701175,0 z m 97.0000041,96.999998 0,22.5 c 0,9.7 8.00116995,17.69922 17.7011699,17.69922 l 8.29883,0 c 9.7,0 17.70117,-7.99922 17.70117,-17.69922 l 0,-22.5 -43.7011699,0 z" + style="fill:#87c214" + inkscape:connector-curvature="0" /><path + id="path35" + d="m -133.84143,-40.33512 c -9.7,0 -17.69922,7.901172 -17.69922,17.701172 l 0,8.298828 c 0,9.7000005 7.89922,17.7011725 17.69922,17.7011725 l 119.500004,0 0,-43.7011725 -119.500004,0 z m 177.101564,0 0,43.7011725 22.5,0 c 9.7,0 17.69922,-7.901172 17.69922,-17.7011725 l 0,-8.298828 c 0,-9.8 -7.99922,-17.701172 -17.69922,-17.701172 l -22.5,0 z M -133.84143,56.664881 c -9.7,0 -17.69922,7.90117 -17.69922,17.70117 l 0,8.29883 c 0,9.7 7.89922,17.701169 17.69922,17.701169 l 22.5,0 0,-43.701169 -22.5,0 z m 80.101565,0 0,43.701169 119.499999,0 c 9.7,0 17.69922,-8.001169 17.69922,-17.701169 l 0,-8.29883 c 0,-9.8 -7.99922,-17.70117 -17.69922,-17.70117 l -119.499999,0 z" + style="fill:#95d220" + inkscape:connector-curvature="0" /></g></svg> \ No newline at end of file diff --git a/mailbox-android/artwork/logo_no_text.svg b/mailbox-android/artwork/logo_no_text.svg new file mode 100644 index 0000000000000000000000000000000000000000..d093809b07a9921362a339678b959acc89e3e66c --- /dev/null +++ b/mailbox-android/artwork/logo_no_text.svg @@ -0,0 +1,154 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_1" + x="0px" + y="0px" + viewBox="0 0 235 234.99999" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="logo_no_text.svg" + width="235" + height="235"><metadata + id="metadata71"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs69" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1021" + id="namedview67" + showgrid="false" + inkscape:zoom="2" + inkscape:cx="42.80241" + inkscape:cy="93.181868" + inkscape:window-x="1443" + inkscape:window-y="23" + inkscape:window-maximized="0" + inkscape:current-layer="Ebene_1" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" /><style + type="text/css" + id="style3"> + .st0{display:none;fill:#87C214;} + .st1{fill:#87C214;} + .st2{display:none;fill:#FFFFFF;} + .st3{fill:#95D220;} + .st4{display:none;fill:#95D220;} +</style><g + id="g5" + transform="translate(-0.5,0)"><g + id="g7"><g + id="g9"><rect + x="47.700001" + y="47.200001" + class="st0" + width="43.700001" + height="43.700001" + id="rect11" + style="display:none;fill:#87c214" /><path + class="st1" + d="m 47.7,97.8 0,119.5 c 0,9.7 7.9,17.7 17.7,17.7 l 8.3,0 c 9.7,0 17.7,-8 17.7,-17.7 l 0,-119.5 -43.7,0 z" + id="path13" + inkscape:connector-curvature="0" + style="fill:#87c214" /><path + class="st1" + d="m 91.3,40.2 0,-22.5 C 91.3,8 83.4,0 73.7,0 L 65.4,0 C 55.7,0 47.7,8 47.7,17.7 l 0,22.5 43.6,0 z" + id="path15" + inkscape:connector-curvature="0" + style="fill:#87c214" /></g><path + class="st2" + d="m 73.7,0 c 9.7,0 17.7,8 17.7,17.7 l 0,199.7 c 0,9.7 -8,17.7 -17.7,17.7 l -8.3,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-199.7 C 47.7,8 55.6,0 65.3,0 l 8.4,0 m 0,-7 -8.3,0 C 51.7,-7 40.7,4.1 40.7,17.7 l 0,199.7 c 0,13.6 11.1,24.7 24.7,24.7 l 8.3,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-199.7 C 98.3,4.1 87.3,-7 73.7,-7 l 0,0 z" + id="path17" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g><g + id="g19"><g + id="g21"><path + class="st1" + d="m 188.3,137.2 0,-119.5 C 188.3,8 180.4,0 170.7,0 l -8.3,0 c -9.7,0 -17.7,8 -17.7,17.7 l 0,119.5 43.6,0 z" + id="path23" + inkscape:connector-curvature="0" + style="fill:#87c214" /><rect + x="144.7" + y="144.2" + class="st0" + width="43.700001" + height="43.700001" + id="rect25" + style="display:none;fill:#87c214" /><path + class="st1" + d="m 144.7,194.8 0,22.5 c 0,9.7 8,17.7 17.7,17.7 l 8.3,0 c 9.7,0 17.7,-8 17.7,-17.7 l 0,-22.5 -43.7,0 z" + id="path27" + inkscape:connector-curvature="0" + style="fill:#87c214" /></g><path + class="st2" + d="m 170.7,0 c 9.7,0 17.7,8 17.7,17.7 l 0,199.7 c 0,9.7 -7.9,17.7 -17.7,17.7 l -8.3,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-199.7 C 144.7,8 152.7,0 162.4,0 l 8.3,0 m 0,-7 -8.3,0 c -13.6,0 -24.7,11.1 -24.7,24.7 l 0,199.7 c 0,13.6 11.1,24.7 24.7,24.7 l 8.3,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-199.7 C 195.3,4.1 184.3,-7 170.7,-7 l 0,0 z" + id="path29" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g><g + id="g31"><g + id="g33"><path + class="st3" + d="m 40.7,144.2 -22.5,0 c -9.7,0 -17.7,7.9 -17.7,17.7 l 0,8.3 c 0,9.7 7.9,17.7 17.7,17.7 l 22.5,0 0,-43.7 z" + id="path35" + inkscape:connector-curvature="0" + style="fill:#95d220" /><rect + x="47.700001" + y="144.2" + class="st4" + width="43.700001" + height="43.700001" + id="rect37" + style="display:none;fill:#95d220" /><path + class="st3" + d="m 217.8,144.2 -119.5,0 0,43.7 119.5,0 c 9.7,0 17.7,-8 17.7,-17.7 l 0,-8.3 c 0,-9.8 -8,-17.7 -17.7,-17.7 z" + id="path39" + inkscape:connector-curvature="0" + style="fill:#95d220" /></g><path + class="st2" + d="m 217.8,144.2 c 9.7,0 17.7,7.9 17.7,17.7 l 0,8.3 c 0,9.7 -8,17.7 -17.7,17.7 l -199.6,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 199.6,0 m 0,-7 -199.6,0 c -13.6,0 -24.7,11.1 -24.7,24.7 l 0,8.3 c 0,13.6 11.1,24.7 24.7,24.7 l 199.7,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-8.3 c -0.1,-13.7 -11.2,-24.7 -24.8,-24.7 l 0,0 z" + id="path41" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g><g + id="g43"><g + id="g45"><rect + x="144.7" + y="47.200001" + class="st4" + width="43.700001" + height="43.700001" + id="rect47" + style="display:none;fill:#95d220" /><path + class="st3" + d="m 137.7,47.2 -119.5,0 C 8.5,47.2 0.5,55.1 0.5,64.9 l 0,8.3 c 0,9.7 7.9,17.7 17.7,17.7 l 119.5,0 0,-43.7 z" + id="path49" + inkscape:connector-curvature="0" + style="fill:#95d220" /><path + class="st3" + d="m 217.8,47.2 -22.5,0 0,43.7 22.5,0 c 9.7,0 17.7,-7.9 17.7,-17.7 l 0,-8.3 c 0,-9.8 -8,-17.7 -17.7,-17.7 z" + id="path51" + inkscape:connector-curvature="0" + style="fill:#95d220" /></g><path + class="st2" + d="m 217.8,47.2 c 9.7,0 17.7,7.9 17.7,17.7 l 0,8.3 c 0,9.7 -8,17.7 -17.7,17.7 l -199.6,0 C 8.5,90.9 0.5,83 0.5,73.2 l 0,-8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 199.6,0 m 0,-7 -199.6,0 c -13.6,0 -24.7,11 -24.7,24.6 l 0,8.3 c 0,13.6 11.1,24.7 24.7,24.7 l 199.7,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-8.3 C 242.5,51.2 231.4,40.2 217.8,40.2 l 0,0 z" + id="path53" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /></g></g></svg> \ No newline at end of file diff --git a/mailbox-android/artwork/logo_vertical_black.svg b/mailbox-android/artwork/logo_vertical_black.svg new file mode 100644 index 0000000000000000000000000000000000000000..89f9b907d9677b32b1709e1af30860c498624385 --- /dev/null +++ b/mailbox-android/artwork/logo_vertical_black.svg @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_1" + x="0px" + y="0px" + viewBox="0 0 235 309.99999" + xml:space="preserve" + inkscape:version="0.91 r13725" + sodipodi:docname="logo_vertical_black.svg" + width="235" + height="310"><metadata + id="metadata71"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs + id="defs69" /><sodipodi:namedview + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1021" + id="namedview67" + showgrid="false" + inkscape:zoom="2" + inkscape:cx="44.30241" + inkscape:cy="167.18188" + inkscape:window-x="1440" + inkscape:window-y="23" + inkscape:window-maximized="0" + inkscape:current-layer="Ebene_1" /><style + type="text/css" + id="style3"> + .st0{display:none;fill:#87C214;} + .st1{fill:#87C214;} + .st2{display:none;fill:#FFFFFF;} + .st3{fill:#95D220;} + .st4{display:none;fill:#95D220;} +</style><rect + style="display:none;fill:#87c214" + id="rect11" + height="43.700001" + width="43.700001" + class="st0" + y="47.199989" + x="47.200001" /><path + style="fill:#87c214" + d="M 64.900391 0 C 55.200391 0 47.199219 7.9992183 47.199219 17.699219 L 47.199219 40.199219 L 90.800781 40.199219 L 90.800781 17.699219 C 90.800781 7.9992183 82.899219 -4.7369516e-15 73.199219 0 L 64.900391 0 z M 161.90039 0 C 152.20039 0 144.19922 7.9992183 144.19922 17.699219 L 144.19922 137.19922 L 187.80078 137.19922 L 187.80078 17.699219 C 187.80078 7.9992183 179.89922 -4.7369516e-15 170.19922 0 L 161.90039 0 z M 47.199219 97.800781 L 47.199219 217.30078 C 47.199219 227.00078 55.100391 235 64.900391 235 L 73.199219 235 C 82.899219 235 90.900391 227.00078 90.900391 217.30078 L 90.900391 97.800781 L 47.199219 97.800781 z M 144.19922 194.80078 L 144.19922 217.30078 C 144.19922 227.00078 152.20039 235 161.90039 235 L 170.19922 235 C 179.89922 235 187.90039 227.00078 187.90039 217.30078 L 187.90039 194.80078 L 144.19922 194.80078 z " + id="path13" /><path + class="st2" + d="m 73.2,-1.221e-5 c 9.7,0 17.7,8.00000001 17.7,17.70000021 l 0,199.700002 c 0,9.7 -8,17.7 -17.7,17.7 l -8.3,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-199.700002 C 47.2,7.9999878 55.1,-1.221e-5 64.8,-1.221e-5 l 8.4,0 m 0,-6.99999999 -8.3,0 c -13.7,0 -24.7,11.1 -24.7,24.7000002 l 0,199.700002 c 0,13.6 11.1,24.7 24.7,24.7 l 8.3,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-199.700002 C 97.8,4.0999878 86.8,-7.0000122 73.2,-7.0000122 l 0,0 z" + id="path17" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><rect + style="display:none;fill:#87c214" + id="rect25" + height="43.700001" + width="43.700001" + class="st0" + y="144.19998" + x="144.2" /><path + class="st2" + d="m 170.2,-1.221e-5 c 9.7,0 17.7,8.00000001 17.7,17.70000021 l 0,199.700002 c 0,9.7 -7.9,17.7 -17.7,17.7 l -8.3,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-199.700002 c 0,-9.7000002 8,-17.70000021 17.7,-17.70000021 l 8.3,0 m 0,-6.99999999 -8.3,0 c -13.6,0 -24.7,11.1 -24.7,24.7000002 l 0,199.700002 c 0,13.6 11.1,24.7 24.7,24.7 l 8.3,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-199.700002 C 194.8,4.0999878 183.8,-7.0000122 170.2,-7.0000122 l 0,0 z" + id="path29" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><path + style="fill:#95d220" + d="M 17.699219 47.199219 C 7.9992186 47.199219 2.3684758e-15 55.100391 0 64.900391 L 0 73.199219 C 0 82.899219 7.8992186 90.900391 17.699219 90.900391 L 137.19922 90.900391 L 137.19922 47.199219 L 17.699219 47.199219 z M 194.80078 47.199219 L 194.80078 90.900391 L 217.30078 90.900391 C 227.00078 90.900391 235 82.999219 235 73.199219 L 235 64.900391 C 235 55.100391 227.00078 47.199219 217.30078 47.199219 L 194.80078 47.199219 z M 17.699219 144.19922 C 7.9992186 144.19922 2.3684758e-15 152.10039 0 161.90039 L 0 170.19922 C 0 179.89922 7.8992186 187.90039 17.699219 187.90039 L 40.199219 187.90039 L 40.199219 144.19922 L 17.699219 144.19922 z M 97.800781 144.19922 L 97.800781 187.90039 L 217.30078 187.90039 C 227.00078 187.90039 235 179.89922 235 170.19922 L 235 161.90039 C 235 152.10039 227.00078 144.19922 217.30078 144.19922 L 97.800781 144.19922 z " + id="path35" /><rect + style="display:none;fill:#95d220" + id="rect37" + height="43.700001" + width="43.700001" + class="st4" + y="144.19998" + x="47.200001" /><path + class="st2" + d="m 217.3,144.19999 c 9.7,0 17.7,7.9 17.7,17.7 l 0,8.3 c 0,9.7 -8,17.7 -17.7,17.7 l -199.6,0 c -9.7,0 -17.7,-8 -17.7,-17.7 l 0,-8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 199.6,0 m 0,-7 -199.6,0 c -13.6,0 -24.7,11.1 -24.7,24.7 l 0,8.3 c 0,13.6 11.1,24.7 24.7,24.7 l 199.7,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-8.3 c -0.1,-13.7 -11.2,-24.7 -24.8,-24.7 l 0,0 z" + id="path41" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><rect + style="display:none;fill:#95d220" + id="rect47" + height="43.700001" + width="43.700001" + class="st4" + y="47.199989" + x="144.2" /><path + class="st2" + d="m 217.3,47.199988 c 9.7,0 17.7,7.9 17.7,17.7 l 0,8.3 c 0,9.7 -8,17.7 -17.7,17.7 l -199.6,0 c -9.7,0 -17.7,-7.9 -17.7,-17.7 l 0,-8.3 c 0,-9.7 7.9,-17.7 17.7,-17.7 l 199.6,0 m 0,-7 -199.6,0 c -13.6,0 -24.7,11 -24.7,24.6 l 0,8.3 c 0,13.6 11.1,24.7 24.7,24.7 l 199.7,0 c 13.6,0 24.7,-11.1 24.7,-24.7 l 0,-8.3 c -0.1,-13.6 -11.2,-24.6 -24.8,-24.6 l 0,0 z" + id="path53" + inkscape:connector-curvature="0" + style="display:none;fill:#ffffff" /><path + d="M 0 253.90039 L 0 310 L 26.265625 310 C 38.649816 310 45.142578 303.79961 45.142578 294.09961 C 45.142578 287.79961 42.24573 283.1 36.453125 280.5 L 36.453125 280.40039 C 40.847515 277.70039 42.746094 274.3 42.746094 269 C 42.746094 261 37.25318 253.90039 25.667969 253.90039 L 0 253.90039 z M 54.53125 253.90039 L 54.53125 310 L 61.121094 310 L 61.121094 287.5 L 60.423828 286.80078 L 73.705078 286.80078 C 81.095643 286.80078 85.291724 289.39922 87.988281 295.19922 L 94.978516 310 L 102.36914 310 L 94.080078 292.5 C 92.282373 288.6 89.385747 286.1 86.789062 285 L 86.789062 284.90039 C 92.881286 283.30039 97.974609 277.8 97.974609 270.5 C 97.974609 259.4 89.386477 253.90039 79.099609 253.90039 L 54.53125 253.90039 z M 109.25977 253.90039 L 109.25977 310 L 115.85156 310 L 115.85156 253.90039 L 109.25977 253.90039 z M 148.01172 253.90039 L 123.3418 310 L 130.5332 310 L 136.52539 296.5 L 136.22656 295.80078 L 166.88672 295.80078 L 166.58789 296.5 L 172.58008 310 L 179.77148 310 L 155.00195 253.90039 L 148.01172 253.90039 z M 187.16016 253.90039 L 187.16016 310 L 193.75195 310 L 193.75195 287.5 L 193.05273 286.80078 L 206.33594 286.80078 C 213.72651 286.80078 217.92258 289.39922 220.61914 295.19922 L 227.60938 310 L 235 310 L 226.71094 292.5 C 224.91324 288.6 222.0166 286.1 219.41992 285 L 219.41992 284.90039 C 225.51214 283.30039 230.60547 277.8 230.60547 270.5 C 230.60547 259.4 222.01733 253.90039 211.73047 253.90039 L 187.16016 253.90039 z M 5.8925781 260.09961 L 24.96875 260.09961 C 32.15957 260.09961 35.953125 263 35.953125 269 C 35.953125 274 32.95855 278 24.96875 278 L 5.8925781 278 L 6.5917969 277.30078 L 6.5917969 260.80078 L 5.8925781 260.09961 z M 60.423828 260.09961 L 79.099609 260.09961 C 85.89094 260.09961 91.083724 262.90039 91.183594 270.40039 C 91.183594 276.40039 86.490064 280.59961 78.400391 280.59961 L 60.423828 280.59961 L 61.121094 279.90039 L 61.121094 260.80078 L 60.423828 260.09961 z M 192.95312 260.09961 L 211.62891 260.09961 C 218.52012 260.09961 223.71484 262.90039 223.71484 270.40039 C 223.71484 276.40039 219.02131 280.59961 210.93164 280.59961 L 192.95312 280.59961 L 193.65234 279.90039 L 193.65234 260.80078 L 192.95312 260.09961 z M 151.60547 260.80078 L 151.70703 260.80078 L 153.4043 266.40039 L 163.29102 288.90039 L 163.99023 289.59961 L 139.32227 289.59961 L 140.02148 288.90039 L 149.9082 266.40039 L 151.60547 260.80078 z M 5.8925781 284.19922 L 26.265625 284.19922 C 34.555043 284.19922 38.351562 287.99961 38.351562 294.09961 C 38.351562 300.29961 34.854661 303.80078 26.265625 303.80078 L 5.8925781 303.80078 L 6.5917969 303.09961 L 6.5917969 284.90039 L 5.8925781 284.19922 z " + id="path57" /></svg> \ No newline at end of file diff --git a/mailbox-android/artwork/notification_ongoing.svg b/mailbox-android/artwork/notification_ongoing.svg new file mode 100644 index 0000000000000000000000000000000000000000..b85939f22e36b43a19e8d316ef10001d3ca9286c --- /dev/null +++ b/mailbox-android/artwork/notification_ongoing.svg @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_1" + x="0px" + y="0px" + viewBox="0 0 24 24" + xml:space="preserve" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="notification_ongoing.svg" + width="24" + height="24"><metadata + id="metadata61"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs + id="defs59" /><sodipodi:namedview + pagecolor="#000000" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1020" + id="namedview57" + showgrid="false" + inkscape:zoom="11.466748" + inkscape:cx="-4.790356" + inkscape:cy="17.536496" + inkscape:window-x="1442" + inkscape:window-y="24" + inkscape:window-maximized="0" + inkscape:current-layer="Ebene_1" + units="px" /><style + type="text/css" + id="style3"> + .st0{fill:#FFFFFF;} + .st1{display:none;fill:#87C214;} + .st2{fill:#87C214;} + .st3{display:none;fill:#FFFFFF;} + .st4{fill:#95D220;} + .st5{display:none;fill:#95D220;} +</style><path + style="fill:#ffffff;stroke-width:0.07272727" + d="M 12,0 A 12,12 0 0 0 0,12 12,12 0 0 0 12,24 12,12 0 0 0 24,12 12,12 0 0 0 12,0 Z M 8.1454545,3.4764204 h 0.6036931 c 0.7054546,0 1.2872164,0.5817614 1.2872164,1.287216 V 6.4 H 6.8582386 V 4.7636364 c 0,-0.7054546 0.5817614,-1.287216 1.2872159,-1.287216 z m 7.0545455,0 h 0.603693 c 0.712727,0 1.287216,0.5817614 1.287216,1.287216 V 13.454545 H 13.912784 V 4.7636364 c 0,-0.7054546 0.581761,-1.287216 1.287216,-1.287216 z M 4.7127841,6.9090909 H 13.403693 V 10.087216 H 4.7127841 c -0.7127273,0 -1.287358,-0.5817618 -1.287358,-1.2872156 V 8.1963069 c 0,-0.7127273 0.5819034,-1.287216 1.287358,-1.287216 z m 12.8872159,0 h 1.636364 c 0.705454,0 1.279943,0.5817615 1.287216,1.287216 v 0.6036935 c 0,0.7127269 -0.581762,1.2872156 -1.287216,1.2872156 H 17.6 Z M 6.8582386,10.596307 h 3.1781254 v 8.690909 c 0,0.705454 -0.5817618,1.287358 -1.2872164,1.287358 H 8.1454545 c -0.7127272,0 -1.2872159,-0.581904 -1.2872159,-1.287358 z m -2.1454545,3.367329 h 1.6363636 v 3.178125 H 4.7127841 c -0.7127273,0 -1.287358,-0.581761 -1.287358,-1.287216 v -0.603693 c 0,-0.712727 0.5819034,-1.287216 1.287358,-1.287216 z m 5.8326709,0 h 8.690909 c 0.705454,0 1.279943,0.581761 1.287216,1.287216 v 0.603693 c 0,0.705455 -0.581762,1.287216 -1.287216,1.287216 h -8.690909 z m 3.367329,3.687216 h 3.178125 v 1.636364 c 0,0.705454 -0.581761,1.287358 -1.287216,1.287358 H 15.2 c -0.705455,0 -1.287216,-0.581904 -1.287216,-1.287358 z" + id="circle7" + inkscape:connector-curvature="0" /></svg> diff --git a/mailbox-android/artwork/notification_reminder.svg b/mailbox-android/artwork/notification_reminder.svg new file mode 100644 index 0000000000000000000000000000000000000000..7d30fd683dc8119476a58e8f795ce8930977568e --- /dev/null +++ b/mailbox-android/artwork/notification_reminder.svg @@ -0,0 +1,57 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + version="1.1" + id="Ebene_1" + x="0px" + y="0px" + viewBox="0 0 24 24" + xml:space="preserve" + inkscape:version="0.92.3 (2405546, 2018-03-11)" + sodipodi:docname="notification_reminder.svg" + width="24" + height="24"><metadata + id="metadata61"><rdf:RDF><cc:Work + rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs + id="defs59" /><sodipodi:namedview + pagecolor="#000000" + bordercolor="#666666" + borderopacity="1" + objecttolerance="10" + gridtolerance="10" + guidetolerance="10" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:window-width="1920" + inkscape:window-height="1020" + id="namedview57" + showgrid="false" + inkscape:zoom="32.43286" + inkscape:cx="6.4172501" + inkscape:cy="12.231457" + inkscape:window-x="1442" + inkscape:window-y="24" + inkscape:window-maximized="0" + inkscape:current-layer="Ebene_1" + units="px" /><style + type="text/css" + id="style3"> + .st0{fill:#FFFFFF;} + .st1{display:none;fill:#87C214;} + .st2{fill:#87C214;} + .st3{display:none;fill:#FFFFFF;} + .st4{fill:#95D220;} + .st5{display:none;fill:#95D220;} +</style><path + style="display:inline;fill:#ffffff;stroke-width:0.07272727" + d="M 12 0 A 12 12 0 0 0 4.875 2.3613281 L 6.9316406 4.4160156 C 7.0875805 3.8805807 7.5639651 3.4765625 8.1464844 3.4765625 L 8.7480469 3.4765625 C 9.4535014 3.4765625 10.035156 4.0582174 10.035156 4.7636719 L 10.035156 6.4003906 L 8.9140625 6.4003906 L 9.4238281 6.9101562 L 13.404297 6.9101562 L 13.404297 10.085938 L 12.601562 10.085938 L 13.914062 11.398438 L 13.914062 4.7636719 C 13.914062 4.0582174 14.495717 3.4765625 15.201172 3.4765625 L 15.802734 3.4765625 C 16.515461 3.4765625 17.089844 4.0582174 17.089844 4.7636719 L 17.089844 13.455078 L 15.96875 13.455078 L 16.478516 13.964844 L 19.236328 13.964844 C 19.941782 13.964844 20.516165 14.546498 20.523438 15.251953 L 20.523438 15.853516 C 20.523438 16.436036 20.119418 16.91242 19.583984 17.068359 L 21.638672 19.125 A 12 12 0 0 0 24 12 A 12 12 0 0 0 12 0 z M 1.2617188 1.3632812 L 0 2.6269531 L 2.3125 4.9472656 A 12 12 0 0 0 0 12 A 12 12 0 0 0 12 24 A 12 12 0 0 0 19.027344 21.707031 L 21.314453 24 L 22.576172 22.734375 L 2.7519531 2.8554688 L 1.9863281 2.0898438 L 1.2617188 1.3632812 z M 17.599609 6.9101562 L 19.236328 6.9101562 C 19.941782 6.9101562 20.516165 7.4918111 20.523438 8.1972656 L 20.523438 8.7988281 C 20.523438 9.511555 19.941782 10.085937 19.236328 10.085938 L 17.599609 10.085938 L 17.599609 6.9101562 z M 4.359375 6.9980469 L 7.4394531 10.085938 L 4.7128906 10.085938 C 4.0001632 10.085938 3.4257813 9.504282 3.4257812 8.7988281 L 3.4257812 8.1972656 C 3.4257812 7.6133228 3.8294199 7.1540656 4.359375 6.9980469 z M 6.859375 10.595703 L 7.9472656 10.595703 L 10.035156 12.689453 L 10.035156 19.287109 C 10.035156 19.992562 9.4535014 20.574219 8.7480469 20.574219 L 8.1464844 20.574219 C 7.4337573 20.574219 6.859375 19.992563 6.859375 19.287109 L 6.859375 10.595703 z M 4.7128906 13.964844 L 6.3496094 13.964844 L 6.3496094 17.140625 L 4.7128906 17.140625 C 4.0001632 17.140625 3.4257813 16.558971 3.4257812 15.853516 L 3.4257812 15.251953 C 3.4257812 14.539226 4.007436 13.964844 4.7128906 13.964844 z M 10.544922 13.964844 L 11.306641 13.964844 L 14.474609 17.140625 L 10.544922 17.140625 L 10.544922 13.964844 z M 13.914062 17.650391 L 14.982422 17.650391 L 16.992188 19.666016 C 16.827053 20.182975 16.371277 20.574219 15.802734 20.574219 L 15.201172 20.574219 C 14.495717 20.574219 13.914063 19.992563 13.914062 19.287109 L 13.914062 17.650391 z " + id="circle7" /></svg> \ No newline at end of file diff --git a/mailbox-android/artwork/qr_code.svg b/mailbox-android/artwork/qr_code.svg new file mode 100644 index 0000000000000000000000000000000000000000..07930f849b6ac18afb8bad78edd76d3d2a531fc3 --- /dev/null +++ b/mailbox-android/artwork/qr_code.svg @@ -0,0 +1,67 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) --> +<svg version="1.1" id="svg2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" + viewBox="-47 347.2 409.2 161.7" style="enable-background:new -47 347.2 409.2 161.7;" xml:space="preserve"> +<style type="text/css"> + .st0{display:none;fill:none;stroke:#FFFFFF;stroke-width:4.3281;} + .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#020202;} +</style> +<path id="path4201" d="M322.8,504.6l-4.3-4.3l-7.1-2.4c-3.9-1.3-8.7-3-10.7-3.7l-3.7-1.3l3.5-0.2c8.2-0.4,13-4,14.3-10.9 + c0.8-4.1,1.1-17.3,0.8-33c-0.2-8.1-0.2-15.4,0-16.3c0.1-0.9,0.5-2.4,0.9-3.4c1.2-3.5,0.3-11.9-1.9-17.6c-0.3-0.9-1.9-4.2-3.5-7.4 + c-4.2-8.2-4.5-8.9-4.9-10.5c-0.5-1.8-0.2-5.4,0.5-6.8c0.7-1.3,2.2-2.9,3.2-3.5c1.3-0.7,2.6,0.1,4.7,2.9c3.4,4.5,14,19.4,15.7,22.2 + c3.7,6,6,11.2,8,18.8c0.7,2.5,1.9,7,2.7,10.1c0.8,3.1,2.7,10.2,4.1,15.8l2.6,10.2l4.6,5.2c2.6,2.9,5.8,6.5,7.2,8 + c1.4,1.6,2.5,3,2.5,3.2c0,0.3-34.5,29.3-34.9,29.3C327.2,508.9,325.2,506.9,322.8,504.6z M228.9,488.2c-1.3-0.6-2.2-1.4-2.9-2.3 + c-2.1-2.7-2,2.4-1.9-68.5l0.1-64l0.7-1.2c1-1.9,2-2.9,3.7-3.9l1.6-0.9l37.8-0.1c42.5-0.1,39.4-0.2,42.1,2.2c0.9,0.8,1.8,2,2.2,2.9 + c0.7,1.6,0.7,1.6,0.8,14.2l0.1,12.6l-1.8-0.1c-1.4-0.1-2.1,0-3.2,0.5c-2,1-3.9,2.9-5.1,5.1l-1,2l0-12.8l0-12.8h-33.6h-33.6v51.3 + v51.3h33.6h33.6l0.1-34.4c0.1-33,0.1-34.4,0.6-32.9c0.3,0.8,1.8,4,3.4,7c5.5,10.6,5.4,9.9,5.4,47.2c0,27.6-0.1,30-1.7,33.1 + c-1.1,2.2-2.7,3.7-5.1,4.7l-1.7,0.7L267,489l-36.2,0.1L228.9,488.2L228.9,488.2z M271.3,483.1c2.9-1.3,4.5-3.7,4.4-6.6 + c0-4.1-3.1-7.2-7.1-7.2c-2.1,0-3.6,0.6-5.2,2.2c-2.2,2.2-2.8,5.4-1.3,8.3c0.7,1.4,2.5,3,4,3.5C267.6,483.8,270,483.8,271.3,483.1z" + /> +<path id="path4201-1" d="M-7.6,504.6l4.3-4.3l7.1-2.4c3.9-1.3,8.7-3,10.7-3.7l3.7-1.3l-3.5-0.2c-8.2-0.4-13-4-14.3-10.9 + c-0.8-4.1-1.1-17.3-0.8-33c0.2-8.1,0.2-15.4,0-16.3c-0.1-0.9-0.5-2.4-0.9-3.4c-1.2-3.5-0.3-11.9,1.9-17.6c0.3-0.9,1.9-4.2,3.5-7.4 + c4.2-8.2,4.5-8.9,4.9-10.5c0.5-1.8,0.2-5.4-0.5-6.8c-0.7-1.3-2.2-2.9-3.2-3.5c-1.3-0.7-2.6,0.1-4.7,2.9c-3.4,4.5-14,19.4-15.7,22.2 + c-3.7,6-6,11.2-8,18.8c-0.7,2.5-1.9,7-2.7,10.1c-0.8,3.1-2.7,10.2-4.1,15.8l-2.6,10.2l-4.6,5.2c-2.6,2.9-5.8,6.5-7.2,8 + s-2.5,3-2.5,3.2c0,0.3,34.5,29.3,34.9,29.3C-12,508.9-9.9,506.9-7.6,504.6z M86.3,488.2c1.3-0.6,2.2-1.4,2.9-2.3 + c2.1-2.7,2,2.4,1.9-68.5l-0.1-64l-0.7-1.2c-1-1.9-2-2.9-3.7-3.9l-1.6-0.9l-37.8-0.1c-42.5-0.1-39.4-0.2-42.1,2.2 + c-0.9,0.8-1.8,2-2.2,2.9c-0.7,1.6-0.7,1.6-0.8,14.2L2,379.2l1.8-0.1c1.4-0.1,2.1,0,3.2,0.5c2,1,3.9,2.9,5.1,5.1l1,2l0-12.8l0-12.8 + h33.6h33.6v51.3v51.3H46.8H13.2l-0.1-34.4c-0.1-33-0.1-34.4-0.6-32.9c-0.3,0.8-1.8,4-3.4,7c-5.5,10.6-5.4,9.9-5.4,47.2 + c0,27.6,0.1,30,1.7,33.1c1.1,2.2,2.7,3.7,5.1,4.7l1.7,0.7l36.2,0.1l36.2,0.1L86.3,488.2L86.3,488.2z M43.9,483.1 + c-2.9-1.3-4.5-3.7-4.4-6.6c0-4.1,3.1-7.2,7.1-7.2c2.1,0,3.6,0.6,5.2,2.2c2.2,2.2,2.8,5.4,1.3,8.3c-0.7,1.4-2.5,3-4,3.5 + C47.6,483.8,45.3,483.8,43.9,483.1z"/> +<g> + <path d="M33.5,410.2h2.3v2.3h2.3v2.3H26.6v-2.3h2.3v-4.6h4.6L33.5,410.2L33.5,410.2z M63.5,431h2.3v-2.3h-2.3V431z M35.8,410.2h2.3 + v-2.3h-2.3V410.2z M68.1,431h2.3v-2.3h-2.3V431z M40.4,433.3H45V431h-4.6V433.3z M61.2,433.3V431h-2.3v2.3H61.2z M52,433.3h2.3 + v-4.6H52V433.3z M33.5,403.3v2.3h6.9v-2.3H33.5z M31.2,405.6v-2.3h-4.6v4.6h2.3v-2.3H31.2z M38.1,401H22v-16.1h16.1V401z + M35.8,387.2H24.3v11.5h11.5V387.2z M26.6,428.7h6.9v-6.9h-6.9V428.7z M49.6,426.3v2.3H52v-2.3H49.6z M33.5,389.5h-6.9v6.9h6.9 + V389.5z M70.4,384.9V401H54.3v-16.1H70.4z M68.1,387.2H56.6v11.5h11.5V387.2z M22,417.1h16.1v16.1H22V417.1z M24.3,431h11.5v-11.5 + H24.3V431z M24.3,403.3H22v11.5h2.3V403.3z M54.3,414.8v2.3h2.3v-2.3H54.3z M47.3,424.1v-2.3H45v2.3h-4.6v4.6H45v2.3h2.3v-4.6h2.3 + v-2.3H47.3z M40.4,394.1H45v-2.3h-4.6V394.1z M58.9,412.5h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3V412.5z + M61.2,419.4h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9V419.4z M61.2,419.4h2.3v-4.6h-2.3V419.4z + M42.7,419.4v-2.3H45v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3H52v-9.2h-2.3v-4.6H52v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3 + H45v4.6h2.3v6.9h2.3v2.3h-2.3v4.6H45V401h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6H45v4.6h-2.3v2.3h-2.3v6.9H45v-2.3H42.7z + M68.1,421.7v-2.3h-4.6v2.3H68.1z M65.8,389.5h-6.9v6.9h6.9V389.5z M47.3,419.4H52v-2.3h-2.3v-2.3h-2.3V419.4z M52,414.8v-2.3h-2.3 + v2.3H52z M65.8,405.6h4.6v-2.3h-4.6V405.6z M68.1,424.1h2.3v-2.3h-2.3V424.1z M68.1,410.2h2.3v-2.3h-2.3V410.2z M47.3,398.7H45v2.3 + h2.3V398.7z M47.3,398.7"/> +</g> +<g> + <path d="M256.5,410.2h2.3v2.3h2.3v2.3h-11.5v-2.3h2.3v-4.6h4.6L256.5,410.2L256.5,410.2z M286.5,431h2.3v-2.3h-2.3V431z + M258.8,410.2h2.3v-2.3h-2.3V410.2z M291.1,431h2.3v-2.3h-2.3V431z M263.4,433.3h4.6V431h-4.6V433.3z M284.2,433.3V431h-2.3v2.3 + H284.2z M275,433.3h2.3v-4.6H275V433.3z M256.5,403.3v2.3h6.9v-2.3H256.5z M254.2,405.6v-2.3h-4.6v4.6h2.3v-2.3H254.2z M261.1,401 + H245v-16.1h16.1V401z M258.8,387.2h-11.5v11.5h11.5V387.2z M249.6,428.7h6.9v-6.9h-6.9V428.7z M272.6,426.3v2.3h2.3v-2.3H272.6z + M256.5,389.5h-6.9v6.9h6.9V389.5z M293.4,384.9V401h-16.1v-16.1H293.4z M291.1,387.2h-11.5v11.5h11.5V387.2z M245,417.1h16.1v16.1 + H245V417.1z M247.3,431h11.5v-11.5h-11.5V431z M247.3,403.3H245v11.5h2.3V403.3z M277.3,414.8v2.3h2.3v-2.3H277.3z M270.3,424.1 + v-2.3H268v2.3h-4.6v4.6h4.6v2.3h2.3v-4.6h2.3v-2.3H270.3z M263.4,394.1h4.6v-2.3h-4.6V394.1z M281.9,412.5h4.6v2.3h2.3v-6.9h-2.3 + v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3V412.5z M284.2,419.4h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9 + V419.4z M284.2,419.4h2.3v-4.6h-2.3V419.4z M265.7,419.4v-2.3h2.3v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3H275v-9.2h-2.3v-4.6 + h2.3v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3h2.3v4.6h2.3v6.9h2.3v2.3h-2.3v4.6H268V401h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9 + h2.3v-4.6h2.3v4.6h-2.3v2.3h-2.3v6.9h4.6v-2.3H265.7z M291.1,421.7v-2.3h-4.6v2.3H291.1z M288.8,389.5h-6.9v6.9h6.9V389.5z + M270.3,419.4h4.6v-2.3h-2.3v-2.3h-2.3V419.4z M275,414.8v-2.3h-2.3v2.3H275z M288.8,405.6h4.6v-2.3h-4.6V405.6z M291.1,424.1h2.3 + v-2.3h-2.3V424.1z M291.1,410.2h2.3v-2.3h-2.3V410.2z M270.3,398.7H268v2.3h2.3V398.7z M270.3,398.7"/> +</g> +<polygon class="st0" points="152,392 152,411.8 110,411.8 126,383.1 126,392 "/> +<polygon class="st0" points="152,392 152,411.8 110,411.8 126,383.1 126,392 "/> +<g> + <path class="st1" d="M132.6,396.1l-20.6,18l20.6,16.7v-5.2H152v-24.3h-19.3V396.1z"/> + <path class="st1" d="M182.4,430.9l20.6-18l-20.6-16.7v5.2H163v24.3h19.3V430.9z"/> +</g> +</svg> diff --git a/mailbox-android/artwork/trust-indicator.svg b/mailbox-android/artwork/trust-indicator.svg new file mode 100644 index 0000000000000000000000000000000000000000..c8f94b6f65347cd2c7f195ebf7148db85ce80be4 --- /dev/null +++ b/mailbox-android/artwork/trust-indicator.svg @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="49" + height="20" + viewBox="0 0 49.000004 20" + id="svg2" + version="1.1" + inkscape:version="0.91 r13725" + sodipodi:docname="trust-indicator.svg"> + <defs + id="defs4" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="15.839192" + inkscape:cx="19.828141" + inkscape:cy="4.1791031" + inkscape:document-units="px" + inkscape:current-layer="layer1" + showgrid="false" + units="px" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="993" + inkscape:window-x="1440" + inkscape:window-y="0" + inkscape:window-maximized="1" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-216.17711,-507.04154)"> + <g + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#b7b7b7;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="text4136" + transform="matrix(1,0,0,0.90497738,-18.96574,55.694085)"> + <path + d="m 250.64285,514.07648 0,-2.275 -2.55,0 0,-3.8 2.55,0 0,-2.275 -2.55,0 0,-4.225 -2.375,0 0,4.225 -3.15,0 0,-4.225 -2.375,0 0,4.225 -2.55,0 0,2.275 2.55,0 0,3.8 -2.55,0 0,2.275 2.55,0 0,4 2.375,0 0,-4 3.15,0 0,4 2.375,0 0,-4 2.55,0 z m -4.925,-2.275 -3.15,0 0,-3.8 3.15,0 0,3.8 z" + style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:Titillium;-inkscape-font-specification:'Titillium Semi-Bold';fill:#b7b7b7;fill-opacity:1" + id="path4745" + inkscape:connector-curvature="0" /> + </g> + <g + transform="matrix(1,0,0,0.90497738,-3.4657389,55.694085)" + id="g4755" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#b7b7b7;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"> + <path + inkscape:connector-curvature="0" + id="path4757" + style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:Titillium;-inkscape-font-specification:'Titillium Semi-Bold';fill:#b7b7b7;fill-opacity:1" + d="m 250.64285,514.07648 0,-2.275 -2.55,0 0,-3.8 2.55,0 0,-2.275 -2.55,0 0,-4.225 -2.375,0 0,4.225 -3.15,0 0,-4.225 -2.375,0 0,4.225 -2.55,0 0,2.275 2.55,0 0,3.8 -2.55,0 0,2.275 2.55,0 0,4 2.375,0 0,-4 3.15,0 0,4 2.375,0 0,-4 2.55,0 z m -4.925,-2.275 -3.15,0 0,-3.8 3.15,0 0,3.8 z" /> + </g> + <g + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:25px;line-height:125%;font-family:FreeSans;-inkscape-font-specification:FreeSans;letter-spacing:0px;word-spacing:0px;fill:#b7b7b7;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" + id="g4139" + transform="matrix(1,0,0,0.90497738,12.034262,55.694085)"> + <path + d="m 250.64285,514.07648 0,-2.275 -2.55,0 0,-3.8 2.55,0 0,-2.275 -2.55,0 0,-4.225 -2.375,0 0,4.225 -3.15,0 0,-4.225 -2.375,0 0,4.225 -2.55,0 0,2.275 2.55,0 0,3.8 -2.55,0 0,2.275 2.55,0 0,4 2.375,0 0,-4 3.15,0 0,4 2.375,0 0,-4 2.55,0 z m -4.925,-2.275 -3.15,0 0,-3.8 3.15,0 0,3.8 z" + style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:normal;font-family:Titillium;-inkscape-font-specification:'Titillium Semi-Bold';fill:#b7b7b7;fill-opacity:1" + id="path4141" + inkscape:connector-curvature="0" /> + </g> + </g> +</svg> diff --git a/mailbox-android/build.gradle b/mailbox-android/build.gradle new file mode 100644 index 0000000000000000000000000000000000000000..56d8b3240a877cd8f494f9bff5c4441eb02cb3f3 --- /dev/null +++ b/mailbox-android/build.gradle @@ -0,0 +1,130 @@ +apply plugin: 'com.android.application' +apply plugin: 'witness' + +dependencies { + implementation project(path: ':bramble-core', configuration: 'default') + implementation project(':bramble-android') + + def supportVersion = '27.1.1' + implementation "com.android.support:support-v4:$supportVersion" + implementation("com.android.support:appcompat-v7:$supportVersion") { + exclude module: 'support-v4' + } + implementation("com.android.support:preference-v14:$supportVersion") { + exclude module: 'support-v4' + } + implementation("com.android.support:design:$supportVersion") { + exclude module: 'support-v4' + exclude module: 'recyclerview-v7' + } + implementation "com.android.support:cardview-v7:$supportVersion" + implementation "com.android.support:support-annotations:$supportVersion" + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + def lifecycle_version = "1.1.1" + + // ViewModel and LiveData + implementation "android.arch.lifecycle:extensions:$lifecycle_version" + // alternately - if using Java8, use the following instead of compiler + implementation "android.arch.lifecycle:common-java8:$lifecycle_version" + + implementation('ch.acra:acra:4.9.1') { + exclude module: 'support-v4' + exclude module: 'support-annotations' + } + implementation 'info.guardianproject.panic:panic:0.5' + implementation 'info.guardianproject.trustedintents:trustedintents:0.2' + implementation 'de.hdodenhof:circleimageview:2.2.0' + implementation 'com.google.zxing:core:3.3.0' + implementation 'com.github.bumptech.glide:glide:3.8.0' + implementation 'uk.co.samuelwall:material-tap-target-prompt:2.8.0' + + annotationProcessor 'com.google.dagger:dagger-compiler:2.0.2' + + compileOnly 'javax.annotation:jsr250-api:1.0' + + testImplementation project(path: ':bramble-api', configuration: 'testOutput') + testImplementation project(path: ':bramble-core', configuration: 'testOutput') + testImplementation 'org.robolectric:robolectric:3.5.1' + testImplementation 'org.robolectric:shadows-support-v4:3.0' + testImplementation 'org.mockito:mockito-core:2.8.9' + testImplementation 'junit:junit:4.12' + testImplementation "org.jmock:jmock:2.8.2" + testImplementation "org.jmock:jmock-junit4:2.8.2" + testImplementation "org.jmock:jmock-legacy:2.8.2" + testImplementation "org.hamcrest:hamcrest-library:1.3" + testImplementation "org.hamcrest:hamcrest-core:1.3" +} + +dependencyVerification { + verify = [ + ] +} + +def getStdout = { command, defaultValue -> + def stdout = new ByteArrayOutputStream() + try { + exec { + commandLine = command + standardOutput = stdout + } + return stdout.toString().trim() + } catch (Exception ignored) { + return defaultValue + } +} + +android { + compileSdkVersion 27 + buildToolsVersion '27.0.3' + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 26 + versionCode 001 + versionName "0.0.1" + applicationId "org.briarproject.mailbox" + resValue "string", "app_package", "org.briarproject.mailbox" + resValue "string", "app_name", "Mailbox" + buildConfigField "String", "GitHash", + "\"${getStdout(['git', 'rev-parse', '--short=7', 'HEAD'], 'No commit hash')}\"" + def now = (long) (System.currentTimeMillis() / 1000) + buildConfigField "Long", "BuildTimestamp", + "${getStdout(['git', 'log', '-n', '1', '--format=%ct'], now)}000L" + } + + buildTypes { + debug { + applicationIdSuffix ".debug" + resValue "string", "app_package", "org.briarproject.mailbox.debug" + resValue "string", "app_name", "Mailbox Debug" + shrinkResources false + minifyEnabled true + crunchPngs false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + release { + shrinkResources true + minifyEnabled true + crunchPngs false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } + + lintOptions { + warning 'MissingTranslation' + warning 'ImpliedQuantity' + warning 'ExtraTranslation' + } +} + diff --git a/mailbox-android/proguard-rules.txt b/mailbox-android/proguard-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..480f35b8cc4b8c9a0717220180d740c5dfd5e379 --- /dev/null +++ b/mailbox-android/proguard-rules.txt @@ -0,0 +1,15 @@ +# Android defaults and rules from ../bramble-android/proguard-rules.txt are also applied + +-dontobfuscate +-keepattributes SourceFile, LineNumberTable, *Annotation*, Signature, InnerClasses, EnclosingMethod + +# QR codes +-keep class com.google.zxing.Result + +# OkHttp does some shenanigans with Android's SSL classes +-dontnote com.android.org.conscrypt.SSLParametersImpl +-dontnote org.apache.harmony.xnet.provider.jsse.SSLParametersImpl +-dontnote sun.security.ssl.SSLContextImpl + +# KeyboardAwareLinearLayout uses reflection on android.View +-dontnote org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout diff --git a/mailbox-android/project.properties b/mailbox-android/project.properties new file mode 100644 index 0000000000000000000000000000000000000000..629dfb0da17d1cce0b5a8a1df0611abdd6711e37 --- /dev/null +++ b/mailbox-android/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-rules.txt + +# Project target. +target=android-22 diff --git a/mailbox-android/src/main/AndroidManifest.xml b/mailbox-android/src/main/AndroidManifest.xml new file mode 100644 index 0000000000000000000000000000000000000000..4c9d79c98ada279dba2dea517502b0c6e9a0ab90 --- /dev/null +++ b/mailbox-android/src/main/AndroidManifest.xml @@ -0,0 +1,145 @@ +<?xml version="1.0" encoding="utf-8"?> + +<manifest + package="org.briarproject.mailbox" + xmlns:android="http://schemas.android.com/apk/res/android"> + + <uses-feature android:name="android.hardware.bluetooth"/> + <uses-feature android:name="android.hardware.camera"/> + + <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> + <uses-permission android:name="android.permission.BLUETOOTH"/> + <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/> + <uses-permission android:name="android.permission.CAMERA"/> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.VIBRATE"/> + <uses-permission android:name="android.permission.WAKE_LOCK"/> + <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> + + <uses-permission-sdk-23 android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/> + + <application + android:name="org.briarproject.mailbox.MailboxApplicationImpl" + android:allowBackup="false" + android:icon="@mipmap/ic_launcher_round" + android:label="@string/app_name" + android:logo="@mipmap/ic_launcher_round" + android:theme="@style/BriarTheme"> + + <receiver + android:name="org.briarproject.mailbox.BootReceiver" + android:exported="false"> + <intent-filter> + <action android:name="android.intent.action.BOOT_COMPLETED"/> + </intent-filter> + </receiver> + + <service + android:name="org.briarproject.mailbox.MailboxService" + android:exported="false"> + <intent-filter> + <action android:name="org.briarproject.mailbox.BriarService"/> + </intent-filter> + </service> + + <activity + android:name="org.briarproject.mailbox.reporting.DevReportActivity" + android:excludeFromRecents="true" + android:exported="false" + android:finishOnTaskLaunch="true" + android:label="@string/crash_report_title" + android:launchMode="singleInstance" + android:theme="@style/BriarTheme.NoActionBar" + android:windowSoftInputMode="stateHidden"> + </activity> + + <activity + android:name="org.briarproject.mailbox.splash.ExpiredActivity" + android:label="@string/app_name"> + </activity> + + <activity + android:name="org.briarproject.mailbox.login.PasswordActivity" + android:label="@string/app_name" + android:windowSoftInputMode="stateVisible"> + </activity> + + <activity + android:name="org.briarproject.mailbox.login.SetupActivity" + android:label="@string/setup_title" + android:windowSoftInputMode="adjustResize"> + </activity> + + <activity + android:name="org.briarproject.mailbox.splash.SplashScreenActivity" + android:label="@string/app_name" + android:theme="@style/BriarTheme.NoActionBar"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> + + <activity + android:name=".login.OpenDatabaseActivity" + android:label="@string/app_name" + android:launchMode="singleTop"/> + + <activity + android:name="org.briarproject.mailbox.navdrawer.NavDrawerActivity" + android:launchMode="singleTop" + android:theme="@style/BriarTheme.NoActionBar"> + </activity> + + <activity + android:name="org.briarproject.mailbox.keyagreement.MailboxExchangeActivity" + android:label="@string/mailbox_pairing" + android:parentActivityName="org.briarproject.mailbox.navdrawer.NavDrawerActivity" + android:theme="@style/BriarTheme.NoActionBar"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="org.briarproject.mailbox.navdrawer.NavDrawerActivity"/> + </activity> + + <activity + android:name="org.briarproject.mailbox.StartupFailureActivity" + android:label="@string/startup_failed_activity_title"> + </activity> + + <activity + android:name="org.briarproject.mailbox.settings.SettingsActivity" + android:label="@string/settings_button" + android:parentActivityName="org.briarproject.mailbox.navdrawer.NavDrawerActivity" + android:permission="android.permission.READ_NETWORK_USAGE_HISTORY"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="org.briarproject.mailbox.navdrawer.NavDrawerActivity" + /> + <intent-filter> + <action android:name="android.intent.action.MANAGE_NETWORK_USAGE"/> + <category android:name="android.intent.category.DEFAULT"/> + </intent-filter> + </activity> + + <activity + android:name="org.briarproject.mailbox.login.ChangePasswordActivity" + android:label="@string/change_password" + android:parentActivityName="org.briarproject.mailbox.settings.SettingsActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value="org.briarproject.mailbox.settings.SettingsActivity" + /> + </activity> + + <activity + android:name="org.briarproject.mailbox.logout.ExitActivity" + android:theme="@android:style/Theme.NoDisplay"> + </activity> + + <activity + android:name="org.briarproject.mailbox.logout.HideUiActivity" + android:theme="@android:style/Theme.NoDisplay"> + </activity> + </application> +</manifest> diff --git a/mailbox-android/src/main/assets/emoji_activity.png b/mailbox-android/src/main/assets/emoji_activity.png new file mode 100644 index 0000000000000000000000000000000000000000..908370dd15ec125d7b57b96ae932ee5fbe58a9d9 Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_activity.png differ diff --git a/mailbox-android/src/main/assets/emoji_animals_nature.png b/mailbox-android/src/main/assets/emoji_animals_nature.png new file mode 100644 index 0000000000000000000000000000000000000000..7a0661fe1ff7b9c82e2a36381ce303ceb1246693 Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_animals_nature.png differ diff --git a/mailbox-android/src/main/assets/emoji_flags.png b/mailbox-android/src/main/assets/emoji_flags.png new file mode 100644 index 0000000000000000000000000000000000000000..80f6bbd0db5f9e5e4a9938a344f064372e894606 Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_flags.png differ diff --git a/mailbox-android/src/main/assets/emoji_food_drink.png b/mailbox-android/src/main/assets/emoji_food_drink.png new file mode 100644 index 0000000000000000000000000000000000000000..33d7cd0a6849759c4ccb852a8938afd5354ec25b Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_food_drink.png differ diff --git a/mailbox-android/src/main/assets/emoji_objects.png b/mailbox-android/src/main/assets/emoji_objects.png new file mode 100644 index 0000000000000000000000000000000000000000..f2b6dfbf9d7b20287d6e95a9739373f37f5eaaaa Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_objects.png differ diff --git a/mailbox-android/src/main/assets/emoji_smiley_people.png b/mailbox-android/src/main/assets/emoji_smiley_people.png new file mode 100644 index 0000000000000000000000000000000000000000..9325d703a8010ac1ca938f3a8b20836cc1efc4c0 Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_smiley_people.png differ diff --git a/mailbox-android/src/main/assets/emoji_symbols.png b/mailbox-android/src/main/assets/emoji_symbols.png new file mode 100644 index 0000000000000000000000000000000000000000..e88275b2fc82bc0a03759a642186a8184c8746f9 Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_symbols.png differ diff --git a/mailbox-android/src/main/assets/emoji_travel_places.png b/mailbox-android/src/main/assets/emoji_travel_places.png new file mode 100644 index 0000000000000000000000000000000000000000..16e2be37d680dd410b6856de655770f3c7cbbac8 Binary files /dev/null and b/mailbox-android/src/main/assets/emoji_travel_places.png differ diff --git a/mailbox-android/src/main/ic_launcher_round-web.png b/mailbox-android/src/main/ic_launcher_round-web.png new file mode 100644 index 0000000000000000000000000000000000000000..235caf294dec5b487de1db3d2bc1c5234eb01ff7 Binary files /dev/null and b/mailbox-android/src/main/ic_launcher_round-web.png differ diff --git a/mailbox-android/src/main/java/im/delight/android/identicons/Identicon.java b/mailbox-android/src/main/java/im/delight/android/identicons/Identicon.java new file mode 100644 index 0000000000000000000000000000000000000000..42f5c959151de89a199811b4235ff1018f3fcfd1 --- /dev/null +++ b/mailbox-android/src/main/java/im/delight/android/identicons/Identicon.java @@ -0,0 +1,101 @@ +package im.delight.android.identicons; + +/** + * Copyright 2014 www.delight.im <info@delight.im> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; + +import static android.graphics.Paint.Style.FILL; + +@UiThread +class Identicon { + + private static final int ROWS = 9, COLUMNS = 9; + private static final int CENTER_COLUMN_INDEX = COLUMNS / 2 + COLUMNS % 2; + + private final byte[] input; + private final Paint paint; + private final int[][] colors; + + private int cellWidth, cellHeight; + + Identicon(@NonNull byte[] input) { + if (input.length == 0) throw new IllegalArgumentException(); + this.input = input; + + paint = new Paint(); + paint.setStyle(FILL); + paint.setAntiAlias(true); + paint.setDither(true); + + colors = new int[ROWS][COLUMNS]; + int colorVisible = getForegroundColor(); + int colorInvisible = getBackgroundColor(); + + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLUMNS; c++) { + if (isCellVisible(r, c)) colors[r][c] = colorVisible; + else colors[r][c] = colorInvisible; + } + } + } + + private byte getByte(int index) { + return input[index % input.length]; + } + + private boolean isCellVisible(int row, int column) { + return getByte(3 + row * CENTER_COLUMN_INDEX + + getSymmetricColumnIndex(column)) >= 0; + } + + private int getSymmetricColumnIndex(int index) { + if (index < CENTER_COLUMN_INDEX) return index; + else return COLUMNS - index - 1; + } + + private int getForegroundColor() { + int r = getByte(0) * 3 / 4 + 96; + int g = getByte(1) * 3 / 4 + 96; + int b = getByte(2) * 3 / 4 + 96; + return Color.rgb(r, g, b); + } + + private int getBackgroundColor() { + // http://www.google.com/design/spec/style/color.html#color-themes + return Color.rgb(0xFA, 0xFA, 0xFA); + } + + void updateSize(int w, int h) { + cellWidth = w / COLUMNS; + cellHeight = h / ROWS; + } + + void draw(Canvas canvas) { + for (int r = 0; r < ROWS; r++) { + for (int c = 0; c < COLUMNS; c++) { + int x = cellWidth * c; + int y = cellHeight * r; + paint.setColor(colors[r][c]); + canvas.drawRect(x, y + cellHeight, x + cellWidth, y, paint); + } + } + } +} diff --git a/mailbox-android/src/main/java/im/delight/android/identicons/IdenticonDrawable.java b/mailbox-android/src/main/java/im/delight/android/identicons/IdenticonDrawable.java new file mode 100644 index 0000000000000000000000000000000000000000..75663bd297eaa918f0abab90cd448b43e8601409 --- /dev/null +++ b/mailbox-android/src/main/java/im/delight/android/identicons/IdenticonDrawable.java @@ -0,0 +1,82 @@ +package im.delight.android.identicons; + +/** + * Copyright 2014 www.delight.im <info@delight.im> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.annotation.NonNull; +import android.support.annotation.UiThread; + +import static android.graphics.PixelFormat.OPAQUE; + +@UiThread +public class IdenticonDrawable extends Drawable { + + private static final int HEIGHT = 200, WIDTH = 200; + + private final Identicon identicon; + + public IdenticonDrawable(@NonNull byte[] input) { + super(); + identicon = new Identicon(input); + } + + @Override + public int getIntrinsicHeight() { + return HEIGHT; + } + + @Override + public int getIntrinsicWidth() { + return WIDTH; + } + + @Override + public void setBounds(@NonNull Rect bounds) { + super.setBounds(bounds); + identicon.updateSize(bounds.right - bounds.left, + bounds.bottom - bounds.top); + } + + @Override + public void setBounds(int left, int top, int right, int bottom) { + super.setBounds(left, top, right, bottom); + identicon.updateSize(right - left, bottom - top); + } + + @Override + public void draw(@NonNull Canvas canvas) { + identicon.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter cf) { + + } + + @Override + public int getOpacity() { + return OPAQUE; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidComponent.java b/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..e4ce080c9009b89e8259206a9967151948a9e791 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidComponent.java @@ -0,0 +1,104 @@ +package org.briarproject.mailbox; + +import android.content.SharedPreferences; + +import org.briarproject.bramble.BrambleAndroidModule; +import org.briarproject.bramble.BrambleCoreEagerSingletons; +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.bramble.api.contact.ContactExchangeTask; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.MailboxExchangeTask; +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.CryptoExecutor; +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; +import org.briarproject.bramble.api.keyagreement.PayloadEncoder; +import org.briarproject.bramble.api.keyagreement.PayloadParser; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.plugin.ConnectionRegistry; +import org.briarproject.bramble.api.plugin.PluginManager; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.mailbox.api.android.AndroidNotificationManager; +import org.briarproject.mailbox.api.android.DozeWatchdog; +import org.briarproject.mailbox.api.android.ScreenFilterMonitor; +import org.briarproject.mailbox.reporting.MailboxReportSender; + +import java.util.concurrent.Executor; + +import javax.inject.Singleton; + +import dagger.Component; + +@Singleton +@Component(modules = { + BrambleCoreModule.class, + BrambleAndroidModule.class, + AppModule.class +}) +public interface AndroidComponent + extends BrambleCoreEagerSingletons { + + // Exposed objects + @CryptoExecutor + Executor cryptoExecutor(); + + PasswordStrengthEstimator passwordStrengthIndicator(); + + CryptoComponent cryptoComponent(); + + DatabaseConfig databaseConfig(); + + @DatabaseExecutor + Executor databaseExecutor(); + + LifecycleManager lifecycleManager(); + + IdentityManager identityManager(); + + PluginManager pluginManager(); + + EventBus eventBus(); + + SharedPreferences sharedPreferences(); + + ScreenFilterMonitor screenFilterMonitor(); + + ConnectionRegistry connectionRegistry(); + + ContactManager contactManager(); + + SettingsManager settingsManager(); + + MailboxExchangeTask mailboxExchangeTask(); + + KeyAgreementTask keyAgreementTask(); + + PayloadEncoder payloadEncoder(); + + PayloadParser payloadParser(); + + AndroidExecutor androidExecutor(); + + Clock clock(); + + DozeWatchdog dozeWatchdog(); + + @IoExecutor + Executor ioExecutor(); + + void inject(BootReceiver briarService); + + void inject(MailboxService mailboxService); + + void inject(MailboxReportSender mailboxReportSender); + + // Eager singleton load + void inject(AppModule.EagerSingletons init); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidDatabaseConfig.java b/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidDatabaseConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..946591187b044fe4629763ec37b4d34c950ab5f7 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidDatabaseConfig.java @@ -0,0 +1,101 @@ +package org.briarproject.mailbox; + +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.io.File; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import static java.util.logging.Level.INFO; + +@NotNullByDefault +class AndroidDatabaseConfig implements DatabaseConfig { + + private static final Logger LOG = + Logger.getLogger(AndroidDatabaseConfig.class.getName()); + + private final File dbDir, keyDir; + + @Nullable + private volatile SecretKey key = null; + @Nullable + private volatile String nickname = null; + + AndroidDatabaseConfig(File dbDir, File keyDir) { + this.dbDir = dbDir; + this.keyDir = keyDir; + } + + @Override + public boolean databaseExists() { + // FIXME should not run on UiThread #620 + if (!dbDir.isDirectory()) { + if (LOG.isLoggable(INFO)) + LOG.info(dbDir.getAbsolutePath() + " is not a directory"); + return false; + } + File[] files = dbDir.listFiles(); + if (LOG.isLoggable(INFO)) { + if (files == null) { + LOG.info("Could not list files in " + dbDir.getAbsolutePath()); + } else { + LOG.info("Files in " + dbDir.getAbsolutePath() + ":"); + for (File f : files) LOG.info(f.getName()); + } + LOG.info("Database exists: " + (files != null && files.length > 0)); + } + return files != null && files.length > 0; + } + + @Override + public File getDatabaseDirectory() { + if (LOG.isLoggable(INFO)) + LOG.info("Database directory: " + dbDir.getAbsolutePath()); + return dbDir; + } + + @Override + public File getDatabaseKeyDirectory() { + if (LOG.isLoggable(INFO)) + LOG.info("Database key directory: " + keyDir.getAbsolutePath()); + return keyDir; + } + + @Override + public void setEncryptionKey(SecretKey key) { + LOG.info("Setting database key"); + this.key = key; + } + + @Override + public void setLocalAuthorName(String nickname) { + LOG.info("Setting local author name"); + this.nickname = nickname; + } + + @Override + @Nullable + public String getLocalAuthorName() { + String nickname = this.nickname; + if (LOG.isLoggable(INFO)) + LOG.info("Local author name has been set: " + (nickname != null)); + return nickname; + } + + @Override + @Nullable + public SecretKey getEncryptionKey() { + SecretKey key = this.key; + if (LOG.isLoggable(INFO)) + LOG.info("Database key has been set: " + (key != null)); + return key; + } + + @Override + public long getMaxSize() { + return Long.MAX_VALUE; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidEagerSingletons.java b/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidEagerSingletons.java new file mode 100644 index 0000000000000000000000000000000000000000..2915dd72f4a7fa3a0f87c0015260ec23b69771b5 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/AndroidEagerSingletons.java @@ -0,0 +1,8 @@ +package org.briarproject.mailbox; + +class AndroidEagerSingletons { + + static void initEagerSingletons(AndroidComponent c) { + c.inject(new AppModule.EagerSingletons()); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/AppModule.java b/mailbox-android/src/main/java/org/briarproject/mailbox/AppModule.java new file mode 100644 index 0000000000000000000000000000000000000000..5c694591d680cb74d46b7ba36f8b27eba493ebdc --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/AppModule.java @@ -0,0 +1,196 @@ +package org.briarproject.mailbox; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.StrictMode; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.PublicKey; +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.BackoffFactory; +import org.briarproject.bramble.api.plugin.PluginConfig; +import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory; +import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory; +import org.briarproject.bramble.api.reporting.DevConfig; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.bramble.api.system.Clock; +import org.briarproject.bramble.api.system.LocationUtils; +import org.briarproject.bramble.api.system.Scheduler; +import org.briarproject.bramble.plugin.bluetooth.AndroidBluetoothPluginFactory; +import org.briarproject.bramble.plugin.tcp.AndroidLanTcpPluginFactory; +import org.briarproject.bramble.plugin.tor.TorPluginFactory; +import org.briarproject.bramble.util.AndroidUtils; +import org.briarproject.bramble.util.StringUtils; +import org.briarproject.mailbox.api.android.DozeWatchdog; +import org.briarproject.mailbox.api.android.ReferenceManager; +import org.briarproject.mailbox.api.android.ScreenFilterMonitor; + +import java.io.File; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; + +import javax.inject.Inject; +import javax.inject.Singleton; +import javax.net.SocketFactory; + +import dagger.Module; +import dagger.Provides; + +import static android.content.Context.MODE_PRIVATE; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONION_ADDRESS; +import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX; + +@Module +public class AppModule { + + static class EagerSingletons { + @Inject + NetworkUsageLogger networkUsageLogger; + @Inject + DozeWatchdog dozeWatchdog; + } + + private final Application application; + + public AppModule(Application application) { + this.application = application; + } + + @Provides + @Singleton + Application providesApplication() { + return application; + } + + @Provides + @Singleton + DatabaseConfig provideDatabaseConfig(Application app) { + //FIXME: StrictMode + StrictMode.ThreadPolicy tp = StrictMode.allowThreadDiskReads(); + StrictMode.allowThreadDiskWrites(); + File dbDir = app.getApplicationContext().getDir("db", MODE_PRIVATE); + File keyDir = app.getApplicationContext().getDir("key", MODE_PRIVATE); + StrictMode.setThreadPolicy(tp); + @MethodsNotNullByDefault + @ParametersNotNullByDefault + DatabaseConfig databaseConfig = + new AndroidDatabaseConfig(dbDir, keyDir); + return databaseConfig; + } + + @Provides + PluginConfig providePluginConfig(@IoExecutor Executor ioExecutor, + @Scheduler ScheduledExecutorService scheduler, + AndroidExecutor androidExecutor, SecureRandom random, + SocketFactory torSocketFactory, BackoffFactory backoffFactory, + Application app, LocationUtils locationUtils, EventBus eventBus, + Clock clock) { + Context appContext = app.getApplicationContext(); + DuplexPluginFactory bluetooth = + new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor, + appContext, random, eventBus, backoffFactory); + DuplexPluginFactory tor = new TorPluginFactory(ioExecutor, scheduler, + appContext, locationUtils, eventBus, torSocketFactory, + backoffFactory, clock); + DuplexPluginFactory lan = new AndroidLanTcpPluginFactory(ioExecutor, + scheduler, backoffFactory, appContext); + Collection<DuplexPluginFactory> duplex = asList(bluetooth, tor, lan); + @NotNullByDefault + PluginConfig pluginConfig = new PluginConfig() { + + @Override + public Collection<DuplexPluginFactory> getDuplexFactories() { + return duplex; + } + + @Override + public Collection<SimplexPluginFactory> getSimplexFactories() { + return emptyList(); + } + + @Override + public boolean shouldPoll() { + return false; + } + }; + return pluginConfig; + } + + @Provides + @Singleton + DevConfig provideDevConfig(Application app, CryptoComponent crypto) { + @NotNullByDefault + DevConfig devConfig = new DevConfig() { + + @Override + public PublicKey getDevPublicKey() { + try { + return crypto.getMessageKeyParser().parsePublicKey( + StringUtils.fromHexString(DEV_PUBLIC_KEY_HEX)); + } catch (GeneralSecurityException e) { + throw new RuntimeException(e); + } + } + + @Override + public String getDevOnionAddress() { + return DEV_ONION_ADDRESS; + } + + @Override + public File getReportDir() { + return AndroidUtils.getReportDir(app.getApplicationContext()); + } + }; + return devConfig; + } + + @Provides + SharedPreferences provideSharedPreferences(Application app) { + return app.getSharedPreferences("db", MODE_PRIVATE); + } + + @Provides + @Singleton + ReferenceManager provideReferenceManager() { + return new ReferenceManagerImpl(); + } + + @Provides + @Singleton + ScreenFilterMonitor provideScreenFilterMonitor( + LifecycleManager lifecycleManager, + ScreenFilterMonitorImpl screenFilterMonitor) { + lifecycleManager.registerService(screenFilterMonitor); + return screenFilterMonitor; + } + + @Provides + NetworkUsageLogger provideNetworkUsageLogger( + LifecycleManager lifecycleManager) { + NetworkUsageLogger networkUsageLogger = new NetworkUsageLogger(); + lifecycleManager.registerService(networkUsageLogger); + return networkUsageLogger; + } + + @Provides + @Singleton + DozeWatchdog provideDozeWatchdog(LifecycleManager lifecycleManager) { + DozeWatchdogImpl dozeWatchdog = new DozeWatchdogImpl(application); + lifecycleManager.registerService(dozeWatchdog); + return dozeWatchdog; + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/BootReceiver.java b/mailbox-android/src/main/java/org/briarproject/mailbox/BootReceiver.java new file mode 100644 index 0000000000000000000000000000000000000000..e773cf9119cdbb6abb6926ee7a43d2542a7d5653 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/BootReceiver.java @@ -0,0 +1,82 @@ +package org.briarproject.mailbox; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.ContextCompat; + +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.mailbox.navdrawer.NavDrawerActivity; + +import javax.inject.Inject; + +import static android.app.NotificationManager.IMPORTANCE_LOW; +import static android.content.Context.NOTIFICATION_SERVICE; +import static android.content.Intent.ACTION_BOOT_COMPLETED; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.Build.VERSION.SDK_INT; +import static android.support.v4.app.NotificationCompat.PRIORITY_LOW; +import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; +import static org.briarproject.mailbox.TestingConstants.FEATURE_FLAG_SIGN_IN_REMINDER; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.REMINDER_CHANNEL_ID; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.REMINDER_NOTIFICATION_ID; + +public class BootReceiver extends BroadcastReceiver { + + @Inject + DatabaseConfig databaseConfig; + + @Override + public void onReceive(Context ctx, Intent intent) { + if (!FEATURE_FLAG_SIGN_IN_REMINDER) return; + + AndroidComponent applicationComponent = + ((MailboxApplication) ctx.getApplicationContext()) + .getApplicationComponent(); + applicationComponent.inject(this); + + String action = intent.getAction(); + if (action != null && action.equals(ACTION_BOOT_COMPLETED)) { + if (databaseConfig.databaseExists()) { + showSignInNotification(ctx); + } + } + } + + private void showSignInNotification(Context ctx) { + NotificationManager nm = (NotificationManager) + ctx.getSystemService(NOTIFICATION_SERVICE); + if (nm == null) return; + + if (SDK_INT >= 26) { + NotificationChannel channel = + new NotificationChannel(REMINDER_CHANNEL_ID, ctx.getString( + R.string.reminder_notification_channel_title), + IMPORTANCE_LOW); + channel.setLockscreenVisibility(VISIBILITY_SECRET); + nm.createNotificationChannel(channel); + } + + NotificationCompat.Builder b = + new NotificationCompat.Builder(ctx, REMINDER_CHANNEL_ID); + b.setSmallIcon(R.drawable.notification_reminder); + b.setColor(ContextCompat.getColor(ctx, R.color.briar_primary)); + b.setContentTitle(ctx.getText(R.string.reminder_notification_title)); + b.setContentText(ctx.getText(R.string.reminder_notification_text)); + b.setAutoCancel(true); + b.setWhen(0); // Don't show the time + b.setPriority(PRIORITY_LOW); + + Intent i = new Intent(ctx, NavDrawerActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); + b.setContentIntent(PendingIntent.getActivity(ctx, 0, i, 0)); + + nm.notify(REMINDER_NOTIFICATION_ID, b.build()); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/DestroyableContext.java b/mailbox-android/src/main/java/org/briarproject/mailbox/DestroyableContext.java new file mode 100644 index 0000000000000000000000000000000000000000..133f2d11fe32cb9350fd17ac77ef2242bec0260d --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/DestroyableContext.java @@ -0,0 +1,6 @@ +package org.briarproject.mailbox; + +public interface DestroyableContext { + + void runOnUiThreadUnlessDestroyed(Runnable runnable); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/DozeWatchdogImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/DozeWatchdogImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..3d2e08f7698ad44d9cd4d895ed88842aa35ffd8f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/DozeWatchdogImpl.java @@ -0,0 +1,57 @@ +package org.briarproject.mailbox; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.PowerManager; + +import org.briarproject.bramble.api.lifecycle.Service; +import org.briarproject.bramble.api.lifecycle.ServiceException; +import org.briarproject.mailbox.api.android.DozeWatchdog; + +import java.util.concurrent.atomic.AtomicBoolean; + +import static android.content.Context.POWER_SERVICE; +import static android.os.Build.VERSION.SDK_INT; +import static android.os.PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED; + +class DozeWatchdogImpl implements DozeWatchdog, Service { + + private final Context appContext; + private final AtomicBoolean dozed = new AtomicBoolean(false); + private final BroadcastReceiver receiver = new DozeBroadcastReceiver(); + + DozeWatchdogImpl(Context appContext) { + this.appContext = appContext; + } + + @Override + public boolean getAndResetDozeFlag() { + return dozed.getAndSet(false); + } + + @Override + public void startService() throws ServiceException { + if (SDK_INT < 23) return; + IntentFilter filter = new IntentFilter(ACTION_DEVICE_IDLE_MODE_CHANGED); + appContext.registerReceiver(receiver, filter); + } + + @Override + public void stopService() throws ServiceException { + if (SDK_INT < 23) return; + appContext.unregisterReceiver(receiver); + } + + private class DozeBroadcastReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + if (SDK_INT < 23) return; + PowerManager pm = + (PowerManager) appContext.getSystemService(POWER_SERVICE); + if (pm.isDeviceIdleMode()) dozed.set(true); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/Localizer.java b/mailbox-android/src/main/java/org/briarproject/mailbox/Localizer.java new file mode 100644 index 0000000000000000000000000000000000000000..f88c115d497b0130a01ed38350ee0910b137c3d3 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/Localizer.java @@ -0,0 +1,92 @@ +package org.briarproject.mailbox; + +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.content.res.Resources; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.Locale; + +import javax.annotation.Nullable; + +import static android.os.Build.VERSION.SDK_INT; +import static org.briarproject.mailbox.settings.SettingsFragment.LANGUAGE; + +@NotNullByDefault +public class Localizer { + + // Locking: class + @Nullable + private static Localizer INSTANCE; + private final Locale systemLocale; + private final Locale locale; + + private Localizer(SharedPreferences sharedPreferences) { + this(Locale.getDefault(), getLocaleFromTag( + sharedPreferences.getString(LANGUAGE, "default"))); + } + + private Localizer(Locale systemLocale, @Nullable Locale userLocale) { + this.systemLocale = systemLocale; + if (userLocale == null) locale = systemLocale; + else locale = userLocale; + } + + // Instantiate the Localizer. + public static synchronized void initialize(SharedPreferences prefs) { + if (INSTANCE == null) + INSTANCE = new Localizer(prefs); + } + + // Reinstantiate the Localizer with the system locale + public static synchronized void reinitialize() { + if (INSTANCE != null) + INSTANCE = new Localizer(INSTANCE.systemLocale, null); + } + + // Get the current instance. + public static synchronized Localizer getInstance() { + if (INSTANCE == null) + throw new IllegalStateException("Localizer not initialized"); + return INSTANCE; + } + + // Get Locale from BCP-47 tag + @Nullable + public static Locale getLocaleFromTag(String tag) { + if (tag.equals("default")) + return null; + if (SDK_INT >= 21) { + return Locale.forLanguageTag(tag); + } + if (tag.contains("-")) { + String[] langArray = tag.split("-"); + return new Locale(langArray[0], langArray[1]); + } else + return new Locale(tag); + } + + // Returns the localized version of context + public Context setLocale(Context context) { + Resources res = context.getResources(); + Configuration conf = res.getConfiguration(); + Locale currentLocale; + if (SDK_INT >= 24) { + currentLocale = conf.getLocales().get(0); + } else + currentLocale = conf.locale; + if (locale.equals(currentLocale)) + return context; + Locale.setDefault(locale); + if (SDK_INT >= 17) { + conf.setLocale(locale); + context.createConfigurationContext(conf); + } else + conf.locale = locale; + //noinspection deprecation + res.updateConfiguration(conf, res.getDisplayMetrics()); + return context; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxApplication.java b/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxApplication.java new file mode 100644 index 0000000000000000000000000000000000000000..fb5126b6b8235e31538c009bebde417292216cd7 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxApplication.java @@ -0,0 +1,15 @@ +package org.briarproject.mailbox; + +import java.util.Collection; +import java.util.logging.LogRecord; + +/** + * This exists so that the Application object will not necessarily be cast + * directly to the Mailbox application object. + */ +public interface MailboxApplication { + + Collection<LogRecord> getRecentLogRecords(); + + AndroidComponent getApplicationComponent(); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxApplicationImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxApplicationImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b8aa7ccf02ddcc0bd63df7b7212f2c2d7716052b --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxApplicationImpl.java @@ -0,0 +1,157 @@ +package org.briarproject.mailbox; + +import android.app.Application; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.StrictMode; +import android.os.StrictMode.ThreadPolicy; +import android.os.StrictMode.VmPolicy; +import android.preference.PreferenceManager; + +import org.acra.ACRA; +import org.acra.ReportingInteractionMode; +import org.acra.annotation.ReportsCrashes; +import org.briarproject.bramble.BrambleCoreModule; +import org.briarproject.mailbox.logging.CachingLogHandler; +import org.briarproject.mailbox.reporting.MailboxReportPrimer; +import org.briarproject.mailbox.reporting.MailboxReportSenderFactory; +import org.briarproject.mailbox.reporting.DevReportActivity; +import org.briarproject.mailbox.util.UiUtils; + +import java.util.Collection; +import java.util.logging.Handler; +import java.util.logging.LogRecord; +import java.util.logging.Logger; + +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.INFO; +import static org.acra.ReportField.ANDROID_VERSION; +import static org.acra.ReportField.APP_VERSION_CODE; +import static org.acra.ReportField.APP_VERSION_NAME; +import static org.acra.ReportField.BRAND; +import static org.acra.ReportField.BUILD_CONFIG; +import static org.acra.ReportField.CRASH_CONFIGURATION; +import static org.acra.ReportField.CUSTOM_DATA; +import static org.acra.ReportField.DEVICE_FEATURES; +import static org.acra.ReportField.DISPLAY; +import static org.acra.ReportField.INITIAL_CONFIGURATION; +import static org.acra.ReportField.PACKAGE_NAME; +import static org.acra.ReportField.PHONE_MODEL; +import static org.acra.ReportField.PRODUCT; +import static org.acra.ReportField.REPORT_ID; +import static org.acra.ReportField.STACK_TRACE; +import static org.acra.ReportField.USER_APP_START_DATE; +import static org.acra.ReportField.USER_CRASH_DATE; +import static org.briarproject.mailbox.TestingConstants.IS_BETA_BUILD; +import static org.briarproject.mailbox.TestingConstants.IS_DEBUG_BUILD; + +@ReportsCrashes( + reportPrimerClass = MailboxReportPrimer.class, + logcatArguments = {"-d", "-v", "time", "*:I"}, + reportSenderFactoryClasses = {MailboxReportSenderFactory.class}, + mode = ReportingInteractionMode.DIALOG, + reportDialogClass = DevReportActivity.class, + resDialogOkToast = R.string.dev_report_saved, + deleteOldUnsentReportsOnApplicationStart = false, + customReportContent = { + REPORT_ID, + APP_VERSION_CODE, APP_VERSION_NAME, PACKAGE_NAME, + PHONE_MODEL, ANDROID_VERSION, BRAND, PRODUCT, + BUILD_CONFIG, + CUSTOM_DATA, + STACK_TRACE, + INITIAL_CONFIGURATION, CRASH_CONFIGURATION, + DISPLAY, DEVICE_FEATURES, + USER_APP_START_DATE, USER_CRASH_DATE + } +) +public class MailboxApplicationImpl extends Application + implements MailboxApplication { + + private static final Logger LOG = + Logger.getLogger(MailboxApplicationImpl.class.getName()); + + private final CachingLogHandler logHandler = new CachingLogHandler(); + + private AndroidComponent applicationComponent; + + @Override + protected void attachBaseContext(Context base) { + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(base); + // Loading the language needs to be done here. + Localizer.initialize(prefs); + super.attachBaseContext( + Localizer.getInstance().setLocale(base)); + setTheme(base, prefs); + ACRA.init(this); + } + + @Override + public void onCreate() { + super.onCreate(); + + if (IS_DEBUG_BUILD) enableStrictMode(); + + Logger rootLogger = Logger.getLogger(""); + if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) { + // Remove default log handlers so system log is not used + for (Handler handler : rootLogger.getHandlers()) { + rootLogger.removeHandler(handler); + } + } + rootLogger.addHandler(logHandler); + rootLogger.setLevel(IS_DEBUG_BUILD || IS_BETA_BUILD ? FINE : INFO); + + LOG.info("Created"); + + applicationComponent = DaggerAndroidComponent.builder() + .appModule(new AppModule(this)) + .build(); + + // We need to load the eager singletons directly after making the + // dependency graphs + BrambleCoreModule.initEagerSingletons(applicationComponent); + //BriarCoreModule.initEagerSingletons(applicationComponent); + AndroidEagerSingletons.initEagerSingletons(applicationComponent); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + Localizer.getInstance().setLocale(this); + } + + private void setTheme(Context ctx, SharedPreferences prefs) { + String theme = prefs.getString("pref_key_theme", null); + if (theme == null) { + // set default value + theme = getString(R.string.pref_theme_light_value); + prefs.edit().putString("pref_key_theme", theme).apply(); + } + // set theme + UiUtils.setTheme(ctx, theme); + } + + private void enableStrictMode() { + ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder(); + threadPolicy.detectAll(); + threadPolicy.penaltyLog(); + StrictMode.setThreadPolicy(threadPolicy.build()); + VmPolicy.Builder vmPolicy = new VmPolicy.Builder(); + vmPolicy.detectAll(); + vmPolicy.penaltyLog(); + StrictMode.setVmPolicy(vmPolicy.build()); + } + + @Override + public Collection<LogRecord> getRecentLogRecords() { + return logHandler.getRecentLogRecords(); + } + + @Override + public AndroidComponent getApplicationComponent() { + return applicationComponent; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxService.java b/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxService.java new file mode 100644 index 0000000000000000000000000000000000000000..46a58271607126ba1a10833f772cb808a09066a0 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/MailboxService.java @@ -0,0 +1,345 @@ +package org.briarproject.mailbox; + +import android.app.ActivityManager; +import android.app.ActivityManager.RunningAppProcessInfo; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import android.support.v4.app.NotificationCompat; +import android.support.v4.content.ContextCompat; + +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.mailbox.logout.HideUiActivity; +import org.briarproject.mailbox.navdrawer.NavDrawerActivity; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; +import static android.app.NotificationManager.IMPORTANCE_DEFAULT; +import static android.app.NotificationManager.IMPORTANCE_NONE; +import static android.app.PendingIntent.FLAG_UPDATE_CURRENT; +import static android.content.Intent.ACTION_SHUTDOWN; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP; +import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; +import static android.os.Build.VERSION.SDK_INT; +import static android.support.v4.app.NotificationCompat.CATEGORY_SERVICE; +import static android.support.v4.app.NotificationCompat.PRIORITY_MIN; +import static android.support.v4.app.NotificationCompat.VISIBILITY_SECRET; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult.SUCCESS; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.FAILURE_CHANNEL_ID; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.FAILURE_NOTIFICATION_ID; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.ONGOING_CHANNEL_ID; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.ONGOING_NOTIFICATION_ID; +import static org.briarproject.mailbox.api.android.AndroidNotificationManager.REMINDER_NOTIFICATION_ID; + +public class MailboxService extends Service { + + public static String EXTRA_START_RESULT = + "org.briarproject.briar.START_RESULT"; + public static String EXTRA_NOTIFICATION_ID = + "org.briarproject.briar.FAILURE_NOTIFICATION_ID"; + public static String EXTRA_STARTUP_FAILED = + "org.briarproject.briar.STARTUP_FAILED"; + + private static final Logger LOG = + Logger.getLogger(MailboxService.class.getName()); + + private final AtomicBoolean created = new AtomicBoolean(false); + private final Binder binder = new BriarBinder(); + + @Nullable + private BroadcastReceiver receiver = null; + + @Inject + protected DatabaseConfig databaseConfig; + // Fields that are accessed from background threads must be volatile + @Inject + protected volatile LifecycleManager lifecycleManager; + @Inject + protected volatile AndroidExecutor androidExecutor; + private volatile boolean started = false; + + @Override + public void onCreate() { + super.onCreate(); + + MailboxApplication application = (MailboxApplication) getApplication(); + application.getApplicationComponent().inject(this); + + LOG.info("Created"); + if (created.getAndSet(true)) { + LOG.info("Already created"); + stopSelf(); + return; + } + if (databaseConfig.getEncryptionKey() == null) { + LOG.info("No database key"); + stopSelf(); + return; + } + + // Create notification channels + NotificationManager nm = (NotificationManager) + getSystemService(NOTIFICATION_SERVICE); + if (SDK_INT >= 26) { + NotificationChannel ongoingChannel = new NotificationChannel( + ONGOING_CHANNEL_ID, + getString(R.string.ongoing_notification_title), + IMPORTANCE_NONE); + ongoingChannel.setLockscreenVisibility(VISIBILITY_SECRET); + nm.createNotificationChannel(ongoingChannel); + NotificationChannel failureChannel = new NotificationChannel( + FAILURE_CHANNEL_ID, + getString(R.string.startup_failed_notification_title), + IMPORTANCE_DEFAULT); + failureChannel.setLockscreenVisibility(VISIBILITY_SECRET); + nm.createNotificationChannel(failureChannel); + } + // Show an ongoing notification that the service is running + NotificationCompat.Builder b = + new NotificationCompat.Builder(this, ONGOING_CHANNEL_ID); + b.setSmallIcon(R.drawable.notification_ongoing); + b.setColor(ContextCompat.getColor(this, R.color.briar_primary)); + b.setContentTitle(getText(R.string.ongoing_notification_title)); + b.setContentText(getText(R.string.ongoing_notification_text)); + b.setWhen(0); // Don't show the time + b.setOngoing(true); + Intent i = new Intent(this, NavDrawerActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); + b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0)); + if (SDK_INT >= 21) { + b.setCategory(CATEGORY_SERVICE); + b.setVisibility(VISIBILITY_SECRET); + } + b.setPriority(PRIORITY_MIN); + startForeground(ONGOING_NOTIFICATION_ID, b.build()); + // Remove sign-in reminder notification + nm.cancel(REMINDER_NOTIFICATION_ID); + // Start the services in a background thread + new Thread(() -> { + String nickname = databaseConfig.getLocalAuthorName(); + StartResult result = lifecycleManager.startServices(nickname); + if (result == SUCCESS) { + started = true; + } else if (result == ALREADY_RUNNING) { + LOG.info("Already running"); + stopSelf(); + } else { + if (LOG.isLoggable(WARNING)) + LOG.warning("Startup failed: " + result); + showStartupFailureNotification(result); + stopSelf(); + } + }).start(); + // Register for device shutdown broadcasts + receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + LOG.info("Device is shutting down"); + shutdownFromBackground(); + } + }; + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_SHUTDOWN); + filter.addAction("android.intent.action.QUICKBOOT_POWEROFF"); + filter.addAction("com.htc.intent.action.QUICKBOOT_POWEROFF"); + registerReceiver(receiver, filter); + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(Localizer.getInstance().setLocale(base)); + } + + private void showStartupFailureNotification(StartResult result) { + androidExecutor.runOnUiThread(() -> { + NotificationCompat.Builder b = new NotificationCompat.Builder( + MailboxService.this, FAILURE_CHANNEL_ID); + b.setSmallIcon(android.R.drawable.stat_notify_error); + b.setContentTitle(getText( + R.string.startup_failed_notification_title)); + b.setContentText(getText( + R.string.startup_failed_notification_text)); + Intent i = new Intent(MailboxService.this, + StartupFailureActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK); + i.putExtra(EXTRA_START_RESULT, result); + i.putExtra(EXTRA_NOTIFICATION_ID, FAILURE_NOTIFICATION_ID); + b.setContentIntent(PendingIntent.getActivity(MailboxService.this, + 0, i, FLAG_UPDATE_CURRENT)); + Object o = getSystemService(NOTIFICATION_SERVICE); + NotificationManager nm = (NotificationManager) o; + nm.notify(FAILURE_NOTIFICATION_ID, b.build()); + // Bring the dashboard to the front to clear the back stack + i = new Intent(MailboxService.this, NavDrawerActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP); + i.putExtra(EXTRA_STARTUP_FAILED, true); + startActivity(i); + }); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + return START_NOT_STICKY; // Don't restart automatically if killed + } + + @Override + public IBinder onBind(Intent intent) { + return binder; + } + + @Override + public void onDestroy() { + super.onDestroy(); + LOG.info("Destroyed"); + stopForeground(true); + if (receiver != null) unregisterReceiver(receiver); + // Stop the services in a background thread + new Thread(() -> { + if (started) lifecycleManager.stopServices(); + }).start(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + LOG.warning("Memory is low"); + // Clear the UI - this is done in onTrimMemory() if SDK_INT >= 16 + if (SDK_INT < 16) hideUi(); + } + + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level == TRIM_MEMORY_UI_HIDDEN) { + LOG.info("Trim memory: UI hidden"); + } else if (level == TRIM_MEMORY_BACKGROUND) { + LOG.info("Trim memory: added to LRU list"); + } else if (level == TRIM_MEMORY_MODERATE) { + LOG.info("Trim memory: near middle of LRU list"); + } else if (level == TRIM_MEMORY_COMPLETE) { + LOG.info("Trim memory: near end of LRU list"); + } else if (SDK_INT >= 16) { + if (level == TRIM_MEMORY_RUNNING_MODERATE) { + LOG.info("Trim memory: running moderately low"); + } else if (level == TRIM_MEMORY_RUNNING_LOW) { + LOG.info("Trim memory: running low"); + } else if (level == TRIM_MEMORY_RUNNING_CRITICAL) { + LOG.info("Trim memory: running critically low"); + // If we're not in the foreground, clear the UI to save memory + RunningAppProcessInfo info = new RunningAppProcessInfo(); + ActivityManager.getMyMemoryState(info); + if (info.importance != IMPORTANCE_FOREGROUND) hideUi(); + } else if (LOG.isLoggable(INFO)) { + LOG.info("Trim memory: unknown level " + level); + } + } else if (LOG.isLoggable(INFO)) { + LOG.info("Trim memory: unknown level " + level); + } + } + + private void hideUi() { + Intent i = new Intent(this, HideUiActivity.class); + i.addFlags(FLAG_ACTIVITY_NEW_TASK + | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | FLAG_ACTIVITY_NO_ANIMATION + | FLAG_ACTIVITY_CLEAR_TASK); + startActivity(i); + } + + private void shutdownFromBackground() { + // Stop the service + stopSelf(); + // Hide the UI + hideUi(); + // Wait for shutdown to complete, then exit + new Thread(() -> { + try { + if (started) lifecycleManager.waitForShutdown(); + } catch (InterruptedException e) { + LOG.info("Interrupted while waiting for shutdown"); + } + LOG.info("Exiting"); + System.exit(0); + }).start(); + } + + /** + * Waits for all services to start before returning. + */ + public void waitForStartup() throws InterruptedException { + lifecycleManager.waitForStartup(); + } + + /** + * Waits for all services to stop before returning. + */ + public void waitForShutdown() throws InterruptedException { + lifecycleManager.waitForShutdown(); + } + + /** + * Starts the shutdown process. + */ + public void shutdown() { + stopSelf(); // This will call onDestroy() + } + + public class BriarBinder extends Binder { + + /** + * Returns the bound service. + */ + public MailboxService getService() { + return MailboxService.this; + } + } + + public static class MailboxServiceConnection implements ServiceConnection { + + private final CountDownLatch binderLatch = new CountDownLatch(1); + + private volatile IBinder binder = null; + + @Override + public void onServiceConnected(ComponentName name, IBinder binder) { + this.binder = binder; + binderLatch.countDown(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + } + + /** + * Waits for the service to connect and returns its binder. + */ + public IBinder waitForBinder() throws InterruptedException { + binderLatch.await(); + return binder; + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/NetworkUsageLogger.java b/mailbox-android/src/main/java/org/briarproject/mailbox/NetworkUsageLogger.java new file mode 100644 index 0000000000000000000000000000000000000000..9de8b39ca4af917566281a00566f8705850a6c0c --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/NetworkUsageLogger.java @@ -0,0 +1,40 @@ +package org.briarproject.mailbox; + +import android.net.TrafficStats; +import android.os.Process; + +import org.briarproject.bramble.api.lifecycle.Service; + +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; +import static org.briarproject.bramble.util.LogUtils.now; + +class NetworkUsageLogger implements Service { + + private static final Logger LOG = + Logger.getLogger(NetworkUsageLogger.class.getName()); + + private volatile long startTime, rxBytes, txBytes; + + @Override + public void startService() { + startTime = now(); + int uid = Process.myUid(); + rxBytes = TrafficStats.getUidRxBytes(uid); + txBytes = TrafficStats.getUidTxBytes(uid); + } + + @Override + public void stopService() { + if (LOG.isLoggable(INFO)) { + long sessionDuration = now() - startTime; + int uid = Process.myUid(); + long rx = TrafficStats.getUidRxBytes(uid) - rxBytes; + long tx = TrafficStats.getUidTxBytes(uid) - txBytes; + LOG.info("Duration " + (sessionDuration / 1000) + " seconds"); + LOG.info("Received " + rx + " bytes"); + LOG.info("Sent " + tx + " bytes"); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/ReferenceManagerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/ReferenceManagerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..61ce2217d4c6689d2868ea47b8b188eea2a0928a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/ReferenceManagerImpl.java @@ -0,0 +1,84 @@ +package org.briarproject.mailbox; + + +import org.briarproject.mailbox.api.android.ReferenceManager; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.logging.Logger; + +import static java.util.logging.Level.INFO; + +class ReferenceManagerImpl implements ReferenceManager { + + private static final Logger LOG = + Logger.getLogger(ReferenceManagerImpl.class.getName()); + + private final Lock lock = new ReentrantLock(); + + // The following are locking: lock + private final Map<Class<?>, Map<Long, Object>> outerMap = new HashMap<>(); + private long nextHandle = 0; + + @Override + public <T> T getReference(long handle, Class<T> c) { + lock.lock(); + try { + Map<Long, Object> innerMap = outerMap.get(c); + if (innerMap == null) { + if (LOG.isLoggable(INFO)) + LOG.info("0 handles for " + c.getName()); + return null; + } + if (LOG.isLoggable(INFO)) + LOG.info(innerMap.size() + " handles for " + c.getName()); + Object o = innerMap.get(handle); + return c.cast(o); + } finally { + lock.unlock(); + } + + } + + @Override + public <T> long putReference(T reference, Class<T> c) { + lock.lock(); + try { + Map<Long, Object> innerMap = outerMap.get(c); + if (innerMap == null) { + innerMap = new HashMap<>(); + outerMap.put(c, innerMap); + } + long handle = nextHandle++; + innerMap.put(handle, reference); + if (LOG.isLoggable(INFO)) { + LOG.info(innerMap.size() + " handles for " + c.getName() + + " after put"); + } + return handle; + } finally { + lock.unlock(); + } + } + + @Override + public <T> T removeReference(long handle, Class<T> c) { + lock.lock(); + try { + Map<Long, Object> innerMap = outerMap.get(c); + if (innerMap == null) return null; + Object o = innerMap.remove(handle); + if (innerMap.isEmpty()) outerMap.remove(c); + if (LOG.isLoggable(INFO)) { + LOG.info(innerMap.size() + " handles for " + c.getName() + + " after remove"); + } + return c.cast(o); + } finally { + lock.unlock(); + } + + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/ScreenFilterMonitorImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/ScreenFilterMonitorImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..5b9c3e1474bdb374d319d3e284a092c8fc0f08b3 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/ScreenFilterMonitorImpl.java @@ -0,0 +1,229 @@ +package org.briarproject.mailbox; + +import android.annotation.SuppressLint; +import android.app.Application; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.lifecycle.Service; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.bramble.util.StringUtils; +import org.briarproject.mailbox.api.android.ScreenFilterMonitor; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.ACTION_PACKAGE_CHANGED; +import static android.content.Intent.ACTION_PACKAGE_REMOVED; +import static android.content.Intent.ACTION_PACKAGE_REPLACED; +import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; +import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; +import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; +import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.content.pm.PackageManager.GET_SIGNATURES; +import static android.os.Build.VERSION.SDK_INT; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class ScreenFilterMonitorImpl implements ScreenFilterMonitor, Service { + + private static final Logger LOG = + Logger.getLogger(ScreenFilterMonitorImpl.class.getName()); + + /* + * Ignore Play Services if it uses this package name and public key - it's + * effectively a system app, but not flagged as such on older systems + */ + private static final String PLAY_SERVICES_PACKAGE = + "com.google.android.gms"; + private static final String PLAY_SERVICES_PUBLIC_KEY = + "30820120300D06092A864886F70D01010105000382010D0030820108" + + "0282010100AB562E00D83BA208AE0A966F124E29DA11F2AB56D08F58" + + "E2CCA91303E9B754D372F640A71B1DCB130967624E4656A7776A9219" + + "3DB2E5BFB724A91E77188B0E6A47A43B33D9609B77183145CCDF7B2E" + + "586674C9E1565B1F4C6A5955BFF251A63DABF9C55C27222252E875E4" + + "F8154A645F897168C0B1BFC612EABF785769BB34AA7984DC7E2EA276" + + "4CAE8307D8C17154D7EE5F64A51A44A602C249054157DC02CD5F5C0E" + + "55FBEF8519FBE327F0B1511692C5A06F19D18385F5C4DBC2D6B93F68" + + "CC2979C70E18AB93866B3BD5DB8999552A0E3B4C99DF58FB918BEDC1" + + "82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" + + "0B145B6AA192858E79020103"; + + private static final String PREF_KEY_ALLOWED = "allowedOverlayApps"; + + private final PackageManager pm; + private final Application app; + private final AndroidExecutor androidExecutor; + private final SharedPreferences prefs; + private final AtomicBoolean used = new AtomicBoolean(false); + + // UiThread + @Nullable + private BroadcastReceiver receiver = null; + + // UiThread + @Nullable + private Collection<AppDetails> cachedApps = null; + + @Inject + ScreenFilterMonitorImpl(Application app, AndroidExecutor androidExecutor, + SharedPreferences prefs) { + pm = app.getPackageManager(); + this.app = app; + this.androidExecutor = androidExecutor; + this.prefs = prefs; + } + + @Override + @UiThread + public Collection<AppDetails> getApps() { + if (cachedApps != null) return cachedApps; + Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED, + Collections.emptySet()); + List<AppDetails> apps = new ArrayList<>(); + List<PackageInfo> packageInfos = + pm.getInstalledPackages(GET_PERMISSIONS); + for (PackageInfo packageInfo : packageInfos) { + if (!allowed.contains(packageInfo.packageName) + && isOverlayApp(packageInfo)) { + String name = getAppName(packageInfo); + apps.add(new AppDetails(name, packageInfo.packageName)); + } + } + Collections.sort(apps, (a, b) -> a.name.compareTo(b.name)); + apps = Collections.unmodifiableList(apps); + cachedApps = apps; + return apps; + } + + @Override + @UiThread + public void allowApps(Collection<String> packageNames) { + cachedApps = null; + Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED, + Collections.emptySet()); + Set<String> merged = new HashSet<>(allowed); + merged.addAll(packageNames); + prefs.edit().putStringSet(PREF_KEY_ALLOWED, merged).apply(); + } + + // Returns the application name for a given package, or the package name + // if no application name is available + private String getAppName(PackageInfo pkgInfo) { + CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo); + return seq == null ? pkgInfo.packageName : seq.toString(); + } + + // Checks if an installed package is a user app using the permission. + private boolean isOverlayApp(PackageInfo packageInfo) { + int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP; + // Ignore system apps + if ((packageInfo.applicationInfo.flags & mask) != 0) return false; + // Ignore Play Services, it's effectively a system app + if (isPlayServices(packageInfo.packageName)) return false; + // Get permissions + String[] requestedPermissions = packageInfo.requestedPermissions; + if (requestedPermissions == null) return false; + if (SDK_INT >= 16 && SDK_INT < 23) { + // Check whether the permission has been requested and granted + int[] flags = packageInfo.requestedPermissionsFlags; + for (int i = 0; i < requestedPermissions.length; i++) { + if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)) { + // 'flags' may be null on Robolectric + return flags == null || + (flags[i] & REQUESTED_PERMISSION_GRANTED) != 0; + } + } + } else { + // Check whether the permission has been requested + for (String requestedPermission : requestedPermissions) { + if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) { + return true; + } + } + } + return false; + } + + @SuppressLint("PackageManagerGetSignatures") + private boolean isPlayServices(String pkg) { + if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false; + try { + PackageInfo sigs = pm.getPackageInfo(pkg, GET_SIGNATURES); + // The genuine Play Services app should have a single signature + Signature[] signatures = sigs.signatures; + if (signatures == null || signatures.length != 1) return false; + // Extract the public key from the signature + CertificateFactory certFactory = + CertificateFactory.getInstance("X509"); + byte[] signatureBytes = signatures[0].toByteArray(); + InputStream in = new ByteArrayInputStream(signatureBytes); + X509Certificate cert = + (X509Certificate) certFactory.generateCertificate(in); + byte[] publicKeyBytes = cert.getPublicKey().getEncoded(); + String publicKey = StringUtils.toHexString(publicKeyBytes); + return PLAY_SERVICES_PUBLIC_KEY.equals(publicKey); + } catch (NameNotFoundException | CertificateException e) { + logException(LOG, WARNING, e); + return false; + } + } + + @Override + public void startService() { + if (used.getAndSet(true)) throw new IllegalStateException(); + androidExecutor.runOnUiThread(() -> { + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_PACKAGE_ADDED); + filter.addAction(ACTION_PACKAGE_CHANGED); + filter.addAction(ACTION_PACKAGE_REMOVED); + filter.addAction(ACTION_PACKAGE_REPLACED); + filter.addDataScheme("package"); + receiver = new PackageBroadcastReceiver(); + app.registerReceiver(receiver, filter); + cachedApps = null; + }); + } + + @Override + public void stopService() { + androidExecutor.runOnUiThread(() -> { + if (receiver != null) app.unregisterReceiver(receiver); + }); + } + + private class PackageBroadcastReceiver extends BroadcastReceiver { + + @Override + @UiThread + public void onReceive(Context context, Intent intent) { + cachedApps = null; + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/StartupFailureActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/StartupFailureActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..d0b4f976ff1f950332b3c8e6e83c216cb62b9da8 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/StartupFailureActivity.java @@ -0,0 +1,72 @@ +package org.briarproject.mailbox; + +import android.app.NotificationManager; +import android.content.Intent; +import android.os.Bundle; + +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.BaseActivity; +import org.briarproject.mailbox.fragment.BaseFragment; +import org.briarproject.mailbox.fragment.ErrorFragment; + +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.StartResult; +import static org.briarproject.mailbox.MailboxService.EXTRA_NOTIFICATION_ID; +import static org.briarproject.mailbox.MailboxService.EXTRA_START_RESULT; + +public class StartupFailureActivity extends BaseActivity implements + BaseFragment.BaseFragmentListener { + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + setContentView(R.layout.activity_fragment_container); + handleIntent(getIntent()); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + private void handleIntent(Intent i) { + StartResult result = + (StartResult) i.getSerializableExtra(EXTRA_START_RESULT); + int notificationId = i.getIntExtra(EXTRA_NOTIFICATION_ID, -1); + + // cancel notification + if (notificationId > -1) { + Object o = getSystemService(NOTIFICATION_SERVICE); + NotificationManager nm = (NotificationManager) o; + nm.cancel(notificationId); + } + + // show proper error message + String errorMsg; + switch (result) { + case DATA_TOO_OLD_ERROR: + errorMsg = getString(R.string.startup_failed_db_error); + break; + case DATA_TOO_NEW_ERROR: + errorMsg = + getString(R.string.startup_failed_data_too_new_error); + break; + case DB_ERROR: + errorMsg = + getString(R.string.startup_failed_data_too_old_error); + break; + case SERVICE_ERROR: + errorMsg = getString(R.string.startup_failed_service_error); + break; + default: + throw new IllegalArgumentException(); + } + showInitialFragment(ErrorFragment.newInstance(errorMsg)); + } + + @Override + public void runOnDbThread(Runnable runnable) { + throw new AssertionError("Deprecated and should not be used"); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/TestingConstants.java b/mailbox-android/src/main/java/org/briarproject/mailbox/TestingConstants.java new file mode 100644 index 0000000000000000000000000000000000000000..b5dc4d8bef5ec0b0c8f0f4239af75c607c2b1f66 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/TestingConstants.java @@ -0,0 +1,41 @@ +package org.briarproject.mailbox; + +public interface TestingConstants { + + /** + * Whether this is a debug build. + */ + boolean IS_DEBUG_BUILD = BuildConfig.DEBUG; + + /** + * Whether this is a beta build. This should be set to false for final + * release builds. + */ + boolean IS_BETA_BUILD = false; + + /** + * Whether to prevent screenshots from being taken. Setting this to true + * prevents Recent Apps from storing screenshots of private information. + * Unfortunately this also prevents the user from taking screenshots + * intentionally. + */ + boolean PREVENT_SCREENSHOTS = !IS_DEBUG_BUILD; + + /** + * Debug and beta builds expire after 90 days. Final release builds expire + * after 292 million years. + */ + long EXPIRY_DATE = IS_DEBUG_BUILD || IS_BETA_BUILD ? + BuildConfig.BuildTimestamp + 90 * 24 * 60 * 60 * 1000L : + Long.MAX_VALUE; + + /** + * Feature flag for enabling the dark UI theme in release builds. + */ + boolean FEATURE_FLAG_DARK_THEME = false; + + /** + * Feature flag for enabling the sign-in reminder in release builds. + */ + boolean FEATURE_FLAG_SIGN_IN_REMINDER = IS_DEBUG_BUILD; +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityComponent.java b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..b317510292754e7d6712ab691e791fe483ff8b24 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityComponent.java @@ -0,0 +1,71 @@ +package org.briarproject.mailbox.activity; + +import android.app.Activity; + +import org.briarproject.mailbox.AndroidComponent; +import org.briarproject.mailbox.StartupFailureActivity; +import org.briarproject.mailbox.overview.OverviewFragment; +import org.briarproject.mailbox.fragment.ScreenFilterDialogFragment; +import org.briarproject.mailbox.keyagreement.IntroFragment; +import org.briarproject.mailbox.keyagreement.KeyAgreementActivity; +import org.briarproject.mailbox.keyagreement.KeyAgreementFragment; +import org.briarproject.mailbox.keyagreement.MailboxExchangeActivity; +import org.briarproject.mailbox.login.AuthorNameFragment; +import org.briarproject.mailbox.login.ChangePasswordActivity; +import org.briarproject.mailbox.login.DozeFragment; +import org.briarproject.mailbox.login.OpenDatabaseActivity; +import org.briarproject.mailbox.login.PasswordActivity; +import org.briarproject.mailbox.login.PasswordFragment; +import org.briarproject.mailbox.login.SetupActivity; +import org.briarproject.mailbox.navdrawer.NavDrawerActivity; +import org.briarproject.mailbox.settings.SettingsActivity; +import org.briarproject.mailbox.settings.SettingsFragment; +import org.briarproject.mailbox.splash.SplashScreenActivity; + +import dagger.Component; + +@ActivityScope +@Component( + modules = {ActivityModule.class}, + dependencies = AndroidComponent.class) +public interface ActivityComponent { + + Activity activity(); + + void inject(SplashScreenActivity activity); + + void inject(SetupActivity activity); + + void inject(OpenDatabaseActivity activity); + + void inject(NavDrawerActivity activity); + + void inject(PasswordActivity activity); + + void inject(KeyAgreementActivity activity); + + void inject(MailboxExchangeActivity activity); + + void inject(SettingsActivity activity); + + void inject(ChangePasswordActivity activity); + + void inject(StartupFailureActivity activity); + + // Fragments + void inject(OverviewFragment fragment); + + void inject(AuthorNameFragment fragment); + + void inject(PasswordFragment fragment); + + void inject(DozeFragment fragment); + + void inject(IntroFragment fragment); + + void inject(KeyAgreementFragment fragment); + + void inject(SettingsFragment fragment); + + void inject(ScreenFilterDialogFragment fragment); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityModule.java b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityModule.java new file mode 100644 index 0000000000000000000000000000000000000000..c334c96dafce53bee6f1db57185215e7376ca241 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityModule.java @@ -0,0 +1,94 @@ +package org.briarproject.mailbox.activity; + +import android.app.Activity; + +import org.briarproject.mailbox.controller.ConfigController; +import org.briarproject.mailbox.controller.ConfigControllerImpl; +import org.briarproject.mailbox.controller.DbController; +import org.briarproject.mailbox.controller.DbControllerImpl; +import org.briarproject.mailbox.controller.MailboxController; +import org.briarproject.mailbox.controller.MailboxControllerImpl; +import org.briarproject.mailbox.login.PasswordController; +import org.briarproject.mailbox.login.PasswordControllerImpl; +import org.briarproject.mailbox.login.SetupController; +import org.briarproject.mailbox.login.SetupControllerImpl; +import org.briarproject.mailbox.navdrawer.NavDrawerController; +import org.briarproject.mailbox.navdrawer.NavDrawerControllerImpl; + +import dagger.Module; +import dagger.Provides; + +import static org.briarproject.mailbox.MailboxService.MailboxServiceConnection; + + +@Module +public class ActivityModule { + + private final BaseActivity activity; + + public ActivityModule(BaseActivity activity) { + this.activity = activity; + } + + @ActivityScope + @Provides + BaseActivity provideBaseActivity() { + return activity; + } + + @ActivityScope + @Provides + Activity provideActivity() { + return activity; + } + + @ActivityScope + @Provides + SetupController provideSetupController( + SetupControllerImpl setupController) { + return setupController; + } + + @ActivityScope + @Provides + ConfigController provideConfigController( + ConfigControllerImpl configController) { + return configController; + } + + @ActivityScope + @Provides + PasswordController providePasswordController( + PasswordControllerImpl passwordController) { + return passwordController; + } + + @ActivityScope + @Provides + protected MailboxController provideMailboxController( + MailboxControllerImpl mailboxController) { + activity.addLifecycleController(mailboxController); + return mailboxController; + } + + @ActivityScope + @Provides + DbController provideDBController(DbControllerImpl dbController) { + return dbController; + } + + @ActivityScope + @Provides + NavDrawerController provideNavDrawerController( + NavDrawerControllerImpl navDrawerController) { + activity.addLifecycleController(navDrawerController); + return navDrawerController; + } + + @ActivityScope + @Provides + MailboxServiceConnection provideMailboxServiceConnection() { + return new MailboxServiceConnection(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityScope.java b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityScope.java new file mode 100644 index 0000000000000000000000000000000000000000..f09232a762de9bf3efae68be958c1d03149da2ac --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/ActivityScope.java @@ -0,0 +1,11 @@ +package org.briarproject.mailbox.activity; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface ActivityScope { +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/activity/BaseActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/BaseActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..a4bda1264486de4a4f5d9d675bf1b77b39530033 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/BaseActivity.java @@ -0,0 +1,272 @@ +package org.briarproject.mailbox.activity; + +import android.content.Context; +import android.os.Bundle; +import android.os.IBinder; +import android.support.annotation.LayoutRes; +import android.support.annotation.UiThread; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; +import android.view.inputmethod.InputMethodManager; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.mailbox.AndroidComponent; +import org.briarproject.mailbox.DestroyableContext; +import org.briarproject.mailbox.Localizer; +import org.briarproject.mailbox.MailboxApplication; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.api.android.ScreenFilterMonitor; +import org.briarproject.mailbox.controller.ActivityLifecycleController; +import org.briarproject.mailbox.fragment.BaseFragment; +import org.briarproject.mailbox.fragment.ScreenFilterDialogFragment; +import org.briarproject.mailbox.util.UiUtils; +import org.briarproject.mailbox.widget.TapSafeFrameLayout; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT; +import static org.briarproject.mailbox.TestingConstants.PREVENT_SCREENSHOTS; +import static org.briarproject.mailbox.api.android.ScreenFilterMonitor.*; +import static org.briarproject.mailbox.widget.TapSafeFrameLayout.OnTapFilteredListener; + +public abstract class BaseActivity extends AppCompatActivity + implements DestroyableContext, OnTapFilteredListener { + + @Inject + protected ScreenFilterMonitor screenFilterMonitor; + + protected ActivityComponent activityComponent; + + private final List<ActivityLifecycleController> lifecycleControllers = + new ArrayList<>(); + private boolean destroyed = false; + + @Nullable + private Toolbar toolbar = null; + private boolean searchedForToolbar = false; + + public abstract void injectActivity(ActivityComponent component); + + public void addLifecycleController(ActivityLifecycleController alc) { + lifecycleControllers.add(alc); + } + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + + if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); + + AndroidComponent applicationComponent = + ((MailboxApplication) getApplication()).getApplicationComponent(); + + activityComponent = DaggerActivityComponent.builder() + .androidComponent(applicationComponent) + .activityModule(getActivityModule()) + .build(); + + injectActivity(activityComponent); + + for (ActivityLifecycleController alc : lifecycleControllers) { + alc.onActivityCreate(this); + } + } + + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext( + Localizer.getInstance().setLocale(base)); + } + + public ActivityComponent getActivityComponent() { + return activityComponent; + } + + // This exists to make test overrides easier + protected ActivityModule getActivityModule() { + return new ActivityModule(this); + } + + @Override + protected void onStart() { + super.onStart(); + for (ActivityLifecycleController alc : lifecycleControllers) { + alc.onActivityStart(); + } + protectToolbar(); + ScreenFilterDialogFragment f = findDialogFragment(); + if (f != null) f.setDismissListener(this::protectToolbar); + } + + @Nullable + private ScreenFilterDialogFragment findDialogFragment() { + Fragment f = getSupportFragmentManager().findFragmentByTag( + ScreenFilterDialogFragment.TAG); + return (ScreenFilterDialogFragment) f; + } + + @Override + protected void onStop() { + super.onStop(); + for (ActivityLifecycleController alc : lifecycleControllers) { + alc.onActivityStop(); + } + } + + protected void showInitialFragment(BaseFragment f) { + getSupportFragmentManager().beginTransaction() + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .commit(); + } + + public void showNextFragment(BaseFragment f) { + getSupportFragmentManager().beginTransaction() + .setCustomAnimations(R.anim.step_next_in, + R.anim.step_previous_out, R.anim.step_previous_in, + R.anim.step_next_out) + .replace(R.id.fragmentContainer, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); + } + + private boolean showScreenFilterWarning() { + // If the dialog is already visible, filter the tap + ScreenFilterDialogFragment f = findDialogFragment(); + if (f != null && f.isVisible()) return false; + Collection<AppDetails> apps = screenFilterMonitor.getApps(); + // If all overlay apps have been allowed, allow the tap + if (apps.isEmpty()) return true; + // Show dialog unless onSaveInstanceState() has been called, see #1112 + FragmentManager fm = getSupportFragmentManager(); + if (!fm.isStateSaved()) { + // Create dialog + f = ScreenFilterDialogFragment.newInstance(apps); + // When dialog is dismissed, update protection of toolbar + f.setDismissListener(this::protectToolbar); + // Hide soft keyboard when (re)showing dialog + View focus = getCurrentFocus(); + if (focus != null) hideSoftKeyboard(focus); + f.show(fm, ScreenFilterDialogFragment.TAG); + } + // Filter the tap + return false; + } + + @Override + protected void onDestroy() { + super.onDestroy(); + destroyed = true; + for (ActivityLifecycleController alc : lifecycleControllers) { + alc.onActivityDestroy(); + } + } + + @Override + public void runOnUiThreadUnlessDestroyed(Runnable r) { + runOnUiThread(() -> { + if (!destroyed && !isFinishing()) r.run(); + }); + } + + public void showSoftKeyboard(View view) { + Object o = getSystemService(INPUT_METHOD_SERVICE); + ((InputMethodManager) o).showSoftInput(view, SHOW_IMPLICIT); + } + + public void hideSoftKeyboard(View view) { + IBinder token = view.getWindowToken(); + Object o = getSystemService(INPUT_METHOD_SERVICE); + ((InputMethodManager) o).hideSoftInputFromWindow(token, 0); + } + + @UiThread + public void handleDbException(DbException e) { + supportFinishAfterTransition(); + } + + /* + * Wraps the given view in a wrapper that notifies this activity when an + * obscured touch has been filtered, and returns the wrapper. + */ + private View makeTapSafeWrapper(View v) { + TapSafeFrameLayout wrapper = new TapSafeFrameLayout(this); + wrapper.setLayoutParams(new LayoutParams(MATCH_PARENT, MATCH_PARENT)); + wrapper.setOnTapFilteredListener(this); + wrapper.addView(v); + return wrapper; + } + + /* + * Finds the AppCompat toolbar, if any, and configures it to filter + * obscured touches. If a custom toolbar is used, it will be part of the + * content view and thus protected by the wrapper. But the default toolbar + * is outside the wrapper. + */ + private void protectToolbar() { + findToolbar(); + if (toolbar != null) { + boolean filter = !screenFilterMonitor.getApps().isEmpty(); + UiUtils.setFilterTouchesWhenObscured(toolbar, filter); + } + } + + private void findToolbar() { + if (searchedForToolbar) return; + View decorView = getWindow().getDecorView(); + if (decorView instanceof ViewGroup) + toolbar = findToolbar((ViewGroup) decorView); + searchedForToolbar = true; + } + + @Nullable + private Toolbar findToolbar(ViewGroup vg) { + // Views inside tap-safe layouts are already protected + if (vg instanceof TapSafeFrameLayout) return null; + for (int i = 0, len = vg.getChildCount(); i < len; i++) { + View child = vg.getChildAt(i); + if (child instanceof Toolbar) return (Toolbar) child; + if (child instanceof ViewGroup) { + Toolbar toolbar = findToolbar((ViewGroup) child); + if (toolbar != null) return toolbar; + } + } + return null; + } + + @Override + public void setContentView(@LayoutRes int layoutRes) { + setContentView(getLayoutInflater().inflate(layoutRes, null)); + } + + @Override + public void setContentView(View v) { + super.setContentView(makeTapSafeWrapper(v)); + } + + @Override + public void setContentView(View v, LayoutParams layoutParams) { + super.setContentView(makeTapSafeWrapper(v), layoutParams); + } + + @Override + public void addContentView(View v, LayoutParams layoutParams) { + super.addContentView(makeTapSafeWrapper(v), layoutParams); + } + + @Override + public boolean shouldAllowTap() { + return showScreenFilterWarning(); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/activity/MailboxActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/MailboxActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..27a45cadefd5ebd21432d4328ac088aaeebd2fd2 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/MailboxActivity.java @@ -0,0 +1,178 @@ +package org.briarproject.mailbox.activity; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.Toolbar; +import android.transition.Slide; +import android.transition.Transition; +import android.view.Gravity; +import android.view.Window; +import android.widget.CheckBox; + +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.controller.DbController; +import org.briarproject.mailbox.controller.MailboxController; +import org.briarproject.mailbox.controller.handler.UiResultHandler; +import org.briarproject.mailbox.login.PasswordActivity; +import org.briarproject.mailbox.logout.ExitActivity; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION; +import static android.os.Build.VERSION.SDK_INT; +import static org.briarproject.mailbox.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; +import static org.briarproject.mailbox.activity.RequestCodes.REQUEST_PASSWORD; +import static org.briarproject.mailbox.util.UiUtils.getDozeWhitelistingIntent; +import static org.briarproject.mailbox.util.UiUtils.isSamsung7; + +@SuppressLint("Registered") +public abstract class MailboxActivity extends BaseActivity { + + private static final Logger LOG = + Logger.getLogger(MailboxActivity.class.getName()); + + @Inject + MailboxController mailboxController; + + @Deprecated + @Inject + DbController dbController; + + @Override + protected void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_PASSWORD) { + if (result == RESULT_OK) mailboxController.startAndBindService(); + else supportFinishAfterTransition(); + } + } + + @Override + public void onStart() { + super.onStart(); + if (!mailboxController.hasEncryptionKey() && !isFinishing()) { + Intent i = new Intent(this, PasswordActivity.class); + startActivityForResult(i, REQUEST_PASSWORD); + } else if (SDK_INT >= 23) { + mailboxController.hasDozed(new UiResultHandler<Boolean>(this) { + @Override + public void onResultUi(Boolean result) { + if (result) { + showDozeDialog(getString(R.string.warning_dozed, + getString(R.string.app_name))); + } + } + }); + } + } + + public void setSceneTransitionAnimation() { + if (SDK_INT < 21) return; + // workaround for #1007 + if (isSamsung7()) { + return; + } + Transition slide = new Slide(Gravity.RIGHT); + slide.excludeTarget(android.R.id.statusBarBackground, true); + slide.excludeTarget(android.R.id.navigationBarBackground, true); + Window window = getWindow(); + window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS); + window.setExitTransition(slide); + window.setEnterTransition(slide); + window.setTransitionBackgroundFadeDuration(getResources() + .getInteger(android.R.integer.config_longAnimTime)); + } + + /** + * This should be called after the content view has been added in onCreate() + * + * @param ownLayout true if the custom toolbar brings its own layout + * @return the Toolbar object or null if content view did not contain one + */ + @Nullable + protected Toolbar setUpCustomToolbar(boolean ownLayout) { + // Custom Toolbar + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setDisplayShowHomeEnabled(true); + ab.setDisplayHomeAsUpEnabled(true); + ab.setDisplayShowCustomEnabled(ownLayout); + ab.setDisplayShowTitleEnabled(!ownLayout); + } + return toolbar; + } + + protected void showDozeDialog(String message) { + AlertDialog.Builder b = + new AlertDialog.Builder(this, R.style.BriarDialogTheme); + b.setMessage(message); + b.setView(R.layout.checkbox); + b.setPositiveButton(R.string.fix, + (dialog, which) -> { + Intent i = getDozeWhitelistingIntent(MailboxActivity.this); + startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + dialog.dismiss(); + }); + b.setNegativeButton(R.string.cancel, + (dialog, which) -> dialog.dismiss()); + b.setOnDismissListener(dialog -> { + CheckBox checkBox = + ((AlertDialog) dialog).findViewById(R.id.checkbox); + if (checkBox.isChecked()) + mailboxController.doNotAskAgainForDozeWhiteListing(); + }); + b.show(); + } + + protected void signOut(boolean removeFromRecentApps) { + if (mailboxController.hasEncryptionKey()) { + // Don't use UiResultHandler because we want the result even if + // this activity has been destroyed + mailboxController.signOut(result -> runOnUiThread( + () -> exit(removeFromRecentApps))); + } else { + exit(removeFromRecentApps); + } + } + + private void exit(boolean removeFromRecentApps) { + if (removeFromRecentApps) startExitActivity(); + else finishAndExit(); + } + + private void startExitActivity() { + Intent i = new Intent(this, ExitActivity.class); + i.addFlags(FLAG_ACTIVITY_NEW_TASK + | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | FLAG_ACTIVITY_NO_ANIMATION + | FLAG_ACTIVITY_CLEAR_TASK); + startActivity(i); + } + + private void finishAndExit() { + if (SDK_INT >= 21) finishAndRemoveTask(); + else supportFinishAfterTransition(); + LOG.info("Exiting"); + System.exit(0); + } + + @Deprecated + public void runOnDbThread(Runnable task) { + dbController.runOnDbThread(task); + } + + @Deprecated + protected void finishOnUiThread() { + runOnUiThreadUnlessDestroyed(this::supportFinishAfterTransition); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/activity/RequestCodes.java b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/RequestCodes.java new file mode 100644 index 0000000000000000000000000000000000000000..4f23a7827b7aed4f125439aaf50c79af632b24fc --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/activity/RequestCodes.java @@ -0,0 +1,10 @@ +package org.briarproject.mailbox.activity; + +public interface RequestCodes { + + int REQUEST_PASSWORD = 1; + int REQUEST_PERMISSION_CAMERA = 8; + int REQUEST_DOZE_WHITELISTING = 9; + int REQUEST_ENABLE_BLUETOOTH = 10; + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/AndroidNotificationManager.java b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/AndroidNotificationManager.java new file mode 100644 index 0000000000000000000000000000000000000000..941829bc1bf407ba3c22a09cf8bc625f45871556 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/AndroidNotificationManager.java @@ -0,0 +1,81 @@ +package org.briarproject.mailbox.api.android; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.sync.GroupId; + +/** + * Manages notifications for private messages, forum posts, blog posts and + * introductions. + */ +public interface AndroidNotificationManager { + + // Keys for notification preferences + String PREF_NOTIFY_PRIVATE = "notifyPrivateMessages"; + String PREF_NOTIFY_GROUP = "notifyGroupMessages"; + String PREF_NOTIFY_FORUM = "notifyForumPosts"; + String PREF_NOTIFY_BLOG = "notifyBlogPosts"; + + String PREF_NOTIFY_SOUND = "notifySound"; + String PREF_NOTIFY_RINGTONE_NAME = "notifyRingtoneName"; + String PREF_NOTIFY_RINGTONE_URI = "notifyRingtoneUri"; + String PREF_NOTIFY_VIBRATION = "notifyVibration"; + String PREF_NOTIFY_LOCK_SCREEN = "notifyLockScreen"; + + // Notification IDs + int ONGOING_NOTIFICATION_ID = 1; + int FAILURE_NOTIFICATION_ID = 2; + int REMINDER_NOTIFICATION_ID = 3; + int PRIVATE_MESSAGE_NOTIFICATION_ID = 4; + int GROUP_MESSAGE_NOTIFICATION_ID = 5; + int FORUM_POST_NOTIFICATION_ID = 6; + int BLOG_POST_NOTIFICATION_ID = 7; + int INTRODUCTION_SUCCESS_NOTIFICATION_ID = 8; + + // Channel IDs + String CONTACT_CHANNEL_ID = "contacts"; + String GROUP_CHANNEL_ID = "groups"; + String FORUM_CHANNEL_ID = "forums"; + String BLOG_CHANNEL_ID = "blogs"; + // Channels are sorted by channel ID in the Settings app, so use IDs + // that will sort below the main channels such as contacts + String ONGOING_CHANNEL_ID = "zForegroundService"; + String FAILURE_CHANNEL_ID = "zStartupFailure"; + String REMINDER_CHANNEL_ID = "zSignInReminder"; + + // Content URIs for pending intents + String CONTACT_URI = "content://org.briarproject.briar/contact"; + String GROUP_URI = "content://org.briarproject.briar/group"; + String FORUM_URI = "content://org.briarproject.briar/forum"; + String BLOG_URI = "content://org.briarproject.briar/blog"; + String INTRODUCTION_URI = "content://org.briarproject.briar/introduction"; + + void clearContactNotification(ContactId c); + + void clearAllContactNotifications(); + + void clearGroupMessageNotification(GroupId g); + + void clearAllGroupMessageNotifications(); + + void clearForumPostNotification(GroupId g); + + void clearAllForumPostNotifications(); + + void clearBlogPostNotification(GroupId g); + + void clearAllBlogPostNotifications(); + + void clearAllIntroductionNotifications(); + + void blockContactNotification(ContactId c); + + void unblockContactNotification(ContactId c); + + void blockNotification(GroupId g); + + void unblockNotification(GroupId g); + + void blockAllBlogPostNotifications(); + + void unblockAllBlogPostNotifications(); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/DozeWatchdog.java b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/DozeWatchdog.java new file mode 100644 index 0000000000000000000000000000000000000000..f6cc5f20c6cb621d0ba6a0571ea29c48d6934044 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/DozeWatchdog.java @@ -0,0 +1,6 @@ +package org.briarproject.mailbox.api.android; + +public interface DozeWatchdog { + + boolean getAndResetDozeFlag(); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/ReferenceManager.java b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/ReferenceManager.java new file mode 100644 index 0000000000000000000000000000000000000000..22b0af532976aed52a3c9fa1184ca0e8313e5d05 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/ReferenceManager.java @@ -0,0 +1,27 @@ +package org.briarproject.mailbox.api.android; + +/** + * Manages mappings between object references and serialisable handles. This + * enables references to be passed between Android UI objects that belong to + * the same process but can only communicate via serialisation. + */ +public interface ReferenceManager { + + /** + * Returns the object with the given handle, or null if no mapping exists + * for the handle. + */ + <T> T getReference(long handle, Class<T> c); + + /** + * Creates a mapping between the given reference and a handle, and returns + * the handle. + */ + <T> long putReference(T reference, Class<T> c); + + /** + * Removes and returns the object with the given handle, or returns null + * if no mapping exists for the handle. + */ + <T> T removeReference(long handle, Class<T> c); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/ScreenFilterMonitor.java b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/ScreenFilterMonitor.java new file mode 100644 index 0000000000000000000000000000000000000000..40ecc0e8b6f416cfeec98afd973aee59d699583f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/api/android/ScreenFilterMonitor.java @@ -0,0 +1,38 @@ +package org.briarproject.mailbox.api.android; + +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.Collection; + +@NotNullByDefault +public interface ScreenFilterMonitor { + + /** + * Returns the details of all apps that have requested the + * SYSTEM_ALERT_WINDOW permission, excluding system apps, Google Play + * Services, and any apps that have been allowed by calling + * {@link #allowApps(Collection)}. + */ + @UiThread + Collection<AppDetails> getApps(); + + /** + * Allows the apps with the given package names to use overlay windows. + * They will not be returned by future calls to {@link #getApps()}. + */ + @UiThread + void allowApps(Collection<String> packageNames); + + class AppDetails { + + public final String name; + public final String packageName; + + public AppDetails(String name, String packageName) { + this.name = name; + this.packageName = packageName; + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ActivityLifecycleController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ActivityLifecycleController.java new file mode 100644 index 0000000000000000000000000000000000000000..abbe3462df2b1063195561b6ac54f4c9d1f3b024 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ActivityLifecycleController.java @@ -0,0 +1,14 @@ +package org.briarproject.mailbox.controller; + +import android.app.Activity; + +public interface ActivityLifecycleController { + + void onActivityCreate(Activity activity); + + void onActivityStart(); + + void onActivityStop(); + + void onActivityDestroy(); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ConfigController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ConfigController.java new file mode 100644 index 0000000000000000000000000000000000000000..cc0189031b38274c5fa2ff89e9de088655997009 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ConfigController.java @@ -0,0 +1,23 @@ +package org.briarproject.mailbox.controller; + +import android.content.Context; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import javax.annotation.Nullable; + +@NotNullByDefault +public interface ConfigController { + + @Nullable + String getEncryptedDatabaseKey(); + + boolean storeEncryptedDatabaseKey(String hex); + + void deleteAccount(Context ctx); + + boolean accountExists(); + + boolean accountSignedIn(); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ConfigControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ConfigControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d56acadc53aa9335e0bd238ff890d4650bdf32ec --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/ConfigControllerImpl.java @@ -0,0 +1,171 @@ +package org.briarproject.mailbox.controller; + +import android.content.Context; +import android.content.SharedPreferences; +import android.support.v7.preference.PreferenceManager; + +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.util.AndroidUtils; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +public class ConfigControllerImpl implements ConfigController { + + private static final Logger LOG = + Logger.getLogger(ConfigControllerImpl.class.getName()); + + private static final String PREF_DB_KEY = "key"; + private static final String DB_KEY_FILENAME = "db.key"; + private static final String DB_KEY_BACKUP_FILENAME = "db.key.bak"; + + private final SharedPreferences briarPrefs; + private final File dbKeyFile, dbKeyBackupFile; + protected final DatabaseConfig databaseConfig; + + @Inject + public ConfigControllerImpl(SharedPreferences briarPrefs, + DatabaseConfig databaseConfig) { + this.briarPrefs = briarPrefs; + this.databaseConfig = databaseConfig; + File keyDir = databaseConfig.getDatabaseKeyDirectory(); + dbKeyFile = new File(keyDir, DB_KEY_FILENAME); + dbKeyBackupFile = new File(keyDir, DB_KEY_BACKUP_FILENAME); + } + + @Override + @Nullable + public String getEncryptedDatabaseKey() { + String key = getDatabaseKeyFromPreferences(); + if (key == null) key = getDatabaseKeyFromFile(); + else migrateDatabaseKeyToFile(key); + return key; + } + + @Nullable + private String getDatabaseKeyFromPreferences() { + String key = briarPrefs.getString(PREF_DB_KEY, null); + if (key == null) LOG.info("No database key in preferences"); + else LOG.info("Found database key in preferences"); + return key; + } + + @Nullable + private String getDatabaseKeyFromFile() { + String key = readDbKeyFromFile(dbKeyFile); + if (key == null) { + LOG.info("No database key in primary file"); + key = readDbKeyFromFile(dbKeyBackupFile); + if (key == null) LOG.info("No database key in backup file"); + else LOG.warning("Found database key in backup file"); + } else { + LOG.info("Found database key in primary file"); + } + return key; + } + + @Nullable + private String readDbKeyFromFile(File f) { + if (!f.exists()) { + LOG.info("Key file does not exist"); + return null; + } + try { + BufferedReader reader = new BufferedReader(new InputStreamReader( + new FileInputStream(f), "UTF-8")); + String key = reader.readLine(); + reader.close(); + return key; + } catch (IOException e) { + logException(LOG, WARNING, e); + return null; + } + } + + private void migrateDatabaseKeyToFile(String key) { + if (storeEncryptedDatabaseKey(key)) { + if (briarPrefs.edit().remove(PREF_DB_KEY).commit()) + LOG.info("Database key migrated to file"); + else LOG.warning("Database key not removed from preferences"); + } else { + LOG.warning("Database key not migrated to file"); + } + } + + @Override + public boolean storeEncryptedDatabaseKey(String hex) { + LOG.info("Storing database key in file"); + // Create the directory if necessary + if (databaseConfig.getDatabaseKeyDirectory().mkdirs()) + LOG.info("Created database key directory"); + // If only the backup file exists, rename it so we don't overwrite it + if (dbKeyBackupFile.exists() && !dbKeyFile.exists()) { + if (dbKeyBackupFile.renameTo(dbKeyFile)) + LOG.info("Renamed old backup"); + else LOG.warning("Failed to rename old backup"); + } + try { + // Write to the backup file + writeDbKeyToFile(hex, dbKeyBackupFile); + LOG.info("Stored database key in backup file"); + // Delete the old primary file, if it exists + if (dbKeyFile.exists()) { + if (dbKeyFile.delete()) LOG.info("Deleted primary file"); + else LOG.warning("Failed to delete primary file"); + } + // The backup file becomes the new primary + if (dbKeyBackupFile.renameTo(dbKeyFile)) { + LOG.info("Renamed backup file to primary"); + } else { + LOG.warning("Failed to rename backup file to primary"); + return false; // Don't overwrite our only copy + } + // Write a second copy to the backup file + writeDbKeyToFile(hex, dbKeyBackupFile); + LOG.info("Stored second copy of database key in backup file"); + return true; + } catch (IOException e) { + logException(LOG, WARNING, e); + return false; + } + } + + private void writeDbKeyToFile(String key, File f) throws IOException { + FileOutputStream out = new FileOutputStream(f); + out.write(key.getBytes("UTF-8")); + out.flush(); + out.close(); + } + + @Override + public void deleteAccount(Context ctx) { + LOG.info("Deleting account"); + SharedPreferences defaultPrefs = + PreferenceManager.getDefaultSharedPreferences(ctx); + AndroidUtils.deleteAppData(ctx, briarPrefs, defaultPrefs); + } + + @Override + public boolean accountExists() { + String hex = getEncryptedDatabaseKey(); + return hex != null && databaseConfig.databaseExists(); + } + + @Override + public boolean accountSignedIn() { + return databaseConfig.getEncryptionKey() != null; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/DbController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/DbController.java new file mode 100644 index 0000000000000000000000000000000000000000..98176151e13629880c3b04022d4e73d56ce54c56 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/DbController.java @@ -0,0 +1,9 @@ +package org.briarproject.mailbox.controller; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +public interface DbController { + + void runOnDbThread(Runnable task); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/DbControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/DbControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..25b7a1d3f1cba8e250f78eb44f6f13bb047bc6d4 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/DbControllerImpl.java @@ -0,0 +1,42 @@ +package org.briarproject.mailbox.controller; + +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.concurrent.Immutable; +import javax.inject.Inject; + +@Immutable +@NotNullByDefault +public class DbControllerImpl implements DbController { + + private static final Logger LOG = + Logger.getLogger(DbControllerImpl.class.getName()); + + protected final Executor dbExecutor; + private final LifecycleManager lifecycleManager; + + @Inject + public DbControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager) { + this.dbExecutor = dbExecutor; + this.lifecycleManager = lifecycleManager; + } + + @Override + public void runOnDbThread(Runnable task) { + dbExecutor.execute(() -> { + try { + lifecycleManager.waitForDatabase(); + task.run(); + } catch (InterruptedException e) { + LOG.warning("Interrupted while waiting for database"); + Thread.currentThread().interrupt(); + } + }); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/MailboxController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/MailboxController.java new file mode 100644 index 0000000000000000000000000000000000000000..2cabdc2b066513c24a05e8a02bf3ba4f72eb33a5 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/MailboxController.java @@ -0,0 +1,21 @@ +package org.briarproject.mailbox.controller; + + +import org.briarproject.mailbox.controller.handler.ResultHandler; + +public interface MailboxController extends ActivityLifecycleController { + + void startAndBindService(); + + boolean hasEncryptionKey(); + + /** + * Returns true via the handler when the app has dozed + * without being white-listed. + */ + void hasDozed(ResultHandler<Boolean> handler); + + void doNotAskAgainForDozeWhiteListing(); + + void signOut(ResultHandler<Void> eventHandler); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/MailboxControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/MailboxControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b24b3e522e831214365524b3c77710ae44953860 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/MailboxControllerImpl.java @@ -0,0 +1,147 @@ +package org.briarproject.mailbox.controller; + +import android.app.Activity; +import android.content.Intent; +import android.os.IBinder; +import android.support.annotation.CallSuper; + +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.mailbox.MailboxService; +import org.briarproject.mailbox.api.android.DozeWatchdog; +import org.briarproject.mailbox.controller.handler.ResultHandler; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.mailbox.MailboxService.*; +import static org.briarproject.mailbox.settings.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.mailbox.util.UiUtils.needsDozeWhitelisting; + +public class MailboxControllerImpl implements MailboxController { + + private static final Logger LOG = + Logger.getLogger(MailboxControllerImpl.class.getName()); + + public static final String DOZE_ASK_AGAIN = "dozeAskAgain"; + + private final MailboxServiceConnection serviceConnection; + private final DatabaseConfig databaseConfig; + @DatabaseExecutor + private final Executor databaseExecutor; + private final SettingsManager settingsManager; + private final DozeWatchdog dozeWatchdog; + private final Activity activity; + + private boolean bound = false; + + @Inject + MailboxControllerImpl(MailboxServiceConnection serviceConnection, + DatabaseConfig databaseConfig, + @DatabaseExecutor Executor databaseExecutor, + SettingsManager settingsManager, DozeWatchdog dozeWatchdog, + Activity activity) { + this.serviceConnection = serviceConnection; + this.databaseConfig = databaseConfig; + this.databaseExecutor = databaseExecutor; + this.settingsManager = settingsManager; + this.dozeWatchdog = dozeWatchdog; + this.activity = activity; + } + + @Override + @CallSuper + public void onActivityCreate(Activity activity) { + if (databaseConfig.getEncryptionKey() != null) startAndBindService(); + } + + @Override + public void onActivityStart() { + } + + @Override + public void onActivityStop() { + } + + @Override + @CallSuper + public void onActivityDestroy() { + unbindService(); + } + + @Override + public void startAndBindService() { + activity.startService(new Intent(activity, MailboxService.class)); + bound = activity.bindService(new Intent(activity, MailboxService.class), + serviceConnection, 0); + } + + @Override + public boolean hasEncryptionKey() { + return databaseConfig.getEncryptionKey() != null; + } + + @Override + public void hasDozed(ResultHandler<Boolean> handler) { + if (!dozeWatchdog.getAndResetDozeFlag() + || !needsDozeWhitelisting(activity)) { + handler.onResult(false); + return; + } + databaseExecutor.execute(() -> { + try { + Settings settings = + settingsManager.getSettings(SETTINGS_NAMESPACE); + boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true); + handler.onResult(ask); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @Override + public void doNotAskAgainForDozeWhiteListing() { + databaseExecutor.execute(() -> { + try { + Settings settings = new Settings(); + settings.putBoolean(DOZE_ASK_AGAIN, false); + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @Override + public void signOut(ResultHandler<Void> eventHandler) { + new Thread(() -> { + try { + // Wait for the service to finish starting up + IBinder binder = serviceConnection.waitForBinder(); + MailboxService service = + ((MailboxService.BriarBinder) binder).getService(); + service.waitForStartup(); + // Shut down the service and wait for it to shut down + LOG.info("Shutting down service"); + service.shutdown(); + service.waitForShutdown(); + } catch (InterruptedException e) { + LOG.warning("Interrupted while waiting for service"); + } + eventHandler.onResult(null); + }).start(); + } + + private void unbindService() { + if (bound) activity.unbindService(serviceConnection); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/SharingController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/SharingController.java new file mode 100644 index 0000000000000000000000000000000000000000..e8430dc549772fa3983512128b5c2c716365c1c0 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/SharingController.java @@ -0,0 +1,70 @@ +package org.briarproject.mailbox.controller; + +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.DestroyableContext; + +import java.util.Collection; + +@NotNullByDefault +public interface SharingController { + + /** + * Sets the listener that is called when contacts go on or offline. + */ + @UiThread + void setSharingListener(SharingListener listener); + + /** + * Call this when your lifecycle starts, + * so the listener will be called when information changes. + */ + @UiThread + void onStart(); + + /** + * Call this when your lifecycle stops, + * so that the controller knows it can stops listening to events. + */ + @UiThread + void onStop(); + + /** + * Adds one contact to be tracked. + */ + @UiThread + void add(ContactId c); + + /** + * Adds a collection of contacts to be tracked. + */ + @UiThread + void addAll(Collection<ContactId> contacts); + + /** + * Call this when the contact identified by c is no longer sharing + * the given group identified by GroupId g. + */ + @UiThread + void remove(ContactId c); + + /** + * Returns the number of online contacts. + */ + @UiThread + int getOnlineCount(); + + /** + * Returns the total number of contacts that have been added. + */ + @UiThread + int getTotalCount(); + + interface SharingListener extends DestroyableContext { + @UiThread + void onSharingInfoUpdated(int total, int online); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/SharingControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/SharingControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..d1e2feb96921811f5844c9de221705e3057b8ce6 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/SharingControllerImpl.java @@ -0,0 +1,100 @@ +package org.briarproject.mailbox.controller; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.ConnectionRegistry; +import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent; +import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +@NotNullByDefault +public class SharingControllerImpl implements SharingController, EventListener { + + private final EventBus eventBus; + private final ConnectionRegistry connectionRegistry; + + @Nullable + private volatile SharingListener listener; + // only access on @UiThread + private final Set<ContactId> contacts = new HashSet<>(); + + @Inject + SharingControllerImpl(EventBus eventBus, + ConnectionRegistry connectionRegistry) { + this.eventBus = eventBus; + this.connectionRegistry = connectionRegistry; + } + + @Override + public void setSharingListener(SharingListener listener) { + this.listener = listener; + } + + @Override + public void onStart() { + eventBus.addListener(this); + } + + @Override + public void onStop() { + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof ContactConnectedEvent) { + setConnected(((ContactConnectedEvent) e).getContactId()); + } else if (e instanceof ContactDisconnectedEvent) { + setConnected(((ContactDisconnectedEvent) e).getContactId()); + } + } + + private void setConnected(ContactId c) { + if (listener == null) return; + listener.runOnUiThreadUnlessDestroyed(() -> { + if (contacts.contains(c)) { + int online = getOnlineCount(); + listener.onSharingInfoUpdated(contacts.size(), online); + } + }); + } + + @Override + public void addAll(Collection<ContactId> c) { + contacts.addAll(c); + } + + @Override + public void add(ContactId c) { + contacts.add(c); + } + + @Override + public void remove(ContactId c) { + contacts.remove(c); + } + + @Override + public int getOnlineCount() { + int online = 0; + for (ContactId c : contacts) { + if (connectionRegistry.isConnected(c)) online++; + } + return online; + } + + @Override + public int getTotalCount() { + return contacts.size(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ExceptionHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..0eb4b040be90e3863db80f63d4c4cd897c62cac4 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ExceptionHandler.java @@ -0,0 +1,7 @@ +package org.briarproject.mailbox.controller.handler; + +public interface ExceptionHandler<E extends Exception> { + + void onException(E exception); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ResultExceptionHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ResultExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..938002dc9bf7f793b6fc6f7d8cbd6bc39371b6f3 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ResultExceptionHandler.java @@ -0,0 +1,8 @@ +package org.briarproject.mailbox.controller.handler; + +public interface ResultExceptionHandler<R, E extends Exception> + extends ExceptionHandler<E> { + + void onResult(R result); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ResultHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ResultHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..a794df8c37255590d676cb19ec80a109b0a7519e --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/ResultHandler.java @@ -0,0 +1,6 @@ +package org.briarproject.mailbox.controller.handler; + +public interface ResultHandler<R> { + + void onResult(R result); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiExceptionHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..9e5cbd1a6a9098577e3ec05202539dd246f3c0c4 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiExceptionHandler.java @@ -0,0 +1,29 @@ +package org.briarproject.mailbox.controller.handler; + +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.DestroyableContext; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public abstract class UiExceptionHandler<E extends Exception> + implements ExceptionHandler<E> { + + protected final DestroyableContext listener; + + protected UiExceptionHandler(DestroyableContext listener) { + this.listener = listener; + } + + @Override + public void onException(E exception) { + listener.runOnUiThreadUnlessDestroyed(() -> onExceptionUi(exception)); + } + + @UiThread + public abstract void onExceptionUi(E exception); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiResultExceptionHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiResultExceptionHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..d33592bd827efa22b50be233a38f29b228d9f572 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiResultExceptionHandler.java @@ -0,0 +1,27 @@ +package org.briarproject.mailbox.controller.handler; + +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.DestroyableContext; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public abstract class UiResultExceptionHandler<R, E extends Exception> + extends UiExceptionHandler<E> implements ResultExceptionHandler<R, E> { + + protected UiResultExceptionHandler(DestroyableContext listener) { + super(listener); + } + + @Override + public void onResult(R result) { + listener.runOnUiThreadUnlessDestroyed(() -> onResultUi(result)); + } + + @UiThread + public abstract void onResultUi(R result); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiResultHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiResultHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..04b26b2149bb1a583f2326c07d66588c2552831f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/controller/handler/UiResultHandler.java @@ -0,0 +1,23 @@ +package org.briarproject.mailbox.controller.handler; + +import android.support.annotation.UiThread; + +import org.briarproject.mailbox.DestroyableContext; + + +public abstract class UiResultHandler<R> implements ResultHandler<R> { + + private final DestroyableContext listener; + + protected UiResultHandler(DestroyableContext listener) { + this.listener = listener; + } + + @Override + public void onResult(R result) { + listener.runOnUiThreadUnlessDestroyed(() -> onResultUi(result)); + } + + @UiThread + public abstract void onResultUi(R result); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/BaseEventFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/BaseEventFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..e1519a1a58cbe3e0b6080d4fd82f1d0b4fe1729d --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/BaseEventFragment.java @@ -0,0 +1,25 @@ +package org.briarproject.mailbox.fragment; + +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; + +import javax.inject.Inject; + +public abstract class BaseEventFragment extends BaseFragment implements + EventListener { + + @Inject + protected volatile EventBus eventBus; + + @Override + public void onStart() { + super.onStart(); + eventBus.addListener(this); + } + + @Override + public void onStop() { + super.onStop(); + eventBus.removeListener(this); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/BaseFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/BaseFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..5e240c7f7c39085067232906bd75faccdab9afa8 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/BaseFragment.java @@ -0,0 +1,104 @@ +package org.briarproject.mailbox.fragment; + +import android.app.Activity; +import android.content.Context; +import android.os.Bundle; +import android.support.annotation.CallSuper; +import android.support.annotation.UiThread; +import android.support.v4.app.Fragment; +import android.view.MenuItem; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.mailbox.DestroyableContext; +import org.briarproject.mailbox.activity.ActivityComponent; + +import javax.annotation.Nullable; + +public abstract class BaseFragment extends Fragment + implements DestroyableContext { + + protected BaseFragmentListener listener; + + public abstract String getUniqueTag(); + + public abstract void injectFragment(ActivityComponent component); + + @Override + public void onAttach(Context context) { + super.onAttach(context); + listener = (BaseFragmentListener) context; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // allow for "up" button to act as back button + setHasOptionsMenu(true); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + injectFragment(listener.getActivityComponent()); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + listener.onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @UiThread + protected void finish() { + if (!isDetached()) + getActivity().supportFinishAfterTransition(); + } + + public interface BaseFragmentListener { + @Deprecated + void runOnDbThread(Runnable runnable); + + @UiThread + void onBackPressed(); + + @UiThread + ActivityComponent getActivityComponent(); + + @UiThread + void showNextFragment(BaseFragment f); + + @UiThread + void handleDbException(DbException e); + } + + @CallSuper + @Override + public void runOnUiThreadUnlessDestroyed(Runnable r) { + Activity activity = getActivity(); + if (activity != null) { + activity.runOnUiThread(() -> { + // Note that we don't have to check if the activity has + // been destroyed as the Fragment has not been detached yet + if (!isDetached() && !activity.isFinishing()) { + r.run(); + } + }); + } + } + + protected void showNextFragment(BaseFragment f) { + listener.showNextFragment(f); + } + + @UiThread + protected void handleDbException(DbException e) { + listener.handleDbException(e); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/ErrorFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/ErrorFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..e15ea4a5ff19abbf5c313a3c1e2fbf252509ce11 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/ErrorFragment.java @@ -0,0 +1,65 @@ +package org.briarproject.mailbox.fragment; + +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; + + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ErrorFragment extends BaseFragment { + + private static final String TAG = ErrorFragment.class.getSimpleName(); + + private static final String ERROR_MSG = "errorMessage"; + + public static ErrorFragment newInstance(String message) { + ErrorFragment f = new ErrorFragment(); + Bundle args = new Bundle(); + args.putString(ERROR_MSG, message); + f.setArguments(args); + return f; + } + + private String errorMessage; + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle args = getArguments(); + if (args == null) throw new AssertionError(); + errorMessage = args.getString(ERROR_MSG); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View v = inflater + .inflate(R.layout.fragment_error, container, false); + TextView msg = v.findViewById(R.id.errorMessage); + msg.setText(errorMessage); + return v; + } + + @Override + public void injectFragment(ActivityComponent component) { + // not necessary + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/ScreenFilterDialogFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/ScreenFilterDialogFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..797c5e3b9cbd078d642308dbc13164e4de25fb75 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/fragment/ScreenFilterDialogFragment.java @@ -0,0 +1,107 @@ +package org.briarproject.mailbox.fragment; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.support.v7.app.AlertDialog; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CheckBox; +import android.widget.TextView; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.BaseActivity; +import org.briarproject.mailbox.api.android.ScreenFilterMonitor; + +import java.util.ArrayList; +import java.util.Collection; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static org.briarproject.mailbox.api.android.ScreenFilterMonitor.*; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class ScreenFilterDialogFragment extends DialogFragment { + + public static final String TAG = ScreenFilterDialogFragment.class.getName(); + + @Inject + ScreenFilterMonitor screenFilterMonitor; + + DismissListener dismissListener = null; + + public static ScreenFilterDialogFragment newInstance( + Collection<AppDetails> apps) { + ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment(); + Bundle args = new Bundle(); + ArrayList<String> appNames = new ArrayList<>(); + for (AppDetails a : apps) appNames.add(a.name); + args.putStringArrayList("appNames", appNames); + ArrayList<String> packageNames = new ArrayList<>(); + for (AppDetails a : apps) packageNames.add(a.packageName); + args.putStringArrayList("packageNames", packageNames); + frag.setArguments(args); + return frag; + } + + public void setDismissListener(DismissListener dismissListener) { + this.dismissListener = dismissListener; + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + Activity activity = getActivity(); + if (activity == null) throw new IllegalStateException(); + ((BaseActivity) activity).getActivityComponent().inject(this); + } + + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + Activity activity = getActivity(); + if (activity == null) throw new IllegalStateException(); + AlertDialog.Builder builder = new AlertDialog.Builder(activity, + R.style.BriarDialogThemeNoFilter); + builder.setTitle(R.string.screen_filter_title); + Bundle args = getArguments(); + if (args == null) throw new IllegalStateException(); + ArrayList<String> appNames = args.getStringArrayList("appNames"); + ArrayList<String> packageNames = + args.getStringArrayList("packageNames"); + if (appNames == null || packageNames == null) + throw new IllegalStateException(); + LayoutInflater inflater = activity.getLayoutInflater(); + // See https://stackoverflow.com/a/24720976/6314875 + @SuppressLint("InflateParams") + View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null); + builder.setView(dialogView); + TextView message = dialogView.findViewById(R.id.screen_filter_message); + message.setText(getString(R.string.screen_filter_body, + TextUtils.join("\n", appNames))); + CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox); + builder.setNeutralButton(R.string.continue_button, (dialog, which) -> { + if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames); + dialog.dismiss(); + }); + builder.setCancelable(false); + return builder.create(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + if (dismissListener != null) dismissListener.onDialogDismissed(); + } + + public interface DismissListener { + void onDialogDismissed(); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/CameraException.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/CameraException.java new file mode 100644 index 0000000000000000000000000000000000000000..19a3c96f6a760e543a6f9c215c2b80f27794d768 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/CameraException.java @@ -0,0 +1,14 @@ +package org.briarproject.mailbox.keyagreement; + +import java.io.IOException; + +class CameraException extends IOException { + + CameraException(String message) { + super(message); + } + + CameraException(Throwable cause) { + super(cause); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/CameraView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/CameraView.java new file mode 100644 index 0000000000000000000000000000000000000000..247563bb0ea2e67ce85620d721a388d00b53904b --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/CameraView.java @@ -0,0 +1,524 @@ +package org.briarproject.mailbox.keyagreement; + +import android.content.Context; +import android.hardware.Camera; +import android.hardware.Camera.AutoFocusCallback; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.Parameters; +import android.hardware.Camera.Size; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.View; +import android.view.WindowManager; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; + +import java.io.IOException; +import java.util.List; +import java.util.logging.Logger; + +import static android.content.Context.WINDOW_SERVICE; +import static android.hardware.Camera.CameraInfo.CAMERA_FACING_BACK; +import static android.hardware.Camera.CameraInfo.CAMERA_FACING_FRONT; +import static android.hardware.Camera.Parameters.FLASH_MODE_OFF; +import static android.hardware.Camera.Parameters.FOCUS_MODE_AUTO; +import static android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE; +import static android.hardware.Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO; +import static android.hardware.Camera.Parameters.FOCUS_MODE_EDOF; +import static android.hardware.Camera.Parameters.FOCUS_MODE_FIXED; +import static android.hardware.Camera.Parameters.FOCUS_MODE_MACRO; +import static android.hardware.Camera.Parameters.SCENE_MODE_AUTO; +import static android.hardware.Camera.Parameters.SCENE_MODE_BARCODE; +import static android.os.Build.VERSION.SDK_INT; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +@SuppressWarnings("deprecation") +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class CameraView extends SurfaceView implements SurfaceHolder.Callback, + AutoFocusCallback, View.OnClickListener { + + // Heuristic for the ideal preview size - small previews don't have enough + // detail, large previews are slow to decode + private static final int IDEAL_PIXELS = 500 * 1000; + + private static final int AUTO_FOCUS_RETRY_DELAY = 5000; // Milliseconds + + private static final Logger LOG = + Logger.getLogger(CameraView.class.getName()); + + private final Runnable autoFocusRetry = this::retryAutoFocus; + + @Nullable + private Camera camera = null; + private int cameraIndex = 0; + private PreviewConsumer previewConsumer = null; + private Surface surface = null; + private int displayOrientation = 0, surfaceWidth = 0, surfaceHeight = 0; + private boolean previewStarted = false; + private boolean autoFocusSupported = false, autoFocusRunning = false; + + public CameraView(Context context) { + super(context); + } + + public CameraView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public CameraView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @UiThread + public void setPreviewConsumer(PreviewConsumer previewConsumer) { + LOG.info("Setting preview consumer"); + this.previewConsumer = previewConsumer; + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + setKeepScreenOn(true); + getHolder().addCallback(this); + setOnClickListener(this); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + setKeepScreenOn(false); + getHolder().removeCallback(this); + } + + @UiThread + public void start() throws CameraException { + LOG.info("Opening camera"); + try { + int cameras = Camera.getNumberOfCameras(); + if (cameras == 0) throw new CameraException("No camera"); + // Try to find a back-facing camera + for (int i = 0; i < cameras; i++) { + CameraInfo info = new CameraInfo(); + Camera.getCameraInfo(i, info); + if (info.facing == CAMERA_FACING_BACK) { + LOG.info("Using back-facing camera"); + camera = Camera.open(i); + cameraIndex = i; + break; + } + } + // If we can't find a back-facing camera, use a front-facing one + if (camera == null) { + LOG.info("Using front-facing camera"); + camera = Camera.open(0); + cameraIndex = 0; + } + } catch (RuntimeException e) { + throw new CameraException(e); + } + setDisplayOrientation(getScreenRotationDegrees()); + // Use barcode scene mode if it's available + Parameters params = camera.getParameters(); + params = setSceneMode(camera, params); + if (SCENE_MODE_BARCODE.equals(params.getSceneMode())) { + // If the scene mode enabled the flash, try to disable it + if (!FLASH_MODE_OFF.equals(params.getFlashMode())) + params = disableFlash(camera, params); + // If the flash is still enabled, disable the scene mode + if (!FLASH_MODE_OFF.equals(params.getFlashMode())) + params = disableSceneMode(camera, params); + } + // Use the best available focus mode, preview size and other options + params = setBestParameters(camera, params); + // Enable auto focus if the selected focus mode uses it + enableAutoFocus(params.getFocusMode()); + // Log the parameters that are being used (maybe not what we asked for) + logCameraParameters(); + // Start the preview when the camera and the surface are both ready + if (surface != null && !previewStarted) startPreview(getHolder()); + } + + @UiThread + public void stop() throws CameraException { + if (camera == null) return; + stopPreview(); + LOG.info("Releasing camera"); + try { + camera.release(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + camera = null; + } + + /** + * See {@link Camera#setDisplayOrientation(int)}. + */ + private int getScreenRotationDegrees() { + WindowManager wm = + (WindowManager) getContext().getSystemService(WINDOW_SERVICE); + Display d = wm.getDefaultDisplay(); + switch (d.getRotation()) { + case Surface.ROTATION_0: + return 0; + case Surface.ROTATION_90: + return 90; + case Surface.ROTATION_180: + return 180; + case Surface.ROTATION_270: + return 270; + default: + throw new AssertionError(); + } + } + + @UiThread + private void startPreview(SurfaceHolder holder) throws CameraException { + LOG.info("Starting preview"); + if (camera == null) throw new CameraException("Camera is null"); + try { + camera.setPreviewDisplay(holder); + camera.startPreview(); + previewStarted = true; + startConsumer(); + } catch (IOException | RuntimeException e) { + throw new CameraException(e); + } + } + + @UiThread + private void stopPreview() throws CameraException { + LOG.info("Stopping preview"); + if (camera == null) throw new CameraException("Camera is null"); + try { + stopConsumer(); + camera.stopPreview(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + previewStarted = false; + } + + @UiThread + private void startConsumer() throws CameraException { + if (camera == null) throw new CameraException("Camera is null"); + startAutoFocus(); + previewConsumer.start(camera, cameraIndex); + } + + @UiThread + private void startAutoFocus() throws CameraException { + if (camera != null && autoFocusSupported && !autoFocusRunning) { + try { + removeCallbacks(autoFocusRetry); + camera.autoFocus(this); + autoFocusRunning = true; + } catch (RuntimeException e) { + throw new CameraException(e); + } + } + } + + @UiThread + private void stopConsumer() throws CameraException { + if (camera == null) throw new CameraException("Camera is null"); + cancelAutoFocus(); + previewConsumer.stop(); + } + + @UiThread + private void cancelAutoFocus() throws CameraException { + if (camera != null && autoFocusSupported && autoFocusRunning) { + try { + removeCallbacks(autoFocusRetry); + camera.cancelAutoFocus(); + autoFocusRunning = false; + } catch (RuntimeException e) { + throw new CameraException(e); + } + } + } + + /** + * See {@link Camera#setDisplayOrientation(int)}. + */ + @UiThread + private void setDisplayOrientation(int rotationDegrees) + throws CameraException { + if (camera == null) throw new CameraException("Camera is null"); + int orientation; + CameraInfo info = new CameraInfo(); + try { + Camera.getCameraInfo(cameraIndex, info); + } catch (RuntimeException e) { + throw new CameraException(e); + } + if (info.facing == CAMERA_FACING_FRONT) { + orientation = (info.orientation + rotationDegrees) % 360; + orientation = (360 - orientation) % 360; + } else { + orientation = (info.orientation - rotationDegrees + 360) % 360; + } + if (LOG.isLoggable(INFO)) { + LOG.info("Screen rotation " + rotationDegrees + + " degrees, camera orientation " + orientation + + " degrees"); + } + try { + camera.setDisplayOrientation(orientation); + } catch (RuntimeException e) { + throw new CameraException(e); + } + displayOrientation = orientation; + } + + @UiThread + private Parameters setSceneMode(Camera camera, Parameters params) + throws CameraException { + List<String> sceneModes = params.getSupportedSceneModes(); + if (sceneModes == null) return params; + if (LOG.isLoggable(INFO)) LOG.info("Scene modes: " + sceneModes); + if (sceneModes.contains(SCENE_MODE_BARCODE)) { + params.setSceneMode(SCENE_MODE_BARCODE); + try { + camera.setParameters(params); + return camera.getParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + } + return params; + } + + @UiThread + private Parameters disableFlash(Camera camera, Parameters params) + throws CameraException { + params.setFlashMode(FLASH_MODE_OFF); + try { + camera.setParameters(params); + return camera.getParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + } + + @UiThread + private Parameters disableSceneMode(Camera camera, Parameters params) + throws CameraException { + params.setSceneMode(SCENE_MODE_AUTO); + try { + camera.setParameters(params); + return camera.getParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + } + + @UiThread + private Parameters setBestParameters(Camera camera, Parameters params) + throws CameraException { + setVideoStabilisation(params); + setFocusMode(params); + params.setFlashMode(FLASH_MODE_OFF); + setPreviewSize(params); + try { + camera.setParameters(params); + return camera.getParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + } + + @UiThread + private void setVideoStabilisation(Parameters params) { + if (SDK_INT >= 15 && params.isVideoStabilizationSupported()) { + params.setVideoStabilization(true); + } + } + + @UiThread + private void setFocusMode(Parameters params) { + List<String> focusModes = params.getSupportedFocusModes(); + if (LOG.isLoggable(INFO)) LOG.info("Focus modes: " + focusModes); + if (focusModes.contains(FOCUS_MODE_CONTINUOUS_PICTURE)) { + params.setFocusMode(FOCUS_MODE_CONTINUOUS_PICTURE); + } else if (focusModes.contains(FOCUS_MODE_CONTINUOUS_VIDEO)) { + params.setFocusMode(FOCUS_MODE_CONTINUOUS_VIDEO); + } else if (focusModes.contains(FOCUS_MODE_EDOF)) { + params.setFocusMode(FOCUS_MODE_EDOF); + } else if (focusModes.contains(FOCUS_MODE_MACRO)) { + params.setFocusMode(FOCUS_MODE_MACRO); + } else if (focusModes.contains(FOCUS_MODE_AUTO)) { + params.setFocusMode(FOCUS_MODE_AUTO); + } else if (focusModes.contains(FOCUS_MODE_FIXED)) { + params.setFocusMode(FOCUS_MODE_FIXED); + } + } + + @UiThread + private void setPreviewSize(Parameters params) { + if (surfaceWidth == 0 || surfaceHeight == 0) return; + // Choose a preview size that's close to the aspect ratio of the + // surface and close to the ideal size for decoding + float idealRatio = (float) surfaceWidth / surfaceHeight; + boolean rotatePreview = displayOrientation % 180 == 90; + List<Size> sizes = params.getSupportedPreviewSizes(); + Size bestSize = null; + float bestScore = 0; + for (Size size : sizes) { + int width = rotatePreview ? size.height : size.width; + int height = rotatePreview ? size.width : size.height; + float ratio = (float) width / height; + float stretch = Math.max(ratio / idealRatio, idealRatio / ratio); + float pixels = width * height; + float zoom = Math.max(pixels / IDEAL_PIXELS, IDEAL_PIXELS / pixels); + float score = 1 / (stretch * zoom); + if (LOG.isLoggable(INFO)) { + LOG.info("Size " + size.width + "x" + size.height + + ", stretch " + stretch + ", zoom " + zoom + + ", score " + score); + } + if (bestSize == null || score > bestScore) { + bestSize = size; + bestScore = score; + } + } + if (bestSize != null) { + if (LOG.isLoggable(INFO)) + LOG.info("Best size " + bestSize.width + "x" + bestSize.height); + params.setPreviewSize(bestSize.width, bestSize.height); + } + } + + @UiThread + private void enableAutoFocus(String focusMode) { + autoFocusSupported = FOCUS_MODE_AUTO.equals(focusMode) || + FOCUS_MODE_MACRO.equals(focusMode); + } + + @UiThread + private void logCameraParameters() throws CameraException { + if (camera == null) throw new AssertionError(); + if (LOG.isLoggable(INFO)) { + Parameters params; + try { + params = camera.getParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + if (SDK_INT >= 15) { + LOG.info("Video stabilisation enabled: " + + params.getVideoStabilization()); + } + LOG.info("Scene mode: " + params.getSceneMode()); + LOG.info("Focus mode: " + params.getFocusMode()); + LOG.info("Flash mode: " + params.getFlashMode()); + Size size = params.getPreviewSize(); + LOG.info("Preview size: " + size.width + "x" + size.height); + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + post(() -> { + try { + surfaceCreatedUi(holder); + } catch (CameraException e) { + logException(LOG, WARNING, e); + } + }); + } + + @UiThread + private void surfaceCreatedUi(SurfaceHolder holder) throws CameraException { + LOG.info("Surface created"); + if (surface != null && surface != holder.getSurface()) { + LOG.info("Releasing old surface"); + surface.release(); + } + surface = holder.getSurface(); + // We'll start the preview when surfaceChanged() is called + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { + post(() -> { + try { + surfaceChangedUi(holder, w, h); + } catch (CameraException e) { + logException(LOG, WARNING, e); + } + }); + } + + @UiThread + private void surfaceChangedUi(SurfaceHolder holder, int w, int h) + throws CameraException { + if (LOG.isLoggable(INFO)) LOG.info("Surface changed: " + w + "x" + h); + if (surface != null && surface != holder.getSurface()) { + LOG.info("Releasing old surface"); + surface.release(); + } + surface = holder.getSurface(); + surfaceWidth = w; + surfaceHeight = h; + if (camera == null) return; // We are stopped + if (previewStarted) stopPreview(); + try { + Parameters params = camera.getParameters(); + setPreviewSize(params); + camera.setParameters(params); + logCameraParameters(); + } catch (RuntimeException e) { + throw new CameraException(e); + } + startPreview(holder); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + post(() -> surfaceDestroyedUi(holder)); + } + + @UiThread + private void surfaceDestroyedUi(SurfaceHolder holder) { + LOG.info("Surface destroyed"); + if (surface != null && surface != holder.getSurface()) { + LOG.info("Releasing old surface"); + surface.release(); + } + surface = null; + holder.getSurface().release(); + } + + @Override + public void onAutoFocus(boolean success, Camera camera) { + if (LOG.isLoggable(INFO)) + LOG.info("Auto focus succeeded: " + success); + autoFocusRunning = false; + postDelayed(autoFocusRetry, AUTO_FOCUS_RETRY_DELAY); + } + + @UiThread + private void retryAutoFocus() { + try { + startAutoFocus(); + } catch (CameraException e) { + logException(LOG, WARNING, e); + } + } + + @Override + public void onClick(View v) { + retryAutoFocus(); + } +} \ No newline at end of file diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/IntroFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/IntroFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..449d43fe83532c900b0a71b59092ae097e5a905c --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/IntroFragment.java @@ -0,0 +1,78 @@ +package org.briarproject.mailbox.keyagreement; + +import android.content.Context; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.fragment.BaseFragment; + +import javax.annotation.Nullable; + +import static android.view.View.FOCUS_DOWN; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class IntroFragment extends BaseFragment { + + interface IntroScreenSeenListener { + void showNextScreen(); + } + + public static final String TAG = IntroFragment.class.getName(); + + private IntroScreenSeenListener screenSeenListener; + private ScrollView scrollView; + + public static IntroFragment newInstance() { + + Bundle args = new Bundle(); + + IntroFragment fragment = new IntroFragment(); + fragment.setArguments(args); + return fragment; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + screenSeenListener = (IntroScreenSeenListener) context; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_keyagreement_id, container, + false); + scrollView = v.findViewById(R.id.scrollView); + View button = v.findViewById(R.id.continueButton); + button.setOnClickListener(view -> screenSeenListener.showNextScreen()); + return v; + } + + @Override + public void onStart() { + super.onStart(); + scrollView.post(() -> scrollView.fullScroll(FOCUS_DOWN)); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/KeyAgreementActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/KeyAgreementActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..ec1da02f06fa6eccc656a4bf37e47c10b6e77c0b --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/KeyAgreementActivity.java @@ -0,0 +1,263 @@ +package org.briarproject.mailbox.keyagreement; + +import android.bluetooth.BluetoothAdapter; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.support.annotation.UiThread; +import android.support.v4.app.ActivityCompat; +import android.support.v4.app.FragmentManager; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog.Builder; +import android.support.v7.widget.Toolbar; +import android.view.MenuItem; +import android.widget.Toast; + +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.event.BluetoothEnabledEvent; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.MailboxActivity; +import org.briarproject.mailbox.fragment.BaseFragment; +import org.briarproject.mailbox.util.UiUtils; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.Manifest.permission.CAMERA; +import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_ENABLE; +import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; +import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; +import static android.bluetooth.BluetoothAdapter.STATE_ON; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.Build.VERSION.SDK_INT; +import static android.widget.Toast.LENGTH_LONG; +import static org.briarproject.mailbox.R.id; +import static org.briarproject.mailbox.R.layout; +import static org.briarproject.mailbox.R.string; +import static org.briarproject.mailbox.R.style; +import static org.briarproject.mailbox.activity.RequestCodes.REQUEST_ENABLE_BLUETOOTH; +import static org.briarproject.mailbox.activity.RequestCodes.REQUEST_PERMISSION_CAMERA; +import static org.briarproject.mailbox.fragment.BaseFragment.BaseFragmentListener; +import static org.briarproject.mailbox.keyagreement.IntroFragment.IntroScreenSeenListener; +import static org.briarproject.mailbox.keyagreement.IntroFragment.newInstance; +import static org.briarproject.mailbox.keyagreement.KeyAgreementFragment.KeyAgreementEventListener; +import static org.briarproject.mailbox.keyagreement.KeyAgreementFragment.newInstance; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public abstract class KeyAgreementActivity extends MailboxActivity implements + BaseFragmentListener, IntroScreenSeenListener, + KeyAgreementEventListener { + + private enum BluetoothState { + UNKNOWN, NO_ADAPTER, WAITING, REFUSED, ENABLED + } + + private static final Logger LOG = + Logger.getLogger(KeyAgreementActivity.class.getName()); + + @Inject + EventBus eventBus; + + private boolean isResumed = false, enableWasRequested = false; + private boolean continueClicked, gotCameraPermission; + private BluetoothState bluetoothState = BluetoothState.UNKNOWN; + private BroadcastReceiver bluetoothReceiver = null; + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + setContentView(layout.activity_fragment_container_toolbar); + Toolbar toolbar = findViewById(id.toolbar); + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + if (state == null) { + showInitialFragment(newInstance()); + } + IntentFilter filter = new IntentFilter(ACTION_STATE_CHANGED); + bluetoothReceiver = new BluetoothStateReceiver(); + registerReceiver(bluetoothReceiver, filter); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (bluetoothReceiver != null) unregisterReceiver(bluetoothReceiver); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + protected void onPostResume() { + super.onPostResume(); + isResumed = true; + // Workaround for + // https://code.google.com/p/android/issues/detail?id=190966 + if (canShowQrCodeFragment()) showQrCodeFragment(); + } + + private boolean canShowQrCodeFragment() { + return isResumed && continueClicked + && (SDK_INT < 23 || gotCameraPermission) + && bluetoothState != BluetoothState.UNKNOWN + && bluetoothState != BluetoothState.WAITING; + } + + @Override + protected void onPause() { + super.onPause(); + isResumed = false; + } + + @Override + public void showNextScreen() { + continueClicked = true; + if (checkPermissions()) { + if (shouldRequestEnableBluetooth()) requestEnableBluetooth(); + else if (canShowQrCodeFragment()) showQrCodeFragment(); + } + } + + private boolean shouldRequestEnableBluetooth() { + return bluetoothState == BluetoothState.UNKNOWN + || bluetoothState == BluetoothState.REFUSED; + } + + private void requestEnableBluetooth() { + BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); + if (bt == null) { + setBluetoothState(BluetoothState.NO_ADAPTER); + } else if (bt.isEnabled()) { + setBluetoothState(BluetoothState.ENABLED); + } else { + enableWasRequested = true; + setBluetoothState(BluetoothState.WAITING); + Intent i = new Intent(ACTION_REQUEST_ENABLE); + startActivityForResult(i, REQUEST_ENABLE_BLUETOOTH); + } + } + + private void setBluetoothState(BluetoothState bluetoothState) { + LOG.info("Setting Bluetooth state to " + bluetoothState); + this.bluetoothState = bluetoothState; + if (enableWasRequested && bluetoothState == BluetoothState.ENABLED) { + eventBus.broadcast(new BluetoothEnabledEvent()); + enableWasRequested = false; + } + if (canShowQrCodeFragment()) showQrCodeFragment(); + } + + @Override + public void onActivityResult(int request, int result, Intent data) { + // If the request was granted we'll catch the state change event + if (request == REQUEST_ENABLE_BLUETOOTH && result == RESULT_CANCELED) + setBluetoothState(BluetoothState.REFUSED); + } + + private void showQrCodeFragment() { + continueClicked = false; + // FIXME #824 + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentByTag(KeyAgreementFragment.TAG) == null) { + BaseFragment f = newInstance(this); + fm.beginTransaction() + .replace(id.fragmentContainer, f, f.getUniqueTag()) + .addToBackStack(f.getUniqueTag()) + .commit(); + } + } + + private boolean checkPermissions() { + if (ContextCompat.checkSelfPermission(this, CAMERA) != + PERMISSION_GRANTED) { + // Should we show an explanation? + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + CAMERA)) { + OnClickListener continueListener = + (dialog, which) -> requestPermission(); + Builder builder = new Builder(this, style.BriarDialogTheme); + builder.setTitle(string.permission_camera_title); + builder.setMessage(string.permission_camera_request_body); + builder.setNeutralButton(string.continue_button, + continueListener); + builder.show(); + } else { + requestPermission(); + } + gotCameraPermission = false; + return false; + } else { + gotCameraPermission = true; + return true; + } + } + + private void requestPermission() { + ActivityCompat.requestPermissions(this, new String[] {CAMERA}, + REQUEST_PERMISSION_CAMERA); + } + + @Override + @UiThread + public void onRequestPermissionsResult(int requestCode, + String permissions[], int[] grantResults) { + if (requestCode == REQUEST_PERMISSION_CAMERA) { + // If request is cancelled, the result arrays are empty. + if (grantResults.length > 0 && + grantResults[0] == PERMISSION_GRANTED) { + gotCameraPermission = true; + showNextScreen(); + } else { + if (!ActivityCompat.shouldShowRequestPermissionRationale(this, + CAMERA)) { + // The user has permanently denied the request + OnClickListener cancelListener = + (dialog, which) -> supportFinishAfterTransition(); + Builder builder = new Builder(this, style.BriarDialogTheme); + builder.setTitle(string.permission_camera_title); + builder.setMessage(string.permission_camera_denied_body); + builder.setPositiveButton(string.ok, + UiUtils.getGoToSettingsListener(this)); + builder.setNegativeButton(string.cancel, cancelListener); + builder.show(); + } else { + Toast.makeText(this, string.permission_camera_denied_toast, + LENGTH_LONG).show(); + supportFinishAfterTransition(); + } + } + } + } + + private class BluetoothStateReceiver extends BroadcastReceiver { + + @Override + public void onReceive(Context context, Intent intent) { + int state = intent.getIntExtra(EXTRA_STATE, 0); + if (state == STATE_ON) setBluetoothState(BluetoothState.ENABLED); + else setBluetoothState(BluetoothState.UNKNOWN); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/KeyAgreementFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/KeyAgreementFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..7b3e064fb45f90bd513b9e908e1f17368d14f85a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/KeyAgreementFragment.java @@ -0,0 +1,371 @@ +package org.briarproject.mailbox.keyagreement; + +import android.content.Context; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.support.annotation.UiThread; +import android.util.DisplayMetrics; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; +import android.widget.LinearLayout.LayoutParams; +import android.widget.TextView; +import android.widget.Toast; + +import com.google.zxing.Result; + +import org.briarproject.bramble.api.UnsupportedVersionException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; +import org.briarproject.bramble.api.keyagreement.KeyAgreementTask; +import org.briarproject.bramble.api.keyagreement.Payload; +import org.briarproject.bramble.api.keyagreement.PayloadEncoder; +import org.briarproject.bramble.api.keyagreement.PayloadParser; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementAbortedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFailedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementFinishedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementStartedEvent; +import org.briarproject.bramble.api.keyagreement.event.KeyAgreementWaitingEvent; +import org.briarproject.bramble.api.lifecycle.IoExecutor; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.fragment.BaseEventFragment; +import org.briarproject.mailbox.fragment.ErrorFragment; +import org.briarproject.mailbox.view.QrCodeView; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; +import javax.inject.Provider; + +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.widget.LinearLayout.HORIZONTAL; +import static android.widget.Toast.LENGTH_LONG; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class KeyAgreementFragment extends BaseEventFragment + implements QrCodeDecoder.ResultCallback, QrCodeView.FullscreenListener { + + static final String TAG = KeyAgreementFragment.class.getName(); + + private static final Logger LOG = Logger.getLogger(TAG); + private static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1"); + + @Inject + Provider<KeyAgreementTask> keyAgreementTaskProvider; + @Inject + PayloadEncoder payloadEncoder; + @Inject + PayloadParser payloadParser; + @Inject + @IoExecutor + Executor ioExecutor; + @Inject + EventBus eventBus; + + private CameraView cameraView; + private LinearLayout cameraOverlay; + private View statusView; + private QrCodeView qrCodeView; + private TextView status; + + private boolean gotRemotePayload; + private volatile boolean gotLocalPayload; + private KeyAgreementTask task; + private KeyAgreementEventListener listener; + + public static KeyAgreementFragment newInstance( + KeyAgreementEventListener listener) { + Bundle args = new Bundle(); + KeyAgreementFragment fragment = new KeyAgreementFragment(); + fragment.listener = listener; + fragment.setArguments(args); + return fragment; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_keyagreement_qr, container, + false); + } + + @Override + public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + cameraView = view.findViewById(R.id.camera_view); + cameraOverlay = view.findViewById(R.id.camera_overlay); + statusView = view.findViewById(R.id.status_container); + status = view.findViewById(R.id.connect_status); + qrCodeView = view.findViewById(R.id.qr_code_view); + qrCodeView.setFullscreenListener(this); + } + + @Override + public void onActivityCreated(@Nullable Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + getActivity().setRequestedOrientation(SCREEN_ORIENTATION_NOSENSOR); + cameraView.setPreviewConsumer(new QrCodeDecoder(this)); + } + + @Override + public void onStart() { + super.onStart(); + try { + cameraView.start(); + } catch (CameraException e) { + logCameraExceptionAndFinish(e); + } + startListening(); + } + + @Override + public void setFullscreen(boolean fullscreen) { + LayoutParams statusParams, qrCodeParams; + if (fullscreen) { + // Grow the QR code view to fill its parent + statusParams = new LayoutParams(0, 0, 0f); + qrCodeParams = new LayoutParams(MATCH_PARENT, MATCH_PARENT, 1f); + } else { + // Shrink the QR code view to fill half its parent + if (cameraOverlay.getOrientation() == HORIZONTAL) { + statusParams = new LayoutParams(0, MATCH_PARENT, 1f); + qrCodeParams = new LayoutParams(0, MATCH_PARENT, 1f); + } else { + statusParams = new LayoutParams(MATCH_PARENT, 0, 1f); + qrCodeParams = new LayoutParams(MATCH_PARENT, 0, 1f); + } + } + statusView.setLayoutParams(statusParams); + qrCodeView.setLayoutParams(qrCodeParams); + cameraOverlay.invalidate(); + } + + @Override + public void onStop() { + super.onStop(); + stopListening(); + try { + cameraView.stop(); + } catch (CameraException e) { + logCameraExceptionAndFinish(e); + } + } + + @UiThread + private void logCameraExceptionAndFinish(CameraException e) { + logException(LOG, WARNING, e); + Toast.makeText(getActivity(), R.string.camera_error, + LENGTH_LONG).show(); + finish(); + } + + @UiThread + private void startListening() { + KeyAgreementTask oldTask = task; + KeyAgreementTask newTask = keyAgreementTaskProvider.get(); + task = newTask; + ioExecutor.execute(() -> { + if (oldTask != null) oldTask.stopListening(); + newTask.listen(); + }); + } + + @UiThread + private void stopListening() { + KeyAgreementTask oldTask = task; + ioExecutor.execute(() -> { + if (oldTask != null) oldTask.stopListening(); + }); + } + + @UiThread + private void reset() { + // If we've stopped the camera view, restart it + if (gotRemotePayload) { + try { + cameraView.start(); + } catch (CameraException e) { + logCameraExceptionAndFinish(e); + return; + } + } + statusView.setVisibility(INVISIBLE); + cameraView.setVisibility(VISIBLE); + gotRemotePayload = false; + gotLocalPayload = false; + startListening(); + } + + @UiThread + private void qrCodeScanned(String content) { + try { + byte[] payloadBytes = content.getBytes(ISO_8859_1); + if (LOG.isLoggable(INFO)) + LOG.info("Remote payload is " + payloadBytes.length + " bytes"); + Payload remotePayload = payloadParser.parse(payloadBytes); + gotRemotePayload = true; + cameraView.stop(); + cameraView.setVisibility(INVISIBLE); + statusView.setVisibility(VISIBLE); + status.setText(R.string.connecting_to_device); + task.connectAndRunProtocol(remotePayload); + } catch (UnsupportedVersionException e) { + reset(); + String msg = getString(R.string.qr_code_unsupported, + getString(R.string.app_name)); + showNextFragment(ErrorFragment.newInstance(msg)); + } catch (CameraException e) { + logCameraExceptionAndFinish(e); + } catch (IOException | IllegalArgumentException e) { + LOG.log(WARNING, "QR Code Invalid", e); + reset(); + Toast.makeText(getActivity(), R.string.qr_code_invalid, + LENGTH_LONG).show(); + } + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof KeyAgreementListeningEvent) { + KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e; + gotLocalPayload = true; + setQrCode(event.getLocalPayload()); + } else if (e instanceof KeyAgreementFailedEvent) { + keyAgreementFailed(); + } else if (e instanceof KeyAgreementWaitingEvent) { + keyAgreementWaiting(); + } else if (e instanceof KeyAgreementStartedEvent) { + keyAgreementStarted(); + } else if (e instanceof KeyAgreementAbortedEvent) { + KeyAgreementAbortedEvent event = (KeyAgreementAbortedEvent) e; + keyAgreementAborted(event.didRemoteAbort()); + } else if (e instanceof KeyAgreementFinishedEvent) { + keyAgreementFinished(((KeyAgreementFinishedEvent) e).getResult()); + } + } + + private void keyAgreementFailed() { + runOnUiThreadUnlessDestroyed(() -> { + reset(); + listener.keyAgreementFailed(); + }); + } + + private void keyAgreementWaiting() { + runOnUiThreadUnlessDestroyed( + () -> status.setText(listener.keyAgreementWaiting())); + } + + private void keyAgreementStarted() { + runOnUiThreadUnlessDestroyed(() -> { + qrCodeView.setVisibility(INVISIBLE); + statusView.setVisibility(VISIBLE); + status.setText(listener.keyAgreementStarted()); + }); + } + + private void keyAgreementAborted(boolean remoteAborted) { + runOnUiThreadUnlessDestroyed(() -> { + reset(); + qrCodeView.setVisibility(VISIBLE); + statusView.setVisibility(INVISIBLE); + status.setText(listener.keyAgreementAborted(remoteAborted)); + }); + } + + private void keyAgreementFinished(KeyAgreementResult result) { + runOnUiThreadUnlessDestroyed(() -> { + statusView.setVisibility(VISIBLE); + status.setText(listener.keyAgreementFinished(result)); + }); + } + + private void setQrCode(Payload localPayload) { + Context context = getContext(); + if (context == null) return; + DisplayMetrics dm = context.getResources().getDisplayMetrics(); + ioExecutor.execute(() -> { + byte[] payloadBytes = payloadEncoder.encode(localPayload); + if (LOG.isLoggable(INFO)) { + LOG.info("Local payload is " + payloadBytes.length + + " bytes"); + } + // Use ISO 8859-1 to encode bytes directly as a string + String content = new String(payloadBytes, ISO_8859_1); + Bitmap qrCode = QrCodeUtils.createQrCode(dm, content); + runOnUiThreadUnlessDestroyed(() -> qrCodeView.setQrCode(qrCode)); + }); + } + + @Override + public void handleResult(Result result) { + runOnUiThreadUnlessDestroyed(() -> { + LOG.info("Got result from decoder"); + // Ignore results until the KeyAgreementTask is ready + if (!gotLocalPayload) return; + if (!gotRemotePayload) qrCodeScanned(result.getText()); + }); + } + + @Override + protected void finish() { + getActivity().getSupportFragmentManager().popBackStack(); + } + + @NotNullByDefault + interface KeyAgreementEventListener { + + @UiThread + void keyAgreementFailed(); + + // Should return a string to be displayed as status. + @UiThread + @Nullable + String keyAgreementWaiting(); + + // Should return a string to be displayed as status. + @UiThread + @Nullable + String keyAgreementStarted(); + + // Should return a string to be displayed as status. + @UiThread + @Nullable + String keyAgreementAborted(boolean remoteAborted); + + // Should return a string to be displayed as status. + @UiThread + @Nullable + String keyAgreementFinished(KeyAgreementResult result); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/MailboxExchangeActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/MailboxExchangeActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..f9197eb6aa540f2512ddafa9d85179331ef4bbba --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/MailboxExchangeActivity.java @@ -0,0 +1,150 @@ +package org.briarproject.mailbox.keyagreement; + +import android.os.Bundle; +import android.support.annotation.UiThread; +import android.widget.Toast; + +import org.briarproject.bramble.api.contact.ContactExchangeListener; +import org.briarproject.bramble.api.contact.MailboxExchangeTask; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.bramble.api.identity.IdentityManager; +import org.briarproject.bramble.api.identity.LocalAuthor; +import org.briarproject.bramble.api.keyagreement.KeyAgreementResult; +import org.briarproject.mailbox.activity.ActivityComponent; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.widget.Toast.LENGTH_LONG; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.contact.ContactTypes.MAILBOX_OWNER; +import static org.briarproject.bramble.api.contact.ContactTypes.PRIVATE_MAILBOX; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.mailbox.R.string.authenticating_with_device; +import static org.briarproject.mailbox.R.string.connection_aborted_local; +import static org.briarproject.mailbox.R.string.connection_aborted_remote; +import static org.briarproject.mailbox.R.string.connection_failed; +import static org.briarproject.mailbox.R.string.contact_exchange_failed; +import static org.briarproject.mailbox.R.string.exchanging_contact_details; +import static org.briarproject.mailbox.R.string.mailbox_already_paired; +import static org.briarproject.mailbox.R.string.mailbox_paired; +import static org.briarproject.mailbox.R.string.waiting_for_contact_to_scan; + +public class MailboxExchangeActivity extends KeyAgreementActivity implements + ContactExchangeListener { + + private static final Logger LOG = + Logger.getLogger(MailboxExchangeActivity.class.getName()); + + @Inject + volatile MailboxExchangeTask mailboxExchangeTask; + @Inject + volatile IdentityManager identityManager; + + @Inject + @DatabaseExecutor + volatile Executor dbExecutor; + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + protected void startContactExchange(KeyAgreementResult result) { + dbExecutor.execute(() -> { + LocalAuthor localAuthor; + // Load the local pseudonym + try { + localAuthor = identityManager.getLocalAuthor(); + } catch (DbException e) { + logException(LOG, WARNING, e); + contactExchangeFailed(); + return; + } + + // Exchange contact details + mailboxExchangeTask.startExchange(MailboxExchangeActivity.this, + localAuthor, result.getMasterKey(), + result.getConnection(), result.getTransportId(), + result.wasAlice(), PRIVATE_MAILBOX, MAILBOX_OWNER); + }); + } + + @Override + public void contactExchangeSucceeded(Author remoteAuthor) { + runOnUiThreadUnlessDestroyed(() -> { + Toast.makeText(MailboxExchangeActivity.this, + mailbox_paired, LENGTH_LONG) + .show(); + supportFinishAfterTransition(); + }); + } + + @Override + public void duplicateContact(Author remoteAuthor) { + runOnUiThreadUnlessDestroyed(() -> { + Toast.makeText(MailboxExchangeActivity.this, + mailbox_already_paired, LENGTH_LONG) + .show(); + finish(); + }); + } + + @Override + public void contactExchangeFailed() { + runOnUiThreadUnlessDestroyed(() -> { + Toast.makeText(MailboxExchangeActivity.this, + contact_exchange_failed, LENGTH_LONG).show(); + finish(); + }); + } + + @UiThread + @Override + public void keyAgreementFailed() { + // TODO show failure somewhere persistent? + Toast.makeText(this, connection_failed, + LENGTH_LONG).show(); + } + + @UiThread + @Override + public String keyAgreementWaiting() { + return getString(waiting_for_contact_to_scan); + } + + @UiThread + @Override + public String keyAgreementStarted() { + return getString(authenticating_with_device); + } + + @UiThread + @Override + public String keyAgreementAborted(boolean remoteAborted) { + // TODO show abort somewhere persistent? + Toast.makeText(this, + remoteAborted ? connection_aborted_remote : + connection_aborted_local, LENGTH_LONG) + .show(); + return null; + } + + @UiThread + @Override + public String keyAgreementFinished(KeyAgreementResult result) { + startContactExchange(result); + return getString(exchanging_contact_details); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/PreviewConsumer.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/PreviewConsumer.java new file mode 100644 index 0000000000000000000000000000000000000000..735bb90edb22d0d418099be8e044bdcf335a9114 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/PreviewConsumer.java @@ -0,0 +1,17 @@ +package org.briarproject.mailbox.keyagreement; + +import android.hardware.Camera; +import android.support.annotation.UiThread; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@SuppressWarnings("deprecation") +@NotNullByDefault +interface PreviewConsumer { + + @UiThread + void start(Camera camera, int cameraIndex); + + @UiThread + void stop(); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/QrCodeDecoder.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/QrCodeDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..192fb50b1f3bc2a6648931ef176885945f84ddd7 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/QrCodeDecoder.java @@ -0,0 +1,148 @@ +package org.briarproject.mailbox.keyagreement; + +import android.hardware.Camera; +import android.hardware.Camera.CameraInfo; +import android.hardware.Camera.PreviewCallback; +import android.hardware.Camera.Size; +import android.os.AsyncTask; +import android.support.annotation.UiThread; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.LuminanceSource; +import com.google.zxing.PlanarYUVLuminanceSource; +import com.google.zxing.Reader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; +import com.google.zxing.qrcode.QRCodeReader; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; + +import java.util.logging.Logger; + +import static com.google.zxing.DecodeHintType.CHARACTER_SET; +import static java.util.Collections.singletonMap; +import static java.util.logging.Level.WARNING; + +@SuppressWarnings("deprecation") +@MethodsNotNullByDefault +@ParametersNotNullByDefault +class QrCodeDecoder implements PreviewConsumer, PreviewCallback { + + private static final Logger LOG = + Logger.getLogger(QrCodeDecoder.class.getName()); + + private final Reader reader = new QRCodeReader(); + private final ResultCallback callback; + + private Camera camera = null; + private int cameraIndex = 0; + + QrCodeDecoder(ResultCallback callback) { + this.callback = callback; + } + + @Override + public void start(Camera camera, int cameraIndex) { + this.camera = camera; + this.cameraIndex = cameraIndex; + askForPreviewFrame(); + } + + @Override + public void stop() { + camera = null; + cameraIndex = 0; + } + + @UiThread + private void askForPreviewFrame() { + if (camera != null) camera.setOneShotPreviewCallback(this); + } + + @UiThread + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + if (camera == this.camera) { + try { + Size size = camera.getParameters().getPreviewSize(); + // The preview should be in NV21 format: width * height bytes of + // Y followed by width * height / 2 bytes of interleaved U and V + if (data.length == size.width * size.height * 3 / 2) { + CameraInfo info = new CameraInfo(); + Camera.getCameraInfo(cameraIndex, info); + new DecoderTask(data, size.width, size.height, + info.orientation).execute(); + } else { + // Camera parameters have changed - ask for a new preview + LOG.info("Preview size does not match camera parameters"); + askForPreviewFrame(); + } + } catch (RuntimeException e) { + LOG.log(WARNING, "Error getting camera parameters.", e); + } + } else { + LOG.info("Camera has changed, ignoring preview frame"); + } + } + + private class DecoderTask extends AsyncTask<Void, Void, Void> { + + private final byte[] data; + private final int width, height, orientation; + + private DecoderTask(byte[] data, int width, int height, + int orientation) { + this.data = data; + this.width = width; + this.height = height; + this.orientation = orientation; + } + + @Override + protected Void doInBackground(Void... params) { + BinaryBitmap bitmap = binarize(data, width, height, orientation); + Result result; + try { + result = reader.decode(bitmap, + singletonMap(CHARACTER_SET, "ISO8859_1")); + } catch (ReaderException e) { + // No barcode found + return null; + } catch (RuntimeException e) { + LOG.warning("Invalid preview frame"); + return null; + } finally { + reader.reset(); + } + callback.handleResult(result); + return null; + } + + @Override + protected void onPostExecute(Void result) { + askForPreviewFrame(); + } + } + + private static BinaryBitmap binarize(byte[] data, int width, int height, + int orientation) { + // Crop to a square at the top (portrait) or left (landscape) of the + // screen - this will be faster to decode and should include + // everything visible in the viewfinder + int crop = Math.min(width, height); + int left = orientation >= 180 ? width - crop : 0; + int top = orientation >= 180 ? height - crop : 0; + LuminanceSource src = new PlanarYUVLuminanceSource(data, width, + height, left, top, crop, crop, false); + return new BinaryBitmap(new HybridBinarizer(src)); + } + + @NotNullByDefault + interface ResultCallback { + + void handleResult(Result result); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/QrCodeUtils.java b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/QrCodeUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..67f55b5a91464398ae9090c25d5453ad4a666b4a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/keyagreement/QrCodeUtils.java @@ -0,0 +1,56 @@ +package org.briarproject.mailbox.keyagreement; + +import android.graphics.Bitmap; +import android.util.DisplayMetrics; + +import com.google.zxing.WriterException; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import static android.graphics.Bitmap.Config.ARGB_8888; +import static android.graphics.Color.BLACK; +import static android.graphics.Color.WHITE; +import static com.google.zxing.BarcodeFormat.QR_CODE; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; + +@NotNullByDefault +class QrCodeUtils { + + private static final Logger LOG = + Logger.getLogger(QrCodeUtils.class.getName()); + + @Nullable + static Bitmap createQrCode(DisplayMetrics dm, String input) { + int smallestDimen = Math.min(dm.widthPixels, dm.heightPixels); + try { + // Generate QR code + BitMatrix encoded = new QRCodeWriter().encode(input, QR_CODE, + smallestDimen, smallestDimen); + return renderQrCode(encoded); + } catch (WriterException e) { + logException(LOG, WARNING, e); + return null; + } + } + + private static Bitmap renderQrCode(BitMatrix matrix) { + int width = matrix.getWidth(); + int height = matrix.getHeight(); + int[] pixels = new int[width * height]; + for (int x = 0; x < width; x++) { + for (int y = 0; y < height; y++) { + pixels[y * width + x] = matrix.get(x, y) ? BLACK : WHITE; + } + } + Bitmap qr = Bitmap.createBitmap(width, height, ARGB_8888); + qr.setPixels(pixels, 0, width, 0, 0, width, height); + return qr; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/logging/BriefLogFormatter.java b/mailbox-android/src/main/java/org/briarproject/mailbox/logging/BriefLogFormatter.java new file mode 100644 index 0000000000000000000000000000000000000000..0c6c3b1534953af7f78218d320235c215fd0243c --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/logging/BriefLogFormatter.java @@ -0,0 +1,63 @@ +package org.briarproject.mailbox.logging; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.TimeZone; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +import javax.annotation.concurrent.ThreadSafe; + +import static java.util.Locale.US; + +@ThreadSafe +@NotNullByDefault +public class BriefLogFormatter extends Formatter { + + private final Object lock = new Object(); + private final DateFormat dateFormat; // Locking: lock + private final Date date; // Locking: lock + + public BriefLogFormatter() { + synchronized (lock) { + dateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS ", US); + dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); + date = new Date(); + } + } + + @Override + public String format(LogRecord record) { + String dateString; + synchronized (lock) { + date.setTime(record.getMillis()); + dateString = dateFormat.format(date); + } + StringBuilder sb = new StringBuilder(dateString); + sb.append(record.getLevel().getName().charAt(0)).append('/'); + String tag = record.getLoggerName(); + tag = tag.substring(tag.lastIndexOf('.') + 1); + sb.append(tag).append(": "); + sb.append(record.getMessage()); + Throwable t = record.getThrown(); + if (t != null) { + sb.append('\n'); + appendThrowable(sb, t); + } + return sb.toString(); + } + + private void appendThrowable(StringBuilder sb, Throwable t) { + sb.append(t); + for (StackTraceElement e : t.getStackTrace()) + sb.append("\n at ").append(e); + Throwable cause = t.getCause(); + if (cause != null) { + sb.append("\n Caused by: "); + appendThrowable(sb, cause); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/logging/CachingLogHandler.java b/mailbox-android/src/main/java/org/briarproject/mailbox/logging/CachingLogHandler.java new file mode 100644 index 0000000000000000000000000000000000000000..ea82defe01b14be01d8bda9d99bcc9d3ba08c4c7 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/logging/CachingLogHandler.java @@ -0,0 +1,48 @@ +package org.briarproject.mailbox.logging; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedList; +import java.util.Queue; +import java.util.logging.Handler; +import java.util.logging.LogRecord; + +import javax.annotation.concurrent.ThreadSafe; + +@ThreadSafe +@NotNullByDefault +public class CachingLogHandler extends Handler { + + private static final int MAX_RECENT_RECORDS = 100; + + private final Object lock = new Object(); + // Locking: lock + private final Queue<LogRecord> recent = new LinkedList<>(); + + @Override + public void publish(LogRecord record) { + synchronized (lock) { + recent.add(record); + if (recent.size() > MAX_RECENT_RECORDS) recent.poll(); + } + } + + @Override + public void flush() { + } + + @Override + public void close() { + synchronized (lock) { + recent.clear(); + } + } + + public Collection<LogRecord> getRecentLogRecords() { + synchronized (lock) { + return new ArrayList<>(recent); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/AuthorNameFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/AuthorNameFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..e93dfdbe9e9d7b59c59815282fa0bb6fbab0aab8 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/AuthorNameFragment.java @@ -0,0 +1,88 @@ +package org.briarproject.mailbox.login; + +import android.os.Bundle; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.util.StringUtils; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; + +import javax.annotation.Nullable; + +import static android.view.inputmethod.EditorInfo.IME_ACTION_NEXT; +import static android.view.inputmethod.EditorInfo.IME_ACTION_NONE; +import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH; +import static org.briarproject.mailbox.util.UiUtils.setError; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class AuthorNameFragment extends SetupFragment { + + private final static String TAG = AuthorNameFragment.class.getName(); + + private TextInputLayout authorNameWrapper; + private TextInputEditText authorNameInput; + private Button nextButton; + + public static AuthorNameFragment newInstance() { + return new AuthorNameFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + getActivity().setTitle(getString(R.string.setup_title)); + View v = inflater.inflate(R.layout.fragment_setup_author_name, + container, false); + authorNameWrapper = v.findViewById(R.id.nickname_entry_wrapper); + authorNameInput = v.findViewById(R.id.nickname_entry); + nextButton = v.findViewById(R.id.next); + + authorNameInput.addTextChangedListener(this); + nextButton.setOnClickListener(this); + + return v; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + protected String getHelpText() { + return getString(R.string.setup_name_explanation); + } + + @Override + public void onTextChanged(CharSequence authorName, int i, int i1, int i2) { + int authorNameLength = StringUtils.toUtf8(authorName.toString()).length; + boolean error = authorNameLength > MAX_AUTHOR_NAME_LENGTH; + setError(authorNameWrapper, getString(R.string.name_too_long), error); + boolean enabled = authorNameLength > 0 && !error; + authorNameInput + .setImeOptions(enabled ? IME_ACTION_NEXT : IME_ACTION_NONE); + authorNameInput.setOnEditorActionListener(enabled ? this : null); + nextButton.setEnabled(enabled); + } + + @Override + public void onClick(View view) { + setupController.setAuthorName(authorNameInput.getText().toString()); + setupController.showPasswordFragment(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/ChangePasswordActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/ChangePasswordActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..1d759d9999b5fef037b6f626cb06d4ac6ecbc9e4 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/ChangePasswordActivity.java @@ -0,0 +1,154 @@ +package org.briarproject.mailbox.login; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; +import android.widget.Toast; + +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.MailboxActivity; +import org.briarproject.mailbox.controller.handler.UiResultHandler; +import org.briarproject.mailbox.util.UiUtils; + +import javax.inject.Inject; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; + +public class ChangePasswordActivity extends MailboxActivity + implements OnClickListener, OnEditorActionListener { + + @Inject + protected PasswordController passwordController; + + private TextInputLayout currentPasswordEntryWrapper; + private TextInputLayout newPasswordEntryWrapper; + private TextInputLayout newPasswordConfirmationWrapper; + private EditText currentPassword; + private EditText newPassword; + private EditText newPasswordConfirmation; + private StrengthMeter strengthMeter; + private Button changePasswordButton; + private ProgressBar progress; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + setContentView(R.layout.activity_change_password); + + currentPasswordEntryWrapper = + findViewById(R.id.current_password_entry_wrapper); + newPasswordEntryWrapper = findViewById(R.id.new_password_entry_wrapper); + newPasswordConfirmationWrapper = + findViewById(R.id.new_password_confirm_wrapper); + currentPassword = findViewById(R.id.current_password_entry); + newPassword = findViewById(R.id.new_password_entry); + newPasswordConfirmation = findViewById(R.id.new_password_confirm); + strengthMeter = findViewById(R.id.strength_meter); + changePasswordButton = findViewById(R.id.change_password); + progress = findViewById(R.id.progress_wheel); + + TextWatcher tw = new TextWatcher() { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + enableOrDisableContinueButton(); + } + + @Override + public void afterTextChanged(Editable s) { + } + }; + + currentPassword.addTextChangedListener(tw); + newPassword.addTextChangedListener(tw); + newPasswordConfirmation.addTextChangedListener(tw); + newPasswordConfirmation.setOnEditorActionListener(this); + changePasswordButton.setOnClickListener(this); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + private void enableOrDisableContinueButton() { + if (progress == null) return; // Not created yet + if (newPassword.getText().length() > 0 && newPassword.hasFocus()) + strengthMeter.setVisibility(VISIBLE); + else strengthMeter.setVisibility(INVISIBLE); + String firstPassword = newPassword.getText().toString(); + String secondPassword = newPasswordConfirmation.getText().toString(); + boolean passwordsMatch = firstPassword.equals(secondPassword); + float strength = + passwordController.estimatePasswordStrength(firstPassword); + strengthMeter.setStrength(strength); + UiUtils.setError(newPasswordEntryWrapper, + getString(R.string.password_too_weak), + firstPassword.length() > 0 && strength < QUITE_WEAK); + UiUtils.setError(newPasswordConfirmationWrapper, + getString(R.string.passwords_do_not_match), + secondPassword.length() > 0 && !passwordsMatch); + changePasswordButton.setEnabled( + !currentPassword.getText().toString().isEmpty() && + passwordsMatch && strength >= QUITE_WEAK); + } + + @Override + public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { + hideSoftKeyboard(v); + return true; + } + + @Override + public void onClick(View view) { + // Replace the button with a progress bar + changePasswordButton.setVisibility(INVISIBLE); + progress.setVisibility(VISIBLE); + passwordController.changePassword(currentPassword.getText().toString(), + newPassword.getText().toString(), + new UiResultHandler<Boolean>(this) { + @Override + public void onResultUi(@NonNull Boolean result) { + if (result) { + Toast.makeText(ChangePasswordActivity.this, + R.string.password_changed, + Toast.LENGTH_LONG).show(); + setResult(RESULT_OK); + supportFinishAfterTransition(); + } else { + tryAgain(); + } + } + }); + } + + private void tryAgain() { + UiUtils.setError(currentPasswordEntryWrapper, + getString(R.string.try_again), true); + changePasswordButton.setVisibility(VISIBLE); + progress.setVisibility(INVISIBLE); + currentPassword.setText(""); + + // show the keyboard again + showSoftKeyboard(currentPassword); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/DozeFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/DozeFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a24ad0485c6eeaf6411e84fe3cf641e7fb4699e3 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/DozeFragment.java @@ -0,0 +1,114 @@ +package org.briarproject.mailbox.login; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.ProgressBar; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.util.UiUtils; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.mailbox.activity.RequestCodes.REQUEST_DOZE_WHITELISTING; +import static org.briarproject.mailbox.login.PowerView.*; +import static org.briarproject.mailbox.util.UiUtils.showOnboardingDialog; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class DozeFragment extends SetupFragment + implements OnCheckedChangedListener { + + private final static String TAG = DozeFragment.class.getName(); + + private DozeView dozeView; + private HuaweiView huaweiView; + private Button next; + private ProgressBar progressBar; + private boolean secondAttempt = false; + + public static DozeFragment newInstance() { + return new DozeFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + getActivity().setTitle(getString(R.string.setup_doze_title)); + setHasOptionsMenu(false); + View v = inflater.inflate(R.layout.fragment_setup_doze, container, + false); + dozeView = v.findViewById(R.id.dozeView); + dozeView.setOnCheckedChangedListener(this); + huaweiView = v.findViewById(R.id.huaweiView); + huaweiView.setOnCheckedChangedListener(this); + next = v.findViewById(R.id.next); + progressBar = v.findViewById(R.id.progress); + + dozeView.setOnButtonClickListener(this::askForDozeWhitelisting); + next.setOnClickListener(this); + + return v; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Override + protected String getHelpText() { + return getString(R.string.setup_doze_explanation); + } + + @Override + public void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_DOZE_WHITELISTING) { + if (!dozeView.needsToBeShown() || secondAttempt) { + dozeView.setChecked(true); + } else if (getContext() != null) { + secondAttempt = true; + showOnboardingDialog(getContext(), getHelpText()); + } + } + } + + @Override + public void onCheckedChanged() { + if (dozeView.isChecked() && huaweiView.isChecked()) { + next.setEnabled(true); + } else { + next.setEnabled(false); + } + } + + @SuppressLint("BatteryLife") + private void askForDozeWhitelisting() { + if (getContext() == null) return; + Intent i = UiUtils.getDozeWhitelistingIntent(getContext()); + startActivityForResult(i, REQUEST_DOZE_WHITELISTING); + } + + @Override + public void onClick(View view) { + next.setVisibility(INVISIBLE); + progressBar.setVisibility(VISIBLE); + setupController.createAccount(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/DozeView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/DozeView.java new file mode 100644 index 0000000000000000000000000000000000000000..4b490c07e8a546956d815b9302a89870d1420ca9 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/DozeView.java @@ -0,0 +1,60 @@ +package org.briarproject.mailbox.login; + + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.R; + +import static org.briarproject.mailbox.util.UiUtils.needsDozeWhitelisting; + +@UiThread +@NotNullByDefault +class DozeView extends PowerView { + + @Nullable + private Runnable onButtonClickListener; + + public DozeView(Context context) { + this(context, null); + } + + public DozeView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public DozeView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + setText(R.string.setup_doze_intro); + setButtonText(R.string.setup_doze_button); + } + + @Override + public boolean needsToBeShown() { + return needsToBeShown(getContext()); + } + + public static boolean needsToBeShown(Context context) { + return needsDozeWhitelisting(context); + } + + @Override + protected int getHelpText() { + return R.string.setup_doze_explanation; + } + + @Override + protected void onButtonClick() { + if (onButtonClickListener == null) throw new IllegalStateException(); + onButtonClickListener.run(); + } + + public void setOnButtonClickListener(Runnable runnable) { + onButtonClickListener = runnable; + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/HuaweiView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/HuaweiView.java new file mode 100644 index 0000000000000000000000000000000000000000..38a5f21c2a9eeb795dea9c51cfbbcc7e896ed879 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/HuaweiView.java @@ -0,0 +1,76 @@ +package org.briarproject.mailbox.login; + + +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.support.annotation.StringRes; +import android.support.annotation.UiThread; +import android.util.AttributeSet; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.R; + +import java.util.List; + +import javax.annotation.Nullable; + +import static android.os.Build.VERSION.SDK_INT; + +@UiThread +@NotNullByDefault +class HuaweiView extends PowerView { + + private final static String PACKAGE_NAME = "com.huawei.systemmanager"; + private final static String CLASS_NAME = + PACKAGE_NAME + ".optimize.process.ProtectActivity"; + + public HuaweiView(Context context) { + this(context, null); + } + + public HuaweiView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public HuaweiView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + setText(R.string.setup_huawei_text); + setButtonText(R.string.setup_huawei_button); + } + + @Override + public boolean needsToBeShown() { + return needsToBeShown(getContext()); + } + + public static boolean needsToBeShown(Context context) { + // "Protected apps" no longer exists on Huawei EMUI 5.0 (Android 7.0) + if (SDK_INT >= 24) return false; + PackageManager pm = context.getPackageManager(); + List<ResolveInfo> resolveInfos = pm.queryIntentActivities(getIntent(), + PackageManager.MATCH_DEFAULT_ONLY); + return !resolveInfos.isEmpty(); + } + + @Override + @StringRes + protected int getHelpText() { + return R.string.setup_huawei_help; + } + + @Override + protected void onButtonClick() { + getContext().startActivity(getIntent()); + setChecked(true); + } + + private static Intent getIntent() { + Intent intent = new Intent(); + intent.setClassName(PACKAGE_NAME, CLASS_NAME); + return intent; + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/OpenDatabaseActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/OpenDatabaseActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..ed6ad1913cd037d4b75696e20473e1ba0cb8273a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/OpenDatabaseActivity.java @@ -0,0 +1,93 @@ +package org.briarproject.mailbox.login; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.widget.ImageView; +import android.widget.TextView; + +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState; +import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.MailboxActivity; +import org.briarproject.mailbox.navdrawer.NavDrawerActivity; + +import javax.annotation.ParametersAreNonnullByDefault; +import javax.inject.Inject; + +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES; + +@ParametersAreNonnullByDefault +public class OpenDatabaseActivity extends MailboxActivity + implements EventListener { + + @Inject + LifecycleManager lifecycleManager; + @Inject + EventBus eventBus; + + private TextView textView; + private ImageView imageView; + private boolean showingMigration = false; + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + setContentView(R.layout.activity_open_database); + textView = findViewById(R.id.textView); + imageView = findViewById(R.id.imageView); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onStart() { + super.onStart(); + LifecycleState state = lifecycleManager.getLifecycleState(); + if (state.isAfter(STARTING_SERVICES)) { + finishAndStartApp(); + } else { + if (state == MIGRATING_DATABASE) showMigration(); + eventBus.addListener(this); + } + } + + @Override + protected void onStop() { + super.onStop(); + eventBus.removeListener(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof LifecycleEvent) { + LifecycleState state = ((LifecycleEvent) e).getLifecycleState(); + if (state.isAfter(STARTING_SERVICES)) + runOnUiThreadUnlessDestroyed(this::finishAndStartApp); + else if (state == MIGRATING_DATABASE) + runOnUiThreadUnlessDestroyed(this::showMigration); + } + } + + private void showMigration() { + if (showingMigration) return; + textView.setText(R.string.startup_migrate_database); + imageView.setImageResource(R.drawable.startup_migration); + showingMigration = true; + } + + private void finishAndStartApp() { + startActivity(new Intent(this, NavDrawerActivity.class)); + supportFinishAfterTransition(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..12cf30b780aad1f5b24bc03f307b4bcc3524eb2c --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordActivity.java @@ -0,0 +1,166 @@ +package org.briarproject.mailbox.login; + +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.TextInputLayout; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ProgressBar; + + +import org.briarproject.mailbox.Localizer; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.BaseActivity; +import org.briarproject.mailbox.controller.MailboxController; +import org.briarproject.mailbox.controller.handler.UiResultHandler; +import org.briarproject.mailbox.util.UiUtils; + +import javax.inject.Inject; + +import static android.content.Intent.ACTION_MAIN; +import static android.content.Intent.CATEGORY_HOME; +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; + +public class PasswordActivity extends BaseActivity { + + @Inject + PasswordController passwordController; + + @Inject + MailboxController mailboxController; + + private Button signInButton; + private ProgressBar progress; + private TextInputLayout input; + private EditText password; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + // fade-in after splash screen instead of default animation + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + + if (!passwordController.accountExists()) { + deleteAccount(); + return; + } + + setContentView(R.layout.activity_password); + signInButton = findViewById(R.id.btn_sign_in); + progress = findViewById(R.id.progress_wheel); + input = findViewById(R.id.password_layout); + password = findViewById(R.id.edit_password); + password.setOnEditorActionListener((v, actionId, event) -> { + validatePassword(); + return true; + }); + password.addTextChangedListener(new TextWatcher() { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + if (count > 0) UiUtils.setError(input, null, false); + } + + @Override + public void afterTextChanged(Editable s) { + } + }); + } + + @Override + public void onStart() { + super.onStart(); + // If the user has already signed in, clean up this instance + if (mailboxController.hasEncryptionKey()) { + setResult(RESULT_OK); + finish(); + } + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public void onBackPressed() { + // Show the home screen rather than another password prompt + Intent intent = new Intent(ACTION_MAIN); + intent.addCategory(CATEGORY_HOME); + startActivity(intent); + } + + private void deleteAccount() { + passwordController.deleteAccount(this); + Localizer.reinitialize(); + UiUtils.setTheme(this, getString(R.string.pref_theme_light_value)); + setResult(RESULT_CANCELED); + Intent i = new Intent(this, SetupActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); + startActivity(i); + } + + public void onSignInClick(View v) { + validatePassword(); + } + + public void onForgottenPasswordClick(View v) { + // TODO Encapsulate the dialog in a re-usable fragment + AlertDialog.Builder builder = new AlertDialog.Builder(this, + R.style.BriarDialogTheme); + builder.setTitle(R.string.dialog_title_lost_password); + builder.setMessage(R.string.dialog_message_lost_password); + builder.setPositiveButton(R.string.cancel, null); + builder.setNegativeButton(R.string.delete, + (dialog, which) -> deleteAccount()); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void validatePassword() { + hideSoftKeyboard(password); + signInButton.setVisibility(INVISIBLE); + progress.setVisibility(VISIBLE); + passwordController.validatePassword(password.getText().toString(), + new UiResultHandler<Boolean>(this) { + @Override + public void onResultUi(@NonNull Boolean result) { + if (result) { + setResult(RESULT_OK); + supportFinishAfterTransition(); + // don't show closing animation, + // but one for opening NavDrawerActivity + overridePendingTransition(R.anim.screen_new_in, + R.anim.screen_old_out); + } else { + tryAgain(); + } + } + }); + } + + private void tryAgain() { + UiUtils.setError(input, getString(R.string.try_again), true); + signInButton.setVisibility(VISIBLE); + progress.setVisibility(INVISIBLE); + password.setText(""); + + // show the keyboard again + showSoftKeyboard(password); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordController.java new file mode 100644 index 0000000000000000000000000000000000000000..6a5e19018eb1804911ecddb6403481bc041edee5 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordController.java @@ -0,0 +1,18 @@ +package org.briarproject.mailbox.login; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.controller.ConfigController; +import org.briarproject.mailbox.controller.handler.ResultHandler; + +@NotNullByDefault +public interface PasswordController extends ConfigController { + + float estimatePasswordStrength(String password); + + void validatePassword(String password, + ResultHandler<Boolean> resultHandler); + + void changePassword(String password, String newPassword, + ResultHandler<Boolean> resultHandler); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..b78ff869145973b45d2e9eb042fe981a71c5ab23 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordControllerImpl.java @@ -0,0 +1,95 @@ +package org.briarproject.mailbox.login; + +import android.content.SharedPreferences; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.CryptoExecutor; +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.util.StringUtils; +import org.briarproject.mailbox.controller.ConfigControllerImpl; +import org.briarproject.mailbox.controller.handler.ResultHandler; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.now; + +@NotNullByDefault +public class PasswordControllerImpl extends ConfigControllerImpl + implements PasswordController { + + private static final Logger LOG = + Logger.getLogger(PasswordControllerImpl.class.getName()); + + protected final Executor cryptoExecutor; + protected final CryptoComponent crypto; + private final PasswordStrengthEstimator strengthEstimator; + + @Inject + PasswordControllerImpl(SharedPreferences briarPrefs, + DatabaseConfig databaseConfig, + @CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto, + PasswordStrengthEstimator strengthEstimator) { + super(briarPrefs, databaseConfig); + this.cryptoExecutor = cryptoExecutor; + this.crypto = crypto; + this.strengthEstimator = strengthEstimator; + } + + @Override + public float estimatePasswordStrength(String password) { + return strengthEstimator.estimateStrength(password); + } + + @Override + public void validatePassword(String password, + ResultHandler<Boolean> resultHandler) { + byte[] encrypted = getEncryptedKey(); + cryptoExecutor.execute(() -> { + byte[] key = crypto.decryptWithPassword(encrypted, password); + if (key == null) { + resultHandler.onResult(false); + } else { + databaseConfig.setEncryptionKey(new SecretKey(key)); + resultHandler.onResult(true); + } + }); + } + + @Override + public void changePassword(String password, String newPassword, + ResultHandler<Boolean> resultHandler) { + byte[] encrypted = getEncryptedKey(); + cryptoExecutor.execute(() -> { + byte[] key = crypto.decryptWithPassword(encrypted, password); + if (key == null) { + resultHandler.onResult(false); + } else { + String hex = + encryptDatabaseKey(new SecretKey(key), newPassword); + resultHandler.onResult(storeEncryptedDatabaseKey(hex)); + } + }); + } + + private byte[] getEncryptedKey() { + String hex = getEncryptedDatabaseKey(); + if (hex == null) + throw new IllegalStateException("Encrypted database key is null"); + return StringUtils.fromHexString(hex); + } + + @CryptoExecutor + String encryptDatabaseKey(SecretKey key, String password) { + long start = now(); + byte[] encrypted = crypto.encryptWithPassword(key.getBytes(), password); + logDuration(LOG, "Key derivation", start); + return StringUtils.toHexString(encrypted); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a45a7e3997bdb7bb7404b778e02a8d95a81b74d0 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PasswordFragment.java @@ -0,0 +1,128 @@ +package org.briarproject.mailbox.login; + +import android.os.Bundle; +import android.os.IBinder; +import android.support.design.widget.TextInputEditText; +import android.support.design.widget.TextInputLayout; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.ProgressBar; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.util.UiUtils; + +import javax.annotation.Nullable; + +import static android.content.Context.INPUT_METHOD_SERVICE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class PasswordFragment extends SetupFragment { + + private final static String TAG = PasswordFragment.class.getName(); + + private TextInputLayout passwordEntryWrapper; + private TextInputLayout passwordConfirmationWrapper; + private TextInputEditText passwordEntry; + private TextInputEditText passwordConfirmation; + private StrengthMeter strengthMeter; + private Button nextButton; + private ProgressBar progressBar; + + public static PasswordFragment newInstance() { + return new PasswordFragment(); + } + + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + getActivity().setTitle(getString(R.string.setup_password_intro)); + View v = inflater.inflate(R.layout.fragment_setup_password, container, + false); + + strengthMeter = v.findViewById(R.id.strength_meter); + passwordEntryWrapper = v.findViewById(R.id.password_entry_wrapper); + passwordEntry = v.findViewById(R.id.password_entry); + passwordConfirmationWrapper = + v.findViewById(R.id.password_confirm_wrapper); + passwordConfirmation = v.findViewById(R.id.password_confirm); + nextButton = v.findViewById(R.id.next); + progressBar = v.findViewById(R.id.progress); + + passwordEntry.addTextChangedListener(this); + passwordConfirmation.addTextChangedListener(this); + nextButton.setOnClickListener(this); + + return v; + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + + // the controller is not yet available in onCreateView() + if (!setupController.needToShowDozeFragment()) { + nextButton.setText(R.string.create_account_button); + } + } + + @Override + protected String getHelpText() { + return getString(R.string.setup_password_explanation); + } + + @Override + public void onTextChanged(CharSequence authorName, int i, int i1, int i2) { + String password1 = passwordEntry.getText().toString(); + String password2 = passwordConfirmation.getText().toString(); + boolean passwordsMatch = password1.equals(password2); + + strengthMeter + .setVisibility(password1.length() > 0 ? VISIBLE : INVISIBLE); + float strength = setupController.estimatePasswordStrength(password1); + strengthMeter.setStrength(strength); + boolean strongEnough = strength >= QUITE_WEAK; + + UiUtils.setError(passwordEntryWrapper, + getString(R.string.password_too_weak), + password1.length() > 0 && !strongEnough); + UiUtils.setError(passwordConfirmationWrapper, + getString(R.string.passwords_do_not_match), + password2.length() > 0 && !passwordsMatch); + + boolean enabled = passwordsMatch && strongEnough; + nextButton.setEnabled(enabled); + passwordConfirmation.setOnEditorActionListener(enabled ? this : null); + } + + @Override + public void onClick(View view) { + IBinder token = passwordEntry.getWindowToken(); + Object o = getContext().getSystemService(INPUT_METHOD_SERVICE); + ((InputMethodManager) o).hideSoftInputFromWindow(token, 0); + setupController.setPassword(passwordEntry.getText().toString()); + if (setupController.needToShowDozeFragment()) { + setupController.showDozeFragment(); + } else { + nextButton.setVisibility(INVISIBLE); + progressBar.setVisibility(VISIBLE); + setupController.createAccount(); + } + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/PowerView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PowerView.java new file mode 100644 index 0000000000000000000000000000000000000000..16d48cd3454390a90bf368c9c66915583a4b2acf --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/PowerView.java @@ -0,0 +1,162 @@ +package org.briarproject.mailbox.login; + +import android.content.Context; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.Nullable; +import android.support.annotation.StringRes; +import android.support.annotation.UiThread; +import android.support.constraint.ConstraintLayout; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.TextView; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.R; + +import static android.content.Context.LAYOUT_INFLATER_SERVICE; +import static org.briarproject.mailbox.util.UiUtils.showOnboardingDialog; + +@UiThread +@NotNullByDefault +abstract class PowerView extends ConstraintLayout { + + private final TextView textView; + private final ImageView checkImage; + private final Button button; + + private boolean checked = false; + + @Nullable + private OnCheckedChangedListener onCheckedChangedListener; + + public PowerView(Context context) { + this(context, null); + } + + public PowerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + @SuppressWarnings("ConstantConditions") + public PowerView(Context context, @Nullable AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(LAYOUT_INFLATER_SERVICE); + View v = inflater.inflate(R.layout.power_view, this, true); + + textView = v.findViewById(R.id.textView); + checkImage = v.findViewById(R.id.checkImage); + button = v.findViewById(R.id.button); + button.setOnClickListener(view -> onButtonClick()); + ImageButton helpButton = v.findViewById(R.id.helpButton); + helpButton.setOnClickListener(view -> onHelpButtonClick()); + + // we need to manage the checkImage state ourselves, because automatic + // state saving is done based on the view's ID and there can be + // multiple ImageViews with the same ID in the view hierarchy + setSaveFromParentEnabled(true); + + if (!isInEditMode() && !needsToBeShown()) { + setVisibility(GONE); + } + } + + @Nullable + @Override + protected Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.value = new boolean[] {checked}; + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + setChecked(ss.value[0]); // also calls listener + } + + public abstract boolean needsToBeShown(); + + public void setChecked(boolean checked) { + this.checked = checked; + if (checked) { + checkImage.setVisibility(VISIBLE); + } else { + checkImage.setVisibility(INVISIBLE); + } + if (onCheckedChangedListener != null) { + onCheckedChangedListener.onCheckedChanged(); + } + } + + public boolean isChecked() { + return getVisibility() == GONE || checked; + } + + public void setOnCheckedChangedListener( + OnCheckedChangedListener onCheckedChangedListener) { + this.onCheckedChangedListener = onCheckedChangedListener; + } + + @StringRes + protected abstract int getHelpText(); + + protected void setText(@StringRes int res) { + textView.setText(res); + } + + protected void setButtonText(@StringRes int res) { + button.setText(res); + } + + protected abstract void onButtonClick(); + + private void onHelpButtonClick() { + showOnboardingDialog(getContext(), + getContext().getString(getHelpText())); + } + + private static class SavedState extends BaseSavedState { + private boolean[] value = {false}; + + private SavedState(@Nullable Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + in.readBooleanArray(value); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + out.writeBooleanArray(value); + } + + static final Creator<SavedState> CREATOR + = new Creator<SavedState>() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + interface OnCheckedChangedListener { + void onCheckedChanged(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..745c99b3aa085ece67125c1a8bed13596fb66bc0 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupActivity.java @@ -0,0 +1,110 @@ +package org.briarproject.mailbox.login; + +import android.annotation.TargetApi; +import android.content.Intent; +import android.os.Bundle; + +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.BaseActivity; +import org.briarproject.mailbox.fragment.BaseFragment; + +import javax.annotation.Nullable; +import javax.inject.Inject; + +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static org.briarproject.mailbox.fragment.BaseFragment.*; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class SetupActivity extends BaseActivity + implements BaseFragmentListener { + + private static final String STATE_KEY_AUTHOR_NAME = "authorName"; + private static final String STATE_KEY_PASSWORD = "password"; + + @Inject + SetupController setupController; + + @Nullable + private String authorName, password; + + @Override + public void onCreate(@Nullable Bundle state) { + super.onCreate(state); + // fade-in after splash screen instead of default animation + overridePendingTransition(R.anim.fade_in, R.anim.fade_out); + setContentView(R.layout.activity_fragment_container); + + if (state == null) { + if (setupController.accountExists()) + throw new AssertionError(); + showInitialFragment(AuthorNameFragment.newInstance()); + } else { + authorName = state.getString(STATE_KEY_AUTHOR_NAME); + password = state.getString(STATE_KEY_PASSWORD); + } + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + setupController.setSetupActivity(this); + } + + @Override + protected void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + if (authorName != null) + state.putString(STATE_KEY_AUTHOR_NAME, authorName); + if (password != null) + state.putString(STATE_KEY_PASSWORD, password); + } + + @Nullable + String getAuthorName() { + return authorName; + } + + void setAuthorName(String authorName) { + this.authorName = authorName; + } + + @Nullable + String getPassword() { + return password; + } + + void setPassword(String password) { + this.password = password; + } + + void showPasswordFragment() { + if (authorName == null) throw new IllegalStateException(); + showNextFragment(PasswordFragment.newInstance()); + } + + @TargetApi(23) + void showDozeFragment() { + if (authorName == null) throw new IllegalStateException(); + if (password == null) throw new IllegalStateException(); + showNextFragment(DozeFragment.newInstance()); + } + + void showApp() { + Intent i = new Intent(this, OpenDatabaseActivity.class); + i.setFlags(FLAG_ACTIVITY_NEW_TASK); + startActivity(i); + supportFinishAfterTransition(); + overridePendingTransition(R.anim.screen_new_in, R.anim.screen_old_out); + } + + @Override + @Deprecated + public void runOnDbThread(Runnable runnable) { + throw new RuntimeException("Don't use this deprecated method here."); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupController.java new file mode 100644 index 0000000000000000000000000000000000000000..f1d34ffd6038d791646f1a0b6bc7258d7b73d76f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupController.java @@ -0,0 +1,32 @@ +package org.briarproject.mailbox.login; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; + +@NotNullByDefault +public interface SetupController extends PasswordController { + + void setSetupActivity(SetupActivity setupActivity); + + boolean needToShowDozeFragment(); + + void setAuthorName(String authorName); + + void setPassword(String password); + + /** + * This should be called after the author name has been set. + */ + void showPasswordFragment(); + + /** + * This should be called after the author name and the password have been + * set. + */ + void showDozeFragment(); + + /** + * This should be called after the author name and the password have been + * set. + */ + void createAccount(); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..6eec438b1056cac06fe6c54ebbe806d7d100d3a6 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupControllerImpl.java @@ -0,0 +1,114 @@ +package org.briarproject.mailbox.login; + +import android.content.SharedPreferences; +import android.support.annotation.Nullable; + +import org.briarproject.bramble.api.crypto.CryptoComponent; +import org.briarproject.bramble.api.crypto.CryptoExecutor; +import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator; +import org.briarproject.bramble.api.crypto.SecretKey; +import org.briarproject.bramble.api.db.DatabaseConfig; +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.controller.handler.ResultHandler; +import org.briarproject.mailbox.controller.handler.UiResultHandler; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +@NotNullByDefault +public class SetupControllerImpl extends PasswordControllerImpl + implements SetupController { + + private static final Logger LOG = + Logger.getLogger(SetupControllerImpl.class.getName()); + + @Nullable + private volatile SetupActivity setupActivity; + + @Inject + SetupControllerImpl(SharedPreferences briarPrefs, + DatabaseConfig databaseConfig, + @CryptoExecutor Executor cryptoExecutor, CryptoComponent crypto, + PasswordStrengthEstimator strengthEstimator) { + super(briarPrefs, databaseConfig, cryptoExecutor, crypto, + strengthEstimator); + } + + @Override + public void setSetupActivity(SetupActivity setupActivity) { + this.setupActivity = setupActivity; + } + + @Override + public boolean needToShowDozeFragment() { + SetupActivity setupActivity = this.setupActivity; + if (setupActivity == null) throw new IllegalStateException(); + return DozeView.needsToBeShown(setupActivity) || + HuaweiView.needsToBeShown(setupActivity); + } + + @Override + public void setAuthorName(String authorName) { + SetupActivity setupActivity = this.setupActivity; + if (setupActivity == null) throw new IllegalStateException(); + setupActivity.setAuthorName(authorName); + } + + @Override + public void setPassword(String password) { + SetupActivity setupActivity = this.setupActivity; + if (setupActivity == null) throw new IllegalStateException(); + setupActivity.setPassword(password); + } + + @Override + public void showPasswordFragment() { + SetupActivity setupActivity = this.setupActivity; + if (setupActivity == null) throw new IllegalStateException(); + setupActivity.showPasswordFragment(); + } + + @Override + public void showDozeFragment() { + SetupActivity setupActivity = this.setupActivity; + if (setupActivity == null) throw new IllegalStateException(); + setupActivity.showDozeFragment(); + } + + @Override + public void createAccount() { + SetupActivity setupActivity = this.setupActivity; + UiResultHandler<Void> resultHandler = + new UiResultHandler<Void>(setupActivity) { + @Override + public void onResultUi(Void result) { + if (setupActivity == null) + throw new IllegalStateException(); + setupActivity.showApp(); + } + }; + createAccount(resultHandler); + } + + // Package access for testing + void createAccount(ResultHandler<Void> resultHandler) { + SetupActivity setupActivity = this.setupActivity; + if (setupActivity == null) throw new IllegalStateException(); + String authorName = setupActivity.getAuthorName(); + if (authorName == null) throw new IllegalStateException(); + String password = setupActivity.getPassword(); + if (password == null) throw new IllegalStateException(); + cryptoExecutor.execute(() -> { + LOG.info("Creating account"); + databaseConfig.setLocalAuthorName(authorName); + SecretKey key = crypto.generateSecretKey(); + databaseConfig.setEncryptionKey(key); + String hex = encryptDatabaseKey(key, password); + storeEncryptedDatabaseKey(hex); + resultHandler.onResult(null); + }); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..024156afe0a4f4ee4bc79208240bb9c0b6a94ec4 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/SetupFragment.java @@ -0,0 +1,70 @@ +package org.briarproject.mailbox.login; + +import android.text.Editable; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View.OnClickListener; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + + +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.fragment.BaseFragment; + +import javax.inject.Inject; + +import static org.briarproject.mailbox.util.UiUtils.showOnboardingDialog; + + +abstract class SetupFragment extends BaseFragment implements TextWatcher, + OnEditorActionListener, OnClickListener { + + @Inject + SetupController setupController; + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.help_action, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == R.id.action_help) { + showOnboardingDialog(getContext(), getHelpText()); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + protected abstract String getHelpText(); + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, + int after) { + // noop + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, + int count) { + // noop + } + + @Override + public boolean onEditorAction(TextView textView, int actionId, + KeyEvent keyEvent) { + onClick(textView); + return true; + } + + @Override + public void afterTextChanged(Editable editable) { + // noop + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/login/StrengthMeter.java b/mailbox-android/src/main/java/org/briarproject/mailbox/login/StrengthMeter.java new file mode 100644 index 0000000000000000000000000000000000000000..eea81686790ef107a090aacc56ab054979a59395 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/login/StrengthMeter.java @@ -0,0 +1,75 @@ +package org.briarproject.mailbox.login; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.drawable.ClipDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; +import android.graphics.drawable.ShapeDrawable; +import android.util.AttributeSet; +import android.widget.ProgressBar; + +import static android.graphics.Color.BLACK; +import static android.graphics.Paint.Style.FILL; +import static android.graphics.Paint.Style.STROKE; +import static android.graphics.drawable.ClipDrawable.HORIZONTAL; +import static android.view.Gravity.LEFT; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_STRONG; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.STRONG; +import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.WEAK; + +public class StrengthMeter extends ProgressBar { + + private static final int MAX = 100; + public static final int RED = Color.rgb(255, 0, 0); + public static final int ORANGE = Color.rgb(255, 160, 0); + public static final int YELLOW = Color.rgb(255, 255, 0); + public static final int LIME = Color.rgb(180, 255, 0); + public static final int GREEN = Color.rgb(0, 255, 0); + + private final ShapeDrawable bar; + + public StrengthMeter(Context context) { + this(context, null); + } + + public StrengthMeter(Context context, AttributeSet attrs) { + super(context, attrs, android.R.attr.progressBarStyleHorizontal); + bar = new ShapeDrawable(); + bar.getPaint().setColor(RED); + ClipDrawable clip = new ClipDrawable(bar, LEFT, HORIZONTAL); + ShapeDrawable background = new ShapeDrawable(); + Paint p = background.getPaint(); + p.setStyle(FILL); + p.setColor(getResources().getColor(android.R.color.transparent)); + p.setStyle(STROKE); + p.setStrokeWidth(1); + p.setColor(BLACK); + Drawable[] layers = new Drawable[] { clip, background }; + setProgressDrawable(new LayerDrawable(layers)); + setIndeterminate(false); + } + + @Override + public int getMax() { + return MAX; + } + + public int getColor() { + return bar.getPaint().getColor(); + } + + public void setStrength(float strength) { + if (strength < 0 || strength > 1) throw new IllegalArgumentException(); + int colour; + if (strength < WEAK) colour = RED; + else if (strength < QUITE_WEAK) colour = ORANGE; + else if (strength < QUITE_STRONG) colour = YELLOW; + else if (strength < STRONG) colour = LIME; + else colour = GREEN; + bar.getPaint().setColor(colour); + setProgress((int) (strength * MAX)); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/logout/ExitActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/logout/ExitActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..3f62135c397505e8e3a379ffcad7a55ec3f448c7 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/logout/ExitActivity.java @@ -0,0 +1,29 @@ +package org.briarproject.mailbox.logout; + +import android.os.Build; +import android.os.Bundle; + +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.BaseActivity; + +import java.util.logging.Logger; + +public class ExitActivity extends BaseActivity { + + private static final Logger LOG = + Logger.getLogger(ExitActivity.class.getName()); + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + if (Build.VERSION.SDK_INT >= 21) finishAndRemoveTask(); + else finish(); + LOG.info("Exiting"); + System.exit(0); + } + + @Override + public void injectActivity(ActivityComponent component) { + + } +} \ No newline at end of file diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/logout/HideUiActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/logout/HideUiActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..def1d04d043515660c74ab49da2419e0a2e911bc --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/logout/HideUiActivity.java @@ -0,0 +1,21 @@ +package org.briarproject.mailbox.logout; + +import android.os.Bundle; + +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.BaseActivity; + + +public class HideUiActivity extends BaseActivity { + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + finish(); + } + + @Override + public void injectActivity(ActivityComponent component) { + + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/logout/SignOutFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/logout/SignOutFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..a98295b062158a55b81220e0c3ac6e8571aeb59a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/logout/SignOutFragment.java @@ -0,0 +1,35 @@ +package org.briarproject.mailbox.logout; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.fragment.BaseFragment; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public class SignOutFragment extends BaseFragment { + + public static final String TAG = SignOutFragment.class.getName(); + + @Override + public View onCreateView(@Nonnull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + return inflater.inflate(R.layout.fragment_sign_out, container, false); + } + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + // no need to inject + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..928f2820f0679922f8b9927dd02817213406dac2 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerActivity.java @@ -0,0 +1,420 @@ +package org.briarproject.mailbox.navdrawer; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.NavigationView; +import android.support.design.widget.NavigationView.OnNavigationItemSelectedListener; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.content.ContextCompat; +import android.support.v4.widget.DrawerLayout; +import android.support.v7.app.ActionBarDrawerToggle; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.TextView; + +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.LanTcpConstants; +import org.briarproject.bramble.api.plugin.TorConstants; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.MailboxActivity; +import org.briarproject.mailbox.controller.handler.UiResultHandler; +import org.briarproject.mailbox.fragment.BaseFragment; +import org.briarproject.mailbox.overview.OverviewFragment; +import org.briarproject.mailbox.logout.SignOutFragment; +import org.briarproject.mailbox.settings.SettingsActivity; + +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static android.support.v4.app.FragmentManager.POP_BACK_STACK_INCLUSIVE; +import static android.support.v4.view.GravityCompat.START; +import static android.support.v4.widget.DrawerLayout.LOCK_MODE_LOCKED_CLOSED; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.RUNNING; +import static org.briarproject.mailbox.MailboxService.EXTRA_STARTUP_FAILED; +import static org.briarproject.mailbox.activity.RequestCodes.REQUEST_PASSWORD; +import static org.briarproject.mailbox.fragment.BaseFragment.BaseFragmentListener; +import static org.briarproject.mailbox.navdrawer.NavDrawerController.ExpiryWarning; +import static org.briarproject.mailbox.navdrawer.NavDrawerController.ExpiryWarning.NO; +import static org.briarproject.mailbox.navdrawer.NavDrawerController.ExpiryWarning.UPDATE; +import static org.briarproject.mailbox.util.UiUtils.getDaysUntilExpiry; + +public class NavDrawerActivity extends MailboxActivity implements + BaseFragmentListener, TransportStateListener, + OnNavigationItemSelectedListener { + + public static final String INTENT_SIGN_OUT = "intent_sign_out"; + + private static final Logger LOG = + Logger.getLogger(NavDrawerActivity.class.getName()); + + private ActionBarDrawerToggle drawerToggle; + + @Inject + NavDrawerController controller; + @Inject + LifecycleManager lifecycleManager; + + private DrawerLayout drawerLayout; + private NavigationView navigation; + + private List<Transport> transports; + private BaseAdapter transportsAdapter; + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + exitIfStartupFailed(intent); + // TODO don't create new instances if they are on the stack (#606) + if (intent.getBooleanExtra(INTENT_SIGN_OUT, false)) { + signOut(false); + } + setIntent(null); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @SuppressWarnings("ConstantConditions") + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + exitIfStartupFailed(getIntent()); + setContentView(R.layout.activity_nav_drawer); + + Toolbar toolbar = findViewById(R.id.toolbar); + drawerLayout = findViewById(R.id.drawer_layout); + navigation = findViewById(R.id.navigation); + GridView transportsView = findViewById(R.id.transportsView); + + setSupportActionBar(toolbar); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setHomeButtonEnabled(true); + + drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, toolbar, + R.string.nav_drawer_open_description, + R.string.nav_drawer_close_description); + drawerLayout.addDrawerListener(drawerToggle); + navigation.setNavigationItemSelectedListener(this); + + initializeTransports(getLayoutInflater()); + transportsView.setAdapter(transportsAdapter); + + if (lifecycleManager.getLifecycleState().isAfter(RUNNING)) { + showSignOutFragment(); + } else if (state == null) { + startFragment(new OverviewFragment(), + R.id.nav_btn_overview); + } + if (getIntent() != null) { + onNewIntent(getIntent()); + } + } + + @Override + @SuppressLint("NewApi") + public void onStart() { + super.onStart(); + updateTransports(); + controller.showExpiryWarning(new UiResultHandler<ExpiryWarning>(this) { + @Override + public void onResultUi(ExpiryWarning expiry) { + if (expiry != NO) showExpiryWarning(expiry); + } + }); + } + + @Override + protected void onActivityResult(int request, int result, Intent data) { + super.onActivityResult(request, result, data); + if (request == REQUEST_PASSWORD && result == RESULT_OK) { + controller.shouldAskForDozeWhitelisting(this, + new UiResultHandler<Boolean>(this) { + @Override + public void onResultUi(Boolean ask) { + if (ask) { + showDozeDialog( + getString(R.string.setup_doze_intro)); + } + } + }); + } + } + + private void exitIfStartupFailed(Intent intent) { + if (intent.getBooleanExtra(EXTRA_STARTUP_FAILED, false)) { + finish(); + LOG.info("Exiting"); + System.exit(0); + } + } + + private void loadFragment(int fragmentId) { + // TODO re-use fragments from the manager when possible (#606) + switch (fragmentId) { + case R.id.nav_btn_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.nav_btn_signout: + signOut(); + break; + } + } + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + drawerLayout.closeDrawer(START); + clearBackStack(); + loadFragment(item.getItemId()); + // Don't display the Settings item as checked + return item.getItemId() != R.id.nav_btn_settings; + } + + @Override + public void onBackPressed() { + if (drawerLayout.isDrawerOpen(START)) { + drawerLayout.closeDrawer(START); + } else { + FragmentManager fm = getSupportFragmentManager(); + if (fm.findFragmentByTag(SignOutFragment.TAG) != null) { + finish(); + } else if (fm.getBackStackEntryCount() == 0 + && fm.findFragmentByTag(OverviewFragment.TAG) == null) { + /* + * This makes sure that the first fragment (ContactListFragment) the + * user sees is the same as the last fragment the user sees before + * exiting. This models the typical Google navigation behaviour such + * as in Gmail/Inbox. + */ + startFragment(new OverviewFragment(), + R.id.nav_btn_overview); + } else { + super.onBackPressed(); + } + } + } + + @Override + public void onPostCreate(Bundle savedInstanceState) { + super.onPostCreate(savedInstanceState); + drawerToggle.syncState(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + drawerToggle.onConfigurationChanged(newConfig); + } + + private void showSignOutFragment() { + drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED); + startFragment(new SignOutFragment()); + } + + private void signOut() { + drawerLayout.setDrawerLockMode(LOCK_MODE_LOCKED_CLOSED); + signOut(false); + finish(); + } + + private void startFragment(BaseFragment fragment, int itemId) { + navigation.setCheckedItem(itemId); + startFragment(fragment); + } + + private void startFragment(BaseFragment fragment) { + if (getSupportFragmentManager().getBackStackEntryCount() == 0) + startFragment(fragment, false); + else startFragment(fragment, true); + } + + private void startFragment(BaseFragment fragment, + boolean isAddedToBackStack) { + FragmentTransaction trans = + getSupportFragmentManager().beginTransaction() + .setCustomAnimations(R.anim.fade_in, + R.anim.fade_out, R.anim.fade_in, + R.anim.fade_out) + .replace(R.id.fragmentContainer, fragment, + fragment.getUniqueTag()); + if (isAddedToBackStack) { + trans.addToBackStack(fragment.getUniqueTag()); + } + trans.commit(); + } + + private void clearBackStack() { + getSupportFragmentManager().popBackStackImmediate(null, + POP_BACK_STACK_INCLUSIVE); + } + + @Override + public void handleDbException(DbException e) { + // Do nothing for now + } + + @SuppressWarnings("ConstantConditions") + private void showExpiryWarning(ExpiryWarning expiry) { + int daysUntilExpiry = getDaysUntilExpiry(); + if (daysUntilExpiry < 0) signOut(); + + // show expiry warning text + ViewGroup expiryWarning = findViewById(R.id.expiryWarning); + TextView expiryWarningText = + expiryWarning.findViewById(R.id.expiryWarningText); + // make close button functional + ImageView expiryWarningClose = + expiryWarning.findViewById(R.id.expiryWarningClose); + + // show a different snackbar in green if this is an update + if (expiry == UPDATE) { + expiryWarning.setBackgroundColor( + ContextCompat.getColor(this, R.color.briar_green_light)); + expiryWarningText.setText( + getString(R.string.expiry_update, daysUntilExpiry)); + expiryWarningText.setTextColor( + ContextCompat.getColor(this, android.R.color.black)); + expiryWarningClose.setColorFilter( + ContextCompat.getColor(this, android.R.color.black)); + } else { + expiryWarningText.setText(getResources() + .getQuantityString(R.plurals.expiry_warning, + daysUntilExpiry, daysUntilExpiry)); + } + + expiryWarningClose.setOnClickListener(v -> { + controller.expiryWarningDismissed(); + expiryWarning.setVisibility(GONE); + }); + + expiryWarning.setVisibility(VISIBLE); + } + + private void initializeTransports(LayoutInflater inflater) { + transports = new ArrayList<>(3); + + Transport tor = new Transport(); + tor.id = TorConstants.ID; + tor.enabled = controller.isTransportRunning(tor.id); + tor.iconId = R.drawable.transport_tor; + tor.textId = R.string.transport_tor; + transports.add(tor); + + Transport bt = new Transport(); + bt.id = BluetoothConstants.ID; + bt.enabled = controller.isTransportRunning(bt.id); + bt.iconId = R.drawable.transport_bt; + bt.textId = R.string.transport_bt; + transports.add(bt); + + Transport lan = new Transport(); + lan.id = LanTcpConstants.ID; + lan.enabled = controller.isTransportRunning(lan.id); + lan.iconId = R.drawable.transport_lan; + lan.textId = R.string.transport_lan; + transports.add(lan); + + transportsAdapter = new BaseAdapter() { + @Override + public int getCount() { + return transports.size(); + } + + @Override + public Transport getItem(int position) { + return transports.get(position); + } + + @Override + public long getItemId(int position) { + return 0; + } + + @Override + public View getView(int position, View convertView, + ViewGroup parent) { + View view; + if (convertView != null) { + view = convertView; + } else { + view = inflater.inflate(R.layout.list_item_transport, + parent, false); + } + + Transport t = getItem(position); + int c; + if (t.enabled) { + c = ContextCompat.getColor(NavDrawerActivity.this, + R.color.briar_green_light); + } else { + c = ContextCompat.getColor(NavDrawerActivity.this, + android.R.color.tertiary_text_light); + } + + ImageView icon = view.findViewById(R.id.imageView); + icon.setImageDrawable(ContextCompat + .getDrawable(NavDrawerActivity.this, t.iconId)); + icon.setColorFilter(c); + + TextView text = view.findViewById(R.id.textView); + text.setText(getString(t.textId)); + + return view; + } + }; + } + + private void setTransport(TransportId id, boolean enabled) { + runOnUiThreadUnlessDestroyed(() -> { + if (transports == null || transportsAdapter == null) return; + for (Transport t : transports) { + if (t.id.equals(id)) { + t.enabled = enabled; + transportsAdapter.notifyDataSetChanged(); + break; + } + } + }); + } + + private void updateTransports() { + if (transports == null || transportsAdapter == null) return; + for (Transport t : transports) { + t.enabled = controller.isTransportRunning(t.id); + } + transportsAdapter.notifyDataSetChanged(); + } + + @Override + public void stateUpdate(TransportId id, boolean enabled) { + setTransport(id, enabled); + } + + private static class Transport { + + private TransportId id; + private boolean enabled; + private int iconId; + private int textId; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerController.java b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerController.java new file mode 100644 index 0000000000000000000000000000000000000000..f18678c668595b8e316bb5a5471fb17c6fbbf3c6 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerController.java @@ -0,0 +1,24 @@ +package org.briarproject.mailbox.navdrawer; + +import android.content.Context; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.mailbox.controller.ActivityLifecycleController; +import org.briarproject.mailbox.controller.handler.ResultHandler; + +@NotNullByDefault +public interface NavDrawerController extends ActivityLifecycleController { + + enum ExpiryWarning {SHOW, NO, UPDATE} + + boolean isTransportRunning(TransportId transportId); + + void showExpiryWarning(ResultHandler<ExpiryWarning> handler); + + void expiryWarningDismissed(); + + void shouldAskForDozeWhitelisting(Context ctx, + ResultHandler<Boolean> handler); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerControllerImpl.java b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerControllerImpl.java new file mode 100644 index 0000000000000000000000000000000000000000..4b58c5fb9c0c7a16149097f7e682788fc47bfb96 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/NavDrawerControllerImpl.java @@ -0,0 +1,195 @@ +package org.briarproject.mailbox.navdrawer; + +import android.app.Activity; +import android.content.Context; + +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.Plugin; +import org.briarproject.bramble.api.plugin.PluginManager; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.event.TransportDisabledEvent; +import org.briarproject.bramble.api.plugin.event.TransportEnabledEvent; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.mailbox.controller.DbControllerImpl; +import org.briarproject.mailbox.controller.handler.ResultHandler; + +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.mailbox.TestingConstants.EXPIRY_DATE; +import static org.briarproject.mailbox.TestingConstants.IS_BETA_BUILD; +import static org.briarproject.mailbox.TestingConstants.IS_DEBUG_BUILD; +import static org.briarproject.mailbox.controller.MailboxControllerImpl.DOZE_ASK_AGAIN; +import static org.briarproject.mailbox.navdrawer.NavDrawerController.ExpiryWarning.NO; +import static org.briarproject.mailbox.navdrawer.NavDrawerController.ExpiryWarning.SHOW; +import static org.briarproject.mailbox.navdrawer.NavDrawerController.ExpiryWarning.UPDATE; +import static org.briarproject.mailbox.settings.SettingsFragment.SETTINGS_NAMESPACE; +import static org.briarproject.mailbox.util.UiUtils.needsDozeWhitelisting; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class NavDrawerControllerImpl extends DbControllerImpl + implements NavDrawerController, EventListener { + + private static final Logger LOG = + Logger.getLogger(NavDrawerControllerImpl.class.getName()); + private static final String EXPIRY_DATE_WARNING = "expiryDateWarning"; + private static final String EXPIRY_SHOW_UPDATE = "expiryShowUpdate"; + + private final PluginManager pluginManager; + private final SettingsManager settingsManager; + private final EventBus eventBus; + + private volatile TransportStateListener listener; + + @Inject + NavDrawerControllerImpl(@DatabaseExecutor Executor dbExecutor, + LifecycleManager lifecycleManager, PluginManager pluginManager, + SettingsManager settingsManager, EventBus eventBus) { + super(dbExecutor, lifecycleManager); + this.pluginManager = pluginManager; + this.settingsManager = settingsManager; + this.eventBus = eventBus; + } + + @Override + public void onActivityCreate(Activity activity) { + listener = (TransportStateListener) activity; + } + + @Override + public void onActivityStart() { + eventBus.addListener(this); + } + + @Override + public void onActivityStop() { + eventBus.removeListener(this); + } + + @Override + public void onActivityDestroy() { + + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof TransportEnabledEvent) { + TransportId id = ((TransportEnabledEvent) e).getTransportId(); + if (LOG.isLoggable(INFO)) { + LOG.info("TransportEnabledEvent: " + id.getString()); + } + transportStateUpdate(id, true); + } else if (e instanceof TransportDisabledEvent) { + TransportId id = ((TransportDisabledEvent) e).getTransportId(); + if (LOG.isLoggable(INFO)) { + LOG.info("TransportDisabledEvent: " + id.getString()); + } + transportStateUpdate(id, false); + } + } + + private void transportStateUpdate(TransportId id, boolean enabled) { + listener.runOnUiThreadUnlessDestroyed( + () -> listener.stateUpdate(id, enabled)); + } + + @Override + public void showExpiryWarning(ResultHandler<ExpiryWarning> handler) { + if (!IS_DEBUG_BUILD && !IS_BETA_BUILD) { + handler.onResult(NO); + return; + } + runOnDbThread(() -> { + try { + Settings settings = + settingsManager.getSettings(SETTINGS_NAMESPACE); + int warningInt = settings.getInt(EXPIRY_DATE_WARNING, 0); + boolean showUpdate = + settings.getBoolean(EXPIRY_SHOW_UPDATE, true); + + if (warningInt == 0) { + // we have not warned before + handler.onResult(SHOW); + } else { + long warningLong = warningInt * 1000L; + long now = System.currentTimeMillis(); + long daysSinceLastWarning = + (now - warningLong) / 1000 / 60 / 60 / 24; + long daysBeforeExpiry = + (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24; + + if (showUpdate) { + handler.onResult(UPDATE); + } else if (daysSinceLastWarning >= 30) { + handler.onResult(SHOW); + } else if (daysBeforeExpiry <= 3 && + daysSinceLastWarning > 0) { + handler.onResult(SHOW); + } else { + handler.onResult(NO); + } + } + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @Override + public void expiryWarningDismissed() { + runOnDbThread(() -> { + try { + Settings settings = new Settings(); + int date = (int) (System.currentTimeMillis() / 1000L); + settings.putInt(EXPIRY_DATE_WARNING, date); + settings.putBoolean(EXPIRY_SHOW_UPDATE, false); + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @Override + public void shouldAskForDozeWhitelisting(Context ctx, + ResultHandler<Boolean> handler) { + // check this first, to hit the DbThread only when really necessary + if (!needsDozeWhitelisting(ctx)) { + handler.onResult(false); + return; + } + runOnDbThread(() -> { + try { + Settings settings = + settingsManager.getSettings(SETTINGS_NAMESPACE); + boolean ask = settings.getBoolean(DOZE_ASK_AGAIN, true); + handler.onResult(ask); + } catch (DbException e) { + logException(LOG, WARNING, e); + handler.onResult(true); + } + }); + } + + @Override + public boolean isTransportRunning(TransportId transportId) { + Plugin plugin = pluginManager.getPlugin(transportId); + + return plugin != null && plugin.isRunning(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/TransportStateListener.java b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/TransportStateListener.java new file mode 100644 index 0000000000000000000000000000000000000000..b81504c6c598c3652d27193ec7b3e7519e51f247 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/navdrawer/TransportStateListener.java @@ -0,0 +1,9 @@ +package org.briarproject.mailbox.navdrawer; + +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.mailbox.DestroyableContext; + +interface TransportStateListener extends DestroyableContext { + + void stateUpdate(TransportId id, boolean enabled); +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/overview/OverviewFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/overview/OverviewFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..24209b1228576b9e8ae6fa8e90c62b786de69d75 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/overview/OverviewFragment.java @@ -0,0 +1,121 @@ +package org.briarproject.mailbox.overview; + +import android.arch.lifecycle.ViewModelProviders; +import android.content.Intent; +import android.os.Bundle; +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.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.fragment.BaseEventFragment; +import org.briarproject.mailbox.keyagreement.MailboxExchangeActivity; +import org.briarproject.mailbox.viewmodel.MailboxOwnerStatusViewModel; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; +import javax.annotation.ParametersAreNonnullByDefault; + +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static org.briarproject.mailbox.R.drawable.contact_offline; +import static org.briarproject.mailbox.R.drawable.contact_online; + +@MethodsNotNullByDefault +@ParametersAreNonnullByDefault +public class OverviewFragment extends BaseEventFragment { + + public static final String TAG = OverviewFragment.class.getName(); + private static final Logger LOG = Logger.getLogger(TAG); + + private ImageView ownerStatus; + private TextView ownerName; + + @Override + public String getUniqueTag() { + return TAG; + } + + @Override + public void injectFragment(ActivityComponent component) { + component.inject(this); + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + getActivity().setTitle(R.string.overview); + return inflater.inflate(R.layout.fragment_overview, container, false); + } + + @Override + public void onViewCreated(View view, + @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + ownerStatus = view.findViewById(R.id.ownerStatus); + ownerName = view.findViewById(R.id.ownerName); + final MailboxOwnerStatusViewModel + viewModel = ViewModelProviders.of(this).get(MailboxOwnerStatusViewModel.class); + viewModel.mailboxOwnerStatusLiveData.observe(this, + mailboxOwnerStatus -> { + if (mailboxOwnerStatus == null) { + ownerName.setText(R.string.mailbox_unpaired); + ownerStatus.setVisibility(INVISIBLE); + } else { + ownerName.setText(mailboxOwnerStatus.getName()); + ownerStatus.setVisibility(VISIBLE); + if (mailboxOwnerStatus.isOnline()) + ownerStatus.setImageResource(contact_online); + else + ownerStatus.setImageResource(contact_offline); + } + }); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.overview_actions, menu); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public void onStart() { + super.onStart(); + } + + @Override + public void eventOccurred(Event e) { + + } + + private void displayOwnerStatus() { + runOnUiThreadUnlessDestroyed(() -> { + + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case R.id.action_add_contact: + Intent intent = + new Intent(getContext(), MailboxExchangeActivity.class); + startActivity(intent); + return true; + default: + return super.onOptionsItemSelected(item); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/DevReportActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/DevReportActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..2c9dcc8515604580cd7d084f511a90b6104a3fc9 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/DevReportActivity.java @@ -0,0 +1,374 @@ +package org.briarproject.mailbox.reporting; + +import android.content.res.Configuration; +import android.os.AsyncTask; +import android.os.Bundle; +import android.support.annotation.Nullable; +import android.support.v7.app.AppCompatDelegate; +import android.support.v7.widget.Toolbar; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import org.acra.ReportField; +import org.acra.collector.CrashReportData; +import org.acra.dialog.BaseCrashReportDialog; +import org.acra.file.CrashReportPersister; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.util.UserFeedback; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Set; +import java.util.logging.Logger; + +import static android.os.Build.VERSION.SDK_INT; +import static android.view.View.GONE; +import static android.view.View.INVISIBLE; +import static android.view.View.VISIBLE; +import static android.view.inputmethod.InputMethodManager.SHOW_FORCED; +import static java.util.logging.Level.WARNING; +import static org.acra.ACRAConstants.EXTRA_REPORT_FILE; +import static org.acra.ReportField.ANDROID_VERSION; +import static org.acra.ReportField.APP_VERSION_CODE; +import static org.acra.ReportField.APP_VERSION_NAME; +import static org.acra.ReportField.PACKAGE_NAME; +import static org.acra.ReportField.REPORT_ID; +import static org.acra.ReportField.STACK_TRACE; + +public class DevReportActivity extends BaseCrashReportDialog + implements CompoundButton.OnCheckedChangeListener { + + private static final Logger LOG = + Logger.getLogger(DevReportActivity.class.getName()); + + private static final String STATE_REVIEWING = "reviewing"; + private static final Set<ReportField> requiredFields = new HashSet<>(); + + static { + requiredFields.add(REPORT_ID); + requiredFields.add(APP_VERSION_CODE); + requiredFields.add(APP_VERSION_NAME); + requiredFields.add(PACKAGE_NAME); + requiredFields.add(ANDROID_VERSION); + requiredFields.add(STACK_TRACE); + } + + private AppCompatDelegate delegate; + private Set<ReportField> excludedFields = new HashSet<>(); + private EditText userCommentView = null; + private EditText userEmailView = null; + private CheckBox includeDebugReport = null; + private Button chevron = null; + private LinearLayout report = null; + private View progress = null; + private MenuItem sendReport = null; + private boolean reviewing = false; + + private AppCompatDelegate getDelegate() { + if (delegate == null) { + delegate = AppCompatDelegate.create(this, null); + } + return delegate; + } + + @Override + protected void preInit(@Nullable Bundle savedInstanceState) { + super.preInit(savedInstanceState); + getDelegate().installViewFactory(); + getDelegate().onCreate(savedInstanceState); + if (getDelegate().applyDayNight()) { + // If DayNight has been applied, we need to re-apply the theme for + // the changes to take effect. On API 23+, we should bypass + // setTheme(), which will no-op if the theme ID is identical to the + // current theme ID. + int theme = R.style.BriarTheme_NoActionBar; + if (SDK_INT >= 23) { + onApplyThemeResource(getTheme(), theme, false); + } else { + setTheme(theme); + } + } + } + + @Override + public void init(Bundle state) { + super.init(state); + + getDelegate().setContentView(R.layout.activity_dev_report); + + Toolbar tb = findViewById(R.id.toolbar); + getDelegate().setSupportActionBar(tb); + + View requestReport = findViewById(R.id.request_report); + View reportForm = findViewById(R.id.report_form); + userCommentView = findViewById(R.id.user_comment); + userEmailView = findViewById(R.id.user_email); + includeDebugReport = findViewById(R.id.include_debug_report); + chevron = findViewById(R.id.chevron); + report = findViewById(R.id.report_content); + progress = findViewById(R.id.progress_wheel); + + //noinspection ConstantConditions + getDelegate().getSupportActionBar().setTitle( + isFeedback() ? R.string.feedback_title : + R.string.crash_report_title); + userCommentView.setHint(isFeedback() ? R.string.enter_feedback : + R.string.describe_crash); + + if (isFeedback()) { + includeDebugReport + .setText(getString(R.string.include_debug_report_feedback)); + reportForm.setVisibility(VISIBLE); + requestReport.setVisibility(INVISIBLE); + } else { + includeDebugReport.setChecked(true); + reportForm.setVisibility(INVISIBLE); + requestReport.setVisibility(VISIBLE); + } + + findViewById(R.id.acceptButton).setOnClickListener(v -> { + reviewing = true; + reportForm.setVisibility(VISIBLE); + requestReport.setVisibility(INVISIBLE); + ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) + .showSoftInput(userCommentView, SHOW_FORCED); + }); + findViewById(R.id.declineButton).setOnClickListener(v -> closeReport()); + chevron.setOnClickListener(v -> { + boolean show = + chevron.getText().equals(getString(R.string.show)); + if (show) { + chevron.setText(R.string.hide); + refresh(); + } else { + chevron.setText(R.string.show); + report.setVisibility(GONE); + } + }); + + if (state != null) + reviewing = state.getBoolean(STATE_REVIEWING, isFeedback()); + + if (!isFeedback() && !reviewing) + requestReport.setVisibility(VISIBLE); + } + + @Override + public void onPostCreate(Bundle state) { + super.onPostCreate(state); + getDelegate().onPostCreate(state); + } + + @Override + public void onStart() { + super.onStart(); + if (chevron.isSelected()) refresh(); + } + + @Override + protected void onPostResume() { + super.onPostResume(); + getDelegate().onPostResume(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // Inflate the menu items for use in the action bar + MenuInflater inflater = getDelegate().getMenuInflater(); + inflater.inflate(R.menu.dev_report_actions, menu); + sendReport = menu.findItem(R.id.action_send_report); + return super.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + // Handle presses on the action bar items + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + case R.id.action_send_report: + processReport(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + @Override + public void onTitleChanged(CharSequence title, int color) { + super.onTitleChanged(title, color); + getDelegate().setTitle(title); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getDelegate().onConfigurationChanged(newConfig); + } + + @Override + public void onSaveInstanceState(Bundle state) { + super.onSaveInstanceState(state); + state.putBoolean(STATE_REVIEWING, reviewing); + } + + @Override + public void onStop() { + super.onStop(); + getDelegate().onStop(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + getDelegate().onDestroy(); + } + + @Override + public void onBackPressed() { + closeReport(); + } + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + ReportField field = (ReportField) buttonView.getTag(); + if (field != null) { + if (isChecked) excludedFields.remove(field); + else excludedFields.add(field); + } + } + + @SuppressWarnings("ThrowableResultOfMethodCallIgnored") + private boolean isFeedback() { + return getException() instanceof UserFeedback; + } + + private void refresh() { + report.setVisibility(INVISIBLE); + progress.setVisibility(VISIBLE); + report.removeAllViews(); + new AsyncTask<Void, Void, CrashReportData>() { + + @Override + protected CrashReportData doInBackground(Void... args) { + File reportFile = (File) getIntent().getSerializableExtra( + EXTRA_REPORT_FILE); + CrashReportPersister persister = new CrashReportPersister(); + try { + return persister.load(reportFile); + } catch (IOException e) { + LOG.log(WARNING, "Could not load report file", e); + return null; + } + } + + @Override + protected void onPostExecute(CrashReportData crashData) { + LayoutInflater inflater = getLayoutInflater(); + if (crashData != null) { + for (Entry<ReportField, String> e : crashData.entrySet()) { + ReportField field = e.getKey(); + String value = e.getValue().replaceAll("\\\\n", "\n"); + boolean required = requiredFields.contains(field); + boolean excluded = excludedFields.contains(field); + View v = inflater.inflate(R.layout.list_item_crash, + report, false); + CheckBox cb = v.findViewById(R.id.include_in_report); + cb.setTag(field); + cb.setChecked(required || !excluded); + cb.setEnabled(!required); + cb.setOnCheckedChangeListener(DevReportActivity.this); + TextView title = v.findViewById(R.id.title); + title.setText(field.toString()); + TextView content = v.findViewById(R.id.content); + content.setText(value); + report.addView(v); + } + } else { + View v = inflater.inflate( + android.R.layout.simple_list_item_1, report, false); + TextView error = v.findViewById(android.R.id.text1); + error.setText(R.string.could_not_load_report_data); + report.addView(v); + } + report.setVisibility(VISIBLE); + progress.setVisibility(GONE); + } + }.execute(); + } + + private void processReport() { + userCommentView.setEnabled(false); + userEmailView.setEnabled(false); + sendReport.setEnabled(false); + progress.setVisibility(VISIBLE); + boolean includeReport = !isFeedback() || includeDebugReport.isChecked(); + new AsyncTask<Void, Void, Boolean>() { + + @Override + protected Boolean doInBackground(Void... args) { + File reportFile = (File) getIntent().getSerializableExtra( + EXTRA_REPORT_FILE); + CrashReportPersister persister = new CrashReportPersister(); + try { + CrashReportData data = persister.load(reportFile); + if (includeReport) { + for (ReportField field : excludedFields) { + LOG.info("Removing field " + field.name()); + data.remove(field); + } + } else { + Iterator<Entry<ReportField, String>> iter = + data.entrySet().iterator(); + while (iter.hasNext()) { + Entry<ReportField, String> e = iter.next(); + if (!requiredFields.contains(e.getKey())) { + iter.remove(); + } + } + } + persister.store(data, reportFile); + return true; + } catch (IOException e) { + LOG.log(WARNING, "Error processing report file", e); + return false; + } + } + + @Override + protected void onPostExecute(Boolean success) { + if (success) { + // Retrieve user's comment and email address, if any + String comment = ""; + if (userCommentView != null) + comment = userCommentView.getText().toString(); + String email = ""; + if (userEmailView != null) { + email = userEmailView.getText().toString(); + } + sendCrash(comment, email); + } + finish(); + } + }.execute(); + } + + private void closeReport() { + cancelReports(); + finish(); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportPrimer.java b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportPrimer.java new file mode 100644 index 0000000000000000000000000000000000000000..0c6de671de2ab2f8b7e331d1738c125f2ccde2a1 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportPrimer.java @@ -0,0 +1,281 @@ +package org.briarproject.mailbox.reporting; + +import android.app.ActivityManager; +import android.bluetooth.BluetoothAdapter; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Build; +import android.os.Environment; +import android.os.Handler; +import android.os.Looper; +import android.provider.Settings; +import android.support.annotation.NonNull; + +import org.acra.builder.ReportBuilder; +import org.acra.builder.ReportPrimer; +import org.briarproject.bramble.util.StringUtils; +import org.briarproject.mailbox.BuildConfig; +import org.briarproject.mailbox.MailboxApplication; +import org.briarproject.mailbox.logging.BriefLogFormatter; + +import java.io.File; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.FutureTask; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE_DISCOVERABLE; +import static android.content.Context.ACTIVITY_SERVICE; +import static android.content.Context.CONNECTIVITY_SERVICE; +import static android.content.Context.WIFI_P2P_SERVICE; +import static android.content.Context.WIFI_SERVICE; +import static android.net.ConnectivityManager.TYPE_MOBILE; +import static android.net.ConnectivityManager.TYPE_WIFI; +import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED; +import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; + +public class MailboxReportPrimer implements ReportPrimer { + + @Override + public void primeReport(@NonNull Context ctx, + @NonNull ReportBuilder builder) { + CustomDataTask task = new CustomDataTask(ctx); + FutureTask<Map<String, String>> futureTask = new FutureTask<>(task); + // Use a new thread as the Android executor thread may have died + new SingleShotAndroidExecutor(futureTask).start(); + try { + builder.customData(futureTask.get()); + } catch (InterruptedException | ExecutionException e) { + builder.customData("Custom data exception", e.toString()); + } + } + + private static class CustomDataTask + implements Callable<Map<String, String>> { + + private final Context ctx; + + private CustomDataTask(Context ctx) { + this.ctx = ctx; + } + + @Override + public Map<String, String> call() { + Map<String, String> customData = new LinkedHashMap<>(); + + // Log + MailboxApplication app = + (MailboxApplication) ctx.getApplicationContext(); + StringBuilder sb = new StringBuilder(); + Formatter formatter = new BriefLogFormatter(); + for (LogRecord record : app.getRecentLogRecords()) { + sb.append(formatter.format(record)).append('\n'); + } + customData.put("Log", sb.toString()); + + // System memory + Object o = ctx.getSystemService(ACTIVITY_SERVICE); + ActivityManager am = (ActivityManager) o; + ActivityManager.MemoryInfo mem = new ActivityManager.MemoryInfo(); + am.getMemoryInfo(mem); + String systemMemory; + if (Build.VERSION.SDK_INT >= 16) { + systemMemory = (mem.totalMem / 1024 / 1024) + " MiB total, " + + (mem.availMem / 1024 / 1204) + " MiB free, " + + (mem.threshold / 1024 / 1024) + " MiB threshold"; + } else { + systemMemory = (mem.availMem / 1024 / 1204) + " MiB free, " + + (mem.threshold / 1024 / 1024) + " MiB threshold"; + } + customData.put("System memory", systemMemory); + + // Virtual machine memory + Runtime runtime = Runtime.getRuntime(); + long heap = runtime.totalMemory(); + long heapFree = runtime.freeMemory(); + long heapMax = runtime.maxMemory(); + String vmMemory = (heap / 1024 / 1024) + " MiB allocated, " + + (heapFree / 1024 / 1024) + " MiB free, " + + (heapMax / 1024 / 1024) + " MiB maximum"; + customData.put("Virtual machine memory", vmMemory); + + // Internal storage + File root = Environment.getRootDirectory(); + long rootTotal = root.getTotalSpace(); + long rootFree = root.getFreeSpace(); + String internal = (rootTotal / 1024 / 1024) + " MiB total, " + + (rootFree / 1024 / 1024) + " MiB free"; + customData.put("Internal storage", internal); + + // External storage (SD card) + File sd = Environment.getExternalStorageDirectory(); + long sdTotal = sd.getTotalSpace(); + long sdFree = sd.getFreeSpace(); + String external = (sdTotal / 1024 / 1024) + " MiB total, " + + (sdFree / 1024 / 1024) + " MiB free"; + customData.put("External storage", external); + + // Is mobile data available? + o = ctx.getSystemService(CONNECTIVITY_SERVICE); + ConnectivityManager cm = (ConnectivityManager) o; + NetworkInfo mobile = cm.getNetworkInfo(TYPE_MOBILE); + boolean mobileAvailable = mobile != null && mobile.isAvailable(); + // Is mobile data enabled? + boolean mobileEnabled = false; + try { + Class<?> clazz = Class.forName(cm.getClass().getName()); + Method method = clazz.getDeclaredMethod("getMobileDataEnabled"); + method.setAccessible(true); + mobileEnabled = (Boolean) method.invoke(cm); + } catch (ClassNotFoundException + | NoSuchMethodException + | IllegalArgumentException + | InvocationTargetException + | IllegalAccessException e) { + customData.put("Mobile data reflection exception", + e.toString()); + } + // Is mobile data connected ? + boolean mobileConnected = mobile != null && mobile.isConnected(); + + String mobileStatus; + if (mobileAvailable) mobileStatus = "Available, "; + else mobileStatus = "Not available, "; + if (mobileEnabled) mobileStatus += "enabled, "; + else mobileStatus += "not enabled, "; + if (mobileConnected) mobileStatus += "connected"; + else mobileStatus += "not connected"; + customData.put("Mobile data status", mobileStatus); + + // Is wifi available? + NetworkInfo wifi = cm.getNetworkInfo(TYPE_WIFI); + boolean wifiAvailable = wifi != null && wifi.isAvailable(); + // Is wifi enabled? + o = ctx.getApplicationContext().getSystemService(WIFI_SERVICE); + WifiManager wm = (WifiManager) o; + boolean wifiEnabled = wm != null && + wm.getWifiState() == WIFI_STATE_ENABLED; + // Is wifi connected? + boolean wifiConnected = wifi != null && wifi.isConnected(); + + String wifiStatus; + if (wifiAvailable) wifiStatus = "Available, "; + else wifiStatus = "Not available, "; + if (wifiEnabled) wifiStatus += "enabled, "; + else wifiStatus += "not enabled, "; + if (wifiConnected) wifiStatus += "connected"; + else wifiStatus += "not connected"; + customData.put("Wi-Fi status", wifiStatus); + + // Is wifi direct supported? + String wifiDirectStatus = "Supported"; + if (ctx.getSystemService(WIFI_P2P_SERVICE) == null) + wifiDirectStatus = "Not supported"; + customData.put("Wi-Fi Direct", wifiDirectStatus); + + if (wm != null) { + WifiInfo wifiInfo = wm.getConnectionInfo(); + if (wifiInfo != null) { + int ip = wifiInfo.getIpAddress(); // Nice API, Google + int ip1 = ip & 0xFF; + int ip2 = (ip >> 8) & 0xFF; + int ip3 = (ip >> 16) & 0xFF; + int ip4 = (ip >> 24) & 0xFF; + String address = ip1 + "." + ip2 + "." + ip3 + "." + ip4; + customData.put("Wi-Fi address", address); + } + } + + // Is Bluetooth available? + BluetoothAdapter bt = BluetoothAdapter.getDefaultAdapter(); + boolean btAvailable = bt != null; + // Is Bluetooth enabled? + boolean btEnabled = bt != null && bt.isEnabled() && + !StringUtils.isNullOrEmpty(bt.getAddress()); + // Is Bluetooth connectable? + boolean btConnectable = bt != null && + (bt.getScanMode() == SCAN_MODE_CONNECTABLE || + bt.getScanMode() == + SCAN_MODE_CONNECTABLE_DISCOVERABLE); + // Is Bluetooth discoverable? + boolean btDiscoverable = bt != null && + bt.getScanMode() == SCAN_MODE_CONNECTABLE_DISCOVERABLE; + // Is Bluetooth LE scanning and advertising supported? + boolean btLeApi = false, btLeScan = false, btLeAdvertise = false; + if (bt != null && Build.VERSION.SDK_INT >= 21) { + btLeApi = true; + btLeScan = bt.getBluetoothLeScanner() != null; + btLeAdvertise = bt.getBluetoothLeAdvertiser() != null; + } + + String btStatus; + if (btAvailable) btStatus = "Available, "; + else btStatus = "Not available, "; + if (btEnabled) btStatus += "enabled, "; + else btStatus += "not enabled, "; + if (btConnectable) btStatus += "connectable, "; + else btStatus += "not connectable, "; + if (btDiscoverable) btStatus += "discoverable"; + else btStatus += "not discoverable"; + customData.put("Bluetooth status", btStatus); + if (btLeApi) { + String btLeStatus; + if (btLeScan) btLeStatus = "Scanning, "; + else btLeStatus = "No scanning, "; + if (btLeAdvertise) btLeStatus += "advertising"; + else btLeStatus += "no advertising"; + customData.put("Bluetooth LE status", btLeStatus); + } + + if (bt != null) { + customData.put("Bluetooth address", + scrubMacAddress(bt.getAddress())); + } + String btSettingsAddr; + try { + btSettingsAddr = Settings.Secure.getString( + ctx.getContentResolver(), "bluetooth_address"); + } catch (SecurityException e) { + btSettingsAddr = "Could not get address from settings"; + } + customData.put("Bluetooth address from settings", + scrubMacAddress(btSettingsAddr)); + + // Git commit ID + customData.put("Commit ID", BuildConfig.GitHash); + + return Collections.unmodifiableMap(customData); + } + } + + private static class SingleShotAndroidExecutor extends Thread { + + private final Runnable runnable; + + private SingleShotAndroidExecutor(Runnable runnable) { + this.runnable = runnable; + } + + @Override + public void run() { + Looper.prepare(); + Handler handler = new Handler(); + handler.post(runnable); + handler.post(() -> { + Looper looper = Looper.myLooper(); + if (looper != null) looper.quit(); + }); + Looper.loop(); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportSender.java b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportSender.java new file mode 100644 index 0000000000000000000000000000000000000000..dbdcddb93a392dc055f1f96fefad9d189502c157 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportSender.java @@ -0,0 +1,51 @@ +package org.briarproject.mailbox.reporting; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.acra.collector.CrashReportData; +import org.acra.sender.ReportSender; +import org.acra.sender.ReportSenderException; +import org.acra.util.JSONReportBuilder.JSONReportException; +import org.briarproject.bramble.api.reporting.DevReporter; +import org.briarproject.bramble.util.AndroidUtils; +import org.briarproject.mailbox.AndroidComponent; + +import java.io.File; +import java.io.FileNotFoundException; + +import javax.inject.Inject; + +import static org.acra.ReportField.REPORT_ID; + +public class MailboxReportSender implements ReportSender { + + private final AndroidComponent component; + + @Inject + DevReporter reporter; + + MailboxReportSender(AndroidComponent component) { + this.component = component; + } + + @Override + public void send(@NonNull Context ctx, + @NonNull CrashReportData errorContent) + throws ReportSenderException { + component.inject(this); + String crashReport; + try { + crashReport = errorContent.toJSON().toString(); + } catch (JSONReportException e) { + throw new ReportSenderException("Couldn't create JSON", e); + } + try { + File reportDir = AndroidUtils.getReportDir(ctx); + String reportId = errorContent.getProperty(REPORT_ID); + reporter.encryptReportToFile(reportDir, reportId, crashReport); + } catch (FileNotFoundException e) { + throw new ReportSenderException("Failed to encrypt report", e); + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportSenderFactory.java b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportSenderFactory.java new file mode 100644 index 0000000000000000000000000000000000000000..5413610a0257c54ac3251a88f60aae4a39fddb33 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/reporting/MailboxReportSenderFactory.java @@ -0,0 +1,21 @@ +package org.briarproject.mailbox.reporting; + +import android.content.Context; +import android.support.annotation.NonNull; + +import org.acra.config.ACRAConfiguration; +import org.acra.sender.ReportSender; +import org.acra.sender.ReportSenderFactory; +import org.briarproject.mailbox.MailboxApplication; + +public class MailboxReportSenderFactory implements ReportSenderFactory { + + @NonNull + @Override + public ReportSender create(@NonNull Context ctx, + @NonNull ACRAConfiguration config) { + // ACRA passes in the Application as context + MailboxApplication app = (MailboxApplication) ctx; + return new MailboxReportSender(app.getApplicationComponent()); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/settings/SettingsActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/settings/SettingsActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..72ea48ace35f4e02537aa9a2d12d9d46b4d4b55f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/settings/SettingsActivity.java @@ -0,0 +1,40 @@ +package org.briarproject.mailbox.settings; + +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.view.MenuItem; + +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.MailboxActivity; + + +public class SettingsActivity extends MailboxActivity { + + @Override + public void onCreate(Bundle bundle) { + super.onCreate(bundle); + + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setHomeButtonEnabled(true); + actionBar.setDisplayHomeAsUpEnabled(true); + } + + setContentView(R.layout.activity_settings); + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + onBackPressed(); + return true; + } + return false; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/settings/SettingsFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/settings/SettingsFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..fda0eb23637399d1318d43d717efb42018d37dab --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/settings/SettingsFragment.java @@ -0,0 +1,359 @@ +package org.briarproject.mailbox.settings; + +import android.annotation.TargetApi; +import android.app.AlertDialog; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.support.annotation.StringRes; +import android.support.v4.text.TextUtilsCompat; +import android.support.v7.preference.CheckBoxPreference; +import android.support.v7.preference.ListPreference; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceFragmentCompat; +import android.support.v7.preference.PreferenceGroup; + +import org.acra.ACRA; +import org.briarproject.bramble.api.db.DbException; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.bramble.api.plugin.BluetoothConstants; +import org.briarproject.bramble.api.plugin.TorConstants; +import org.briarproject.bramble.api.settings.Settings; +import org.briarproject.bramble.api.settings.SettingsManager; +import org.briarproject.bramble.api.settings.event.SettingsUpdatedEvent; +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.mailbox.Localizer; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.navdrawer.NavDrawerActivity; +import org.briarproject.mailbox.util.UiUtils; +import org.briarproject.mailbox.util.UserFeedback; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.Build.VERSION.SDK_INT; +import static android.provider.Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS; +import static android.provider.Settings.EXTRA_APP_PACKAGE; +import static android.provider.Settings.EXTRA_CHANNEL_ID; +import static android.support.v4.view.ViewCompat.LAYOUT_DIRECTION_LTR; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.PREF_BT_ENABLE; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK; +import static org.briarproject.bramble.api.plugin.TorConstants.PREF_TOR_NETWORK_ALWAYS; +import static org.briarproject.bramble.util.LogUtils.logDuration; +import static org.briarproject.bramble.util.LogUtils.logException; +import static org.briarproject.bramble.util.LogUtils.now; +import static org.briarproject.mailbox.TestingConstants.FEATURE_FLAG_DARK_THEME; +import static org.briarproject.mailbox.TestingConstants.IS_DEBUG_BUILD; +import static org.briarproject.mailbox.navdrawer.NavDrawerActivity.INTENT_SIGN_OUT; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class SettingsFragment extends PreferenceFragmentCompat + implements EventListener, Preference.OnPreferenceChangeListener { + + public static final String SETTINGS_NAMESPACE = "android-ui"; + public static final String BT_NAMESPACE = BluetoothConstants.ID.getString(); + public static final String TOR_NAMESPACE = TorConstants.ID.getString(); + public static final String LANGUAGE = "pref_key_language"; + + private static final Logger LOG = + Logger.getLogger(SettingsFragment.class.getName()); + + private SettingsActivity listener; + private ListPreference language; + private ListPreference enableBluetooth; + private ListPreference torNetwork; + + + // Fields that are accessed from background threads must be volatile + volatile Settings settings; + @Inject + volatile SettingsManager settingsManager; + @Inject + volatile EventBus eventBus; + + @Inject + AndroidExecutor androidExecutor; + + @Override + public void onAttach(Context context) { + super.onAttach(context); + listener = (SettingsActivity) context; + // we need to inject here, + // because onActivityCreated() is called after onCreatePreferences() + listener.getActivityComponent().inject(this); + } + + @Override + public void onCreatePreferences(Bundle bundle, String s) { + addPreferencesFromResource(R.xml.settings); + + language = (ListPreference) findPreference(LANGUAGE); + setLanguageEntries(); + ListPreference theme = + (ListPreference) findPreference("pref_key_theme"); + enableBluetooth = (ListPreference) findPreference("pref_key_bluetooth"); + torNetwork = (ListPreference) findPreference("pref_key_tor_network"); + + setSettingsEnabled(false); + + language.setOnPreferenceChangeListener(this); + theme.setOnPreferenceChangeListener((preference, newValue) -> { + if (getActivity() != null) { + // activate new theme + UiUtils.setTheme(getActivity(), (String) newValue); + // bring up parent activity, so it can change its theme as well + // upstream bug: https://issuetracker.google.com/issues/38352704 + Intent intent = + new Intent(getActivity(), NavDrawerActivity.class); + intent.setFlags( + FLAG_ACTIVITY_CLEAR_TASK | FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + // bring this activity back to the foreground + intent = new Intent(getActivity(), getActivity().getClass()); + startActivity(intent); + getActivity().finish(); + } + return true; + }); + enableBluetooth.setOnPreferenceChangeListener(this); + torNetwork.setOnPreferenceChangeListener(this); + + findPreference("pref_key_send_feedback").setOnPreferenceClickListener( + preference -> { + triggerFeedback(); + return true; + }); + + if (IS_DEBUG_BUILD) { + findPreference("pref_key_explode").setOnPreferenceClickListener( + preference -> { + throw new RuntimeException("Boom!"); + } + ); + } else { + theme.setVisible(FEATURE_FLAG_DARK_THEME); + + findPreference("pref_key_explode").setVisible(false); + findPreference("pref_key_test_data").setVisible(false); + PreferenceGroup testing = + findPreference("pref_key_explode").getParent(); + if (testing == null) throw new AssertionError(); + testing.setVisible(false); + } + + loadSettings(); + } + + @Override + public void onStart() { + super.onStart(); + eventBus.addListener(this); + } + + @Override + public void onStop() { + super.onStop(); + eventBus.removeListener(this); + } + + private void setLanguageEntries() { + CharSequence[] tags = language.getEntryValues(); + List<CharSequence> entries = new ArrayList<>(tags.length); + List<CharSequence> entryValues = new ArrayList<>(tags.length); + for (CharSequence cs : tags) { + String tag = cs.toString(); + if (tag.equals("default")) { + entries.add(getString(R.string.pref_language_default)); + entryValues.add(tag); + continue; + } + Locale locale = Localizer.getLocaleFromTag(tag); + if (locale == null) + throw new IllegalStateException(); + // Exclude RTL locales on API < 17, they won't be laid out correctly + if (SDK_INT < 17 && !isLeftToRight(locale)) { + if (LOG.isLoggable(INFO)) + LOG.info("Skipping RTL locale " + tag); + continue; + } + String nativeName = locale.getDisplayName(locale); + // Fallback to English if the name is unknown in both native and + // current locale. + if (nativeName.equals(tag)) { + String tmp = locale.getDisplayLanguage(Locale.ENGLISH); + if (!tmp.isEmpty() && !tmp.equals(nativeName)) + nativeName = tmp; + } + // Prefix with LRM marker to prevent any RTL direction + entries.add("\u200E" + nativeName.substring(0, 1).toUpperCase() + + nativeName.substring(1)); + entryValues.add(tag); + } + language.setEntries(entries.toArray(new CharSequence[0])); + language.setEntryValues(entryValues.toArray(new CharSequence[0])); + } + + private boolean isLeftToRight(Locale locale) { + // TextUtilsCompat returns the wrong direction for Hebrew on some phones + String language = locale.getLanguage(); + if (language.equals("iw") || language.equals("he")) return false; + int direction = TextUtilsCompat.getLayoutDirectionFromLocale(locale); + return direction == LAYOUT_DIRECTION_LTR; + } + + private void loadSettings() { + listener.runOnDbThread(() -> { + try { + long start = now(); + settings = settingsManager.getSettings(SETTINGS_NAMESPACE); + Settings btSettings = settingsManager.getSettings(BT_NAMESPACE); + Settings torSettings = + settingsManager.getSettings(TOR_NAMESPACE); + logDuration(LOG, "Loading settings", start); + boolean btSetting = + btSettings.getBoolean(PREF_BT_ENABLE, false); + int torSetting = torSettings.getInt(PREF_TOR_NETWORK, + PREF_TOR_NETWORK_ALWAYS); + displaySettings(btSetting, torSetting); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void displaySettings(boolean btSetting, int torSetting) { + listener.runOnUiThreadUnlessDestroyed(() -> { + enableBluetooth.setValue(Boolean.toString(btSetting)); + torNetwork.setValue(Integer.toString(torSetting)); + setSettingsEnabled(true); + }); + } + + private void setSettingsEnabled(boolean enabled) { + // theme not needed here, because handled by SharedPreferences + enableBluetooth.setEnabled(enabled); + torNetwork.setEnabled(enabled); + } + + @TargetApi(26) + private void setupNotificationPreference(CheckBoxPreference pref, + String channelId, @StringRes int summary) { + pref.setWidgetLayoutResource(0); + pref.setSummary(summary); + pref.setOnPreferenceClickListener(clickedPref -> { + Intent intent = new Intent(ACTION_CHANNEL_NOTIFICATION_SETTINGS) + .putExtra(EXTRA_APP_PACKAGE, getContext().getPackageName()) + .putExtra(EXTRA_CHANNEL_ID, channelId); + startActivity(intent); + return true; + }); + } + + private void triggerFeedback() { + androidExecutor.runOnBackgroundThread(() -> ACRA.getErrorReporter() + .handleException(new UserFeedback(), false)); + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (preference == language) { + if (!language.getValue().equals(newValue)) + languageChanged((String) newValue); + return false; + } else if (preference == enableBluetooth) { + boolean btSetting = Boolean.valueOf((String) newValue); + storeBluetoothSettings(btSetting); + } else if (preference == torNetwork) { + int torSetting = Integer.valueOf((String) newValue); + storeTorSettings(torSetting); + } + return true; + } + + private void languageChanged(String newValue) { + AlertDialog.Builder builder = + new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.pref_language_title); + builder.setMessage(R.string.pref_language_changed); + builder.setPositiveButton(R.string.sign_out_button, + (dialogInterface, i) -> { + language.setValue(newValue); + Intent intent = new Intent(getContext(), + NavDrawerActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + intent.putExtra(INTENT_SIGN_OUT, true); + getActivity().startActivity(intent); + getActivity().finish(); + }); + builder.setNegativeButton(R.string.cancel, null); + builder.setCancelable(false); + builder.show(); + } + + private void storeTorSettings(int torSetting) { + listener.runOnDbThread(() -> { + try { + Settings s = new Settings(); + s.putInt(PREF_TOR_NETWORK, torSetting); + long start = now(); + settingsManager.mergeSettings(s, TOR_NAMESPACE); + logDuration(LOG, "Merging settings", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void storeBluetoothSettings(boolean btSetting) { + listener.runOnDbThread(() -> { + try { + Settings s = new Settings(); + s.putBoolean(PREF_BT_ENABLE, btSetting); + long start = now(); + settingsManager.mergeSettings(s, BT_NAMESPACE); + logDuration(LOG, "Merging settings", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + private void storeSettings(Settings settings) { + listener.runOnDbThread(() -> { + try { + long start = now(); + settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE); + logDuration(LOG, "Merging settings", start); + } catch (DbException e) { + logException(LOG, WARNING, e); + } + }); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof SettingsUpdatedEvent) { + String namespace = ((SettingsUpdatedEvent) e).getNamespace(); + if (namespace.equals(BT_NAMESPACE) + || namespace.equals(TOR_NAMESPACE) + || namespace.equals(SETTINGS_NAMESPACE)) { + LOG.info("Settings updated"); + loadSettings(); + } + } + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/splash/ExpiredActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/splash/ExpiredActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..fabbe016ebd3bd203feccba63f658993f7bf545c --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/splash/ExpiredActivity.java @@ -0,0 +1,35 @@ +package org.briarproject.mailbox.splash; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; +import android.view.View; +import android.view.View.OnClickListener; + +import org.briarproject.mailbox.R; + +import static android.content.Intent.ACTION_VIEW; +import static android.view.WindowManager.LayoutParams.FLAG_SECURE; +import static org.briarproject.mailbox.TestingConstants.PREVENT_SCREENSHOTS; + +public class ExpiredActivity extends AppCompatActivity + implements OnClickListener { + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + if (PREVENT_SCREENSHOTS) getWindow().addFlags(FLAG_SECURE); + + setContentView(R.layout.activity_expired); + findViewById(R.id.download_briar_button).setOnClickListener(this); + } + + @Override + public void onClick(View v) { + Uri uri = Uri.parse("https://briarproject.org/download.html"); + startActivity(new Intent(ACTION_VIEW, uri)); + finish(); + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/splash/SplashScreenActivity.java b/mailbox-android/src/main/java/org/briarproject/mailbox/splash/SplashScreenActivity.java new file mode 100644 index 0000000000000000000000000000000000000000..9e5610698d41649e1b05de87875fedb53150606b --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/splash/SplashScreenActivity.java @@ -0,0 +1,75 @@ +package org.briarproject.mailbox.splash; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.transition.Fade; + +import org.briarproject.bramble.api.system.AndroidExecutor; +import org.briarproject.mailbox.R; +import org.briarproject.mailbox.activity.ActivityComponent; +import org.briarproject.mailbox.activity.BaseActivity; +import org.briarproject.mailbox.controller.ConfigController; +import org.briarproject.mailbox.login.OpenDatabaseActivity; +import org.briarproject.mailbox.login.SetupActivity; + +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static org.briarproject.mailbox.TestingConstants.EXPIRY_DATE; + + +public class SplashScreenActivity extends BaseActivity { + + private static final Logger LOG = + Logger.getLogger(SplashScreenActivity.class.getName()); + + @Inject + protected ConfigController configController; + @Inject + protected AndroidExecutor androidExecutor; + + @Override + public void onCreate(Bundle state) { + super.onCreate(state); + + if (Build.VERSION.SDK_INT >= 21) { + getWindow().setExitTransition(new Fade()); + } + + setContentView(R.layout.splash); + + if (configController.accountSignedIn()) { + startActivity(new Intent(this, OpenDatabaseActivity.class)); + finish(); + } else { + new Handler().postDelayed(() -> { + startNextActivity(); + supportFinishAfterTransition(); + }, 100); + } + } + + @Override + public void injectActivity(ActivityComponent component) { + component.inject(this); + } + + protected void startNextActivity() { + if (System.currentTimeMillis() >= EXPIRY_DATE) { + LOG.info("Expired"); + startActivity(new Intent(this, ExpiredActivity.class)); + } else { + if (configController.accountExists()) { + LOG.info("Account exists"); + startActivity(new Intent(this, OpenDatabaseActivity.class)); + } else { + LOG.info("Account does not exist"); + configController.deleteAccount(this); + startActivity(new Intent(this, SetupActivity.class)); + } + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/util/BriarAdapter.java b/mailbox-android/src/main/java/org/briarproject/mailbox/util/BriarAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..e1bdad3da9bf1c50cca8752452dbd798212cf8a6 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/util/BriarAdapter.java @@ -0,0 +1,127 @@ +package org.briarproject.mailbox.util; + +import android.content.Context; +import android.support.annotation.UiThread; +import android.support.v7.util.SortedList; +import android.support.v7.widget.RecyclerView.Adapter; +import android.support.v7.widget.RecyclerView.ViewHolder; + +import java.util.Collection; + +import javax.annotation.Nullable; + +import static android.support.v7.util.SortedList.INVALID_POSITION; + +public abstract class BriarAdapter<T, V extends ViewHolder> + extends Adapter<V> implements VersionedAdapter { + + protected final Context ctx; + protected final SortedList<T> items; + + private volatile int revision = 0; + + public BriarAdapter(Context ctx, Class<T> c) { + this.ctx = ctx; + this.items = new SortedList<>(c, new SortedList.Callback<T>() { + @Override + public int compare(T item1, T item2) { + return BriarAdapter.this.compare(item1, item2); + } + + @Override + public void onInserted(int position, int count) { + notifyItemRangeInserted(position, count); + } + + @Override + public void onRemoved(int position, int count) { + notifyItemRangeRemoved(position, count); + } + + @Override + public void onMoved(int fromPosition, int toPosition) { + notifyItemMoved(fromPosition, toPosition); + } + + @Override + public void onChanged(int position, int count) { + notifyItemRangeChanged(position, count); + } + + @Override + public boolean areContentsTheSame(T item1, T item2) { + return BriarAdapter.this.areContentsTheSame(item1, item2); + } + + @Override + public boolean areItemsTheSame(T item1, T item2) { + return BriarAdapter.this.areItemsTheSame(item1, item2); + } + }); + } + + public abstract int compare(T item1, T item2); + + public abstract boolean areContentsTheSame(T item1, T item2); + + public abstract boolean areItemsTheSame(T item1, T item2); + + @Override + public int getItemCount() { + return items.size(); + } + + public void add(T item) { + items.add(item); + } + + public void addAll(Collection<T> items) { + this.items.addAll(items); + } + + public void setItems(Collection<T> items) { + this.items.beginBatchedUpdates(); + this.items.clear(); + this.items.addAll(items); + this.items.endBatchedUpdates(); + } + + @Nullable + public T getItemAt(int position) { + if (position == INVALID_POSITION || position >= items.size()) { + return null; + } + return items.get(position); + } + + public int findItemPosition(T item) { + return items.indexOf(item); + } + + public void updateItemAt(int position, T item) { + items.updateItemAt(position, item); + } + + public void remove(T item) { + items.remove(item); + } + + public void clear() { + items.clear(); + } + + public boolean isEmpty() { + return items.size() == 0; + } + + @Override + public int getRevision() { + return revision; + } + + @UiThread + @Override + public void incrementRevision() { + revision++; + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/util/UiUtils.java b/mailbox-android/src/main/java/org/briarproject/mailbox/util/UiUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..cb3b752819708b079ddc0c05655946b045462bdb --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/util/UiUtils.java @@ -0,0 +1,203 @@ +package org.briarproject.mailbox.util; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.net.Uri; +import android.os.PowerManager; +import android.support.annotation.AttrRes; +import android.support.annotation.ColorInt; +import android.support.annotation.ColorRes; +import android.support.design.widget.TextInputLayout; +import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; +import android.text.Html; +import android.text.Spannable; +import android.text.SpannableString; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.format.DateUtils; +import android.text.style.ForegroundColorSpan; +import android.util.TypedValue; +import android.view.View; + +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; +import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; +import org.briarproject.mailbox.R; + +import javax.annotation.Nullable; + +import static android.content.Context.POWER_SERVICE; +import static android.content.Intent.CATEGORY_DEFAULT; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.os.Build.MANUFACTURER; +import static android.os.Build.VERSION.SDK_INT; +import static android.provider.Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS; +import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_AUTO; +import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM; +import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_NO; +import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES; +import static android.support.v7.app.AppCompatDelegate.setDefaultNightMode; +import static android.text.format.DateUtils.DAY_IN_MILLIS; +import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; +import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE; +import static android.text.format.DateUtils.FORMAT_ABBREV_TIME; +import static android.text.format.DateUtils.FORMAT_SHOW_DATE; +import static android.text.format.DateUtils.MINUTE_IN_MILLIS; +import static android.text.format.DateUtils.WEEK_IN_MILLIS; +import static org.briarproject.mailbox.BuildConfig.APPLICATION_ID; +import static org.briarproject.mailbox.TestingConstants.EXPIRY_DATE; + +@MethodsNotNullByDefault +@ParametersNotNullByDefault +public class UiUtils { + + public static final long MIN_DATE_RESOLUTION = MINUTE_IN_MILLIS; + public static final int TEASER_LENGTH = 320; + public static final float GREY_OUT = 0.5f; + + public static void setError(TextInputLayout til, @Nullable String error, + boolean set) { + if (set) { + if (til.getError() == null) til.setError(error); + } else { + til.setError(null); + } + } + + public static String formatDate(Context ctx, long time) { + int flags = FORMAT_ABBREV_RELATIVE | + FORMAT_SHOW_DATE | FORMAT_ABBREV_TIME | FORMAT_ABBREV_MONTH; + + long diff = System.currentTimeMillis() - time; + if (diff < MIN_DATE_RESOLUTION) return ctx.getString(R.string.now); + if (diff >= DAY_IN_MILLIS && diff < WEEK_IN_MILLIS) { + // also show time when older than a day, but newer than a week + return DateUtils.getRelativeDateTimeString(ctx, time, + MIN_DATE_RESOLUTION, WEEK_IN_MILLIS, flags).toString(); + } + // otherwise just show "...ago" or date string + return DateUtils.getRelativeTimeSpanString(time, + System.currentTimeMillis(), + MIN_DATE_RESOLUTION, flags).toString(); + } + + public static int getDaysUntilExpiry() { + long now = System.currentTimeMillis(); + long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24; + return (int) daysBeforeExpiry; + } + + public static SpannableStringBuilder getTeaser(Context ctx, Spanned body) { + if (body.length() < TEASER_LENGTH) + throw new IllegalArgumentException( + "String is shorter than TEASER_LENGTH"); + + SpannableStringBuilder builder = + new SpannableStringBuilder(body.subSequence(0, TEASER_LENGTH)); + String ellipsis = ctx.getString(R.string.ellipsis); + builder.append(ellipsis).append(" "); + + Spannable readMore = new SpannableString( + ctx.getString(R.string.read_more) + ellipsis); + ForegroundColorSpan fg = new ForegroundColorSpan( + ContextCompat.getColor(ctx, R.color.briar_text_link)); + readMore.setSpan(fg, 0, readMore.length(), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + builder.append(readMore); + + return builder; + } + + public static Spanned getSpanned(String s) { + return Html.fromHtml(s); + } + + public static String getAvatarTransitionName(ContactId c) { + return "avatar" + c.getInt(); + } + + public static String getBulbTransitionName(ContactId c) { + return "bulb" + c.getInt(); + } + + public static OnClickListener getGoToSettingsListener(Context context) { + return (dialog, which) -> { + Intent i = new Intent(); + i.setAction("android.settings.APPLICATION_DETAILS_SETTINGS"); + i.addCategory(CATEGORY_DEFAULT); + i.setData(Uri.parse("package:" + APPLICATION_ID)); + i.addFlags(FLAG_ACTIVITY_NEW_TASK); + context.startActivity(i); + }; + } + + public static void showOnboardingDialog(Context ctx, String text) { + new AlertDialog.Builder(ctx, R.style.OnboardingDialogTheme) + .setMessage(text) + .setNeutralButton(R.string.got_it, + (dialog, which) -> dialog.cancel()) + .show(); + } + + public static boolean needsDozeWhitelisting(Context ctx) { + if (SDK_INT < 23) return false; + PowerManager pm = (PowerManager) ctx.getSystemService(POWER_SERVICE); + String packageName = ctx.getPackageName(); + if (pm == null) throw new AssertionError(); + return !pm.isIgnoringBatteryOptimizations(packageName); + } + + @TargetApi(23) + @SuppressLint("BatteryLife") + public static Intent getDozeWhitelistingIntent(Context ctx) { + Intent i = new Intent(); + i.setAction(ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + i.setData(Uri.parse("package:" + ctx.getPackageName())); + return i; + } + + public static boolean isSamsung7() { + return SDK_INT == 24 && MANUFACTURER.equalsIgnoreCase("Samsung"); + } + + public static void setFilterTouchesWhenObscured(View v, boolean filter) { + v.setFilterTouchesWhenObscured(filter); + // Workaround for Android bug #13530806, see + // https://android.googlesource.com/platform/frameworks/base/+/aba566589e0011c4b973c0d4f77be4e9ee176089%5E%21/core/java/android/view/View.java + if (v.getFilterTouchesWhenObscured() != filter) + v.setFilterTouchesWhenObscured(!filter); + } + + public static void setTheme(Context ctx, String theme) { + if (theme.equals(ctx.getString(R.string.pref_theme_light_value))) { + setDefaultNightMode(MODE_NIGHT_NO); + } else if (theme + .equals(ctx.getString(R.string.pref_theme_dark_value))) { + setDefaultNightMode(MODE_NIGHT_YES); + } else if (theme + .equals(ctx.getString(R.string.pref_theme_auto_value))) { + setDefaultNightMode(MODE_NIGHT_AUTO); + } else if (theme + .equals(ctx.getString(R.string.pref_theme_system_value))) { + setDefaultNightMode(MODE_NIGHT_FOLLOW_SYSTEM); + } + } + + public static int resolveAttribute(Context ctx, @AttrRes int attr) { + TypedValue outValue = new TypedValue(); + ctx.getTheme().resolveAttribute(attr, outValue, true); + return outValue.resourceId; + } + + @ColorInt + public static int resolveColorAttribute(Context ctx, @AttrRes int res) { + @ColorRes + int color = resolveAttribute(ctx, res); + return ContextCompat.getColor(ctx, color); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/util/UserFeedback.java b/mailbox-android/src/main/java/org/briarproject/mailbox/util/UserFeedback.java new file mode 100644 index 0000000000000000000000000000000000000000..045628b5c5bc78fcbbf7e074b43a3de4eb10b6f9 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/util/UserFeedback.java @@ -0,0 +1,5 @@ +package org.briarproject.mailbox.util; + +public class UserFeedback extends Exception { + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/util/VersionedAdapter.java b/mailbox-android/src/main/java/org/briarproject/mailbox/util/VersionedAdapter.java new file mode 100644 index 0000000000000000000000000000000000000000..84cf7c3e070e4fac4fcd7d71c6825dc2ec4295ba --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/util/VersionedAdapter.java @@ -0,0 +1,26 @@ +package org.briarproject.mailbox.util; + +import android.support.annotation.UiThread; + +public interface VersionedAdapter { + + /** + * Returns the adapter's revision counter. This method should be called on + * any thread before starting an asynchronous load that could overwrite + * other changes to the adapter, and called again on the UI thread before + * applying the changes from the asynchronous load. If the revision has + * changed between the two calls, the asynchronous load should be restarted + * without applying its changes. Otherwise {@link #incrementRevision()} + * should be called before applying the changes. + */ + int getRevision(); + + /** + * Increments the adapter's revision counter. This method should be called + * on the UI thread before applying any changes to the adapter that could + * be overwritten by an asynchronous load. + */ + @UiThread + void incrementRevision(); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/view/BriarRecyclerView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/view/BriarRecyclerView.java new file mode 100644 index 0000000000000000000000000000000000000000..044065809af49aa9ded2a0bb261c212aed8a39f1 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/view/BriarRecyclerView.java @@ -0,0 +1,202 @@ +package org.briarproject.mailbox.view; + +import android.content.Context; +import android.content.res.TypedArray; +import android.os.Handler; +import android.os.Looper; +import android.support.v7.widget.RecyclerView; +import android.support.v7.widget.RecyclerView.Adapter; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.ProgressBar; +import android.widget.TextView; + +import org.briarproject.mailbox.R; + +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +import static org.briarproject.mailbox.util.UiUtils.MIN_DATE_RESOLUTION; + + +public class BriarRecyclerView extends FrameLayout { + + private static final Logger LOG = + Logger.getLogger(BriarRecyclerView.class.getName()); + + private final Handler handler = new Handler(Looper.getMainLooper()); + + private RecyclerView recyclerView; + private TextView emptyView; + private ProgressBar progressBar; + private RecyclerView.AdapterDataObserver emptyObserver; + private Runnable refresher = null; + private boolean isScrollingToEnd = false; + + public BriarRecyclerView(Context context) { + this(context, null, 0); + } + + public BriarRecyclerView(Context context, @Nullable AttributeSet attrs) { + this(context, attrs, 0); + } + + public BriarRecyclerView(Context context, @Nullable AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + + TypedArray attributes = context.obtainStyledAttributes(attrs, + R.styleable.BriarRecyclerView); + isScrollingToEnd = attributes + .getBoolean(R.styleable.BriarRecyclerView_scrollToEnd, true); + String emtpyText = + attributes.getString(R.styleable.BriarRecyclerView_emptyText); + if (emtpyText != null) setEmptyText(emtpyText); + attributes.recycle(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + stopPeriodicUpdate(); + } + + private void initViews() { + View v = LayoutInflater.from(getContext()).inflate( + R.layout.briar_recycler_view, this, true); + + recyclerView = v.findViewById(R.id.recyclerView); + emptyView = v.findViewById(R.id.emptyView); + progressBar = v.findViewById(R.id.progressBar); + + showProgressBar(); + + // scroll down when opening keyboard + if (isScrollingToEnd) { + addLayoutChangeListener(); + } + + emptyObserver = new RecyclerView.AdapterDataObserver() { + @Override + public void onItemRangeInserted(int positionStart, int itemCount) { + super.onItemRangeInserted(positionStart, itemCount); + if (itemCount > 0) showData(); + } + + @Override + public void onItemRangeRemoved(int positionStart, int itemCount) { + super.onItemRangeRemoved(positionStart, itemCount); + if (itemCount > 0) showData(); + } + }; + } + + private void addLayoutChangeListener() { + recyclerView.addOnLayoutChangeListener((v, left, top, right, bottom, + oldLeft, oldTop, oldRight, oldBottom) -> { + if (bottom < oldBottom) { + recyclerView.postDelayed(() -> scrollToPosition( + recyclerView.getAdapter().getItemCount() - 1), 100); + } + }); + } + + public void setLayoutManager(RecyclerView.LayoutManager layout) { + if (recyclerView == null) initViews(); + recyclerView.setLayoutManager(layout); + } + + public void setAdapter(Adapter adapter) { + if (recyclerView == null) initViews(); + + Adapter oldAdapter = recyclerView.getAdapter(); + if (oldAdapter != null) { + oldAdapter.unregisterAdapterDataObserver(emptyObserver); + } + + recyclerView.setAdapter(adapter); + + if (adapter != null) { + adapter.registerAdapterDataObserver(emptyObserver); + + if (adapter.getItemCount() > 0) { + // only show data if adapter has data already + // otherwise progress bar is shown + emptyObserver.onChanged(); + } + } + } + + public void setEmptyText(String text) { + if (recyclerView == null) initViews(); + emptyView.setText(text); + } + + public void setEmptyText(int res) { + if (recyclerView == null) initViews(); + emptyView.setText(res); + } + + public void showProgressBar() { + if (recyclerView == null) initViews(); + recyclerView.setVisibility(INVISIBLE); + emptyView.setVisibility(INVISIBLE); + progressBar.setVisibility(VISIBLE); + } + + public void showData() { + if (recyclerView == null) initViews(); + Adapter adapter = recyclerView.getAdapter(); + if (adapter != null) { + if (adapter.getItemCount() == 0) { + emptyView.setVisibility(VISIBLE); + recyclerView.setVisibility(INVISIBLE); + } else { + // use GONE here so empty view doesn't use space on small lists + emptyView.setVisibility(GONE); + recyclerView.setVisibility(VISIBLE); + } + progressBar.setVisibility(GONE); + } + } + + public void scrollToPosition(int position) { + if (recyclerView == null) initViews(); + recyclerView.scrollToPosition(position); + } + + public void smoothScrollToPosition(int position) { + if (recyclerView == null) initViews(); + recyclerView.smoothScrollToPosition(position); + } + + public RecyclerView getRecyclerView() { + return this.recyclerView; + } + + public void startPeriodicUpdate() { + if (recyclerView == null || recyclerView.getAdapter() == null) { + throw new IllegalStateException("Need to call setAdapter() first!"); + } + refresher = () -> { + LOG.info("Updating Content..."); + Adapter adapter = recyclerView.getAdapter(); + adapter.notifyItemRangeChanged(0, adapter.getItemCount()); + handler.postDelayed(refresher, MIN_DATE_RESOLUTION); + }; + LOG.info("Adding Handler Callback"); + handler.postDelayed(refresher, MIN_DATE_RESOLUTION); + } + + public void stopPeriodicUpdate() { + if (refresher != null) { + LOG.info("Removing Handler Callback"); + handler.removeCallbacks(refresher); + refresher = null; + } + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/view/BriarRecyclerViewBehavior.java b/mailbox-android/src/main/java/org/briarproject/mailbox/view/BriarRecyclerViewBehavior.java new file mode 100644 index 0000000000000000000000000000000000000000..c39647bc495500f2a0505617f9c92a590530034a --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/view/BriarRecyclerViewBehavior.java @@ -0,0 +1,44 @@ +package org.briarproject.mailbox.view; + +import android.content.Context; +import android.support.design.widget.CoordinatorLayout; +import android.support.design.widget.Snackbar; +import android.util.AttributeSet; +import android.view.View; + +public class BriarRecyclerViewBehavior + extends CoordinatorLayout.Behavior<BriarRecyclerView> { + + public BriarRecyclerViewBehavior(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + public boolean onDependentViewChanged(CoordinatorLayout parent, + BriarRecyclerView child, View dependency) { + + // FIXME the below code works, but does not reset margin when snackbar is dismissed +/* + int margin = 0; + if (dependency.isShown()) margin = dependency.getHeight(); + + // set snackbar height as bottom margin if it is shown + CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) child.getLayoutParams(); + params.setMargins(0, 0, 0, margin); + child.setLayoutParams(params); + + child.scrollToPosition(0); +*/ + return true; + } + + @Override + public boolean layoutDependsOn(CoordinatorLayout parent, + BriarRecyclerView child, View dependency) { + // we only want to trigger the change + // only when the changes is from a snackbar + return dependency instanceof Snackbar.SnackbarLayout; + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/view/QrCodeView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/view/QrCodeView.java new file mode 100644 index 0000000000000000000000000000000000000000..27afed48b576b55fb57428dd7f550b9300d47bf3 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/view/QrCodeView.java @@ -0,0 +1,64 @@ +package org.briarproject.mailbox.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.UiThread; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.animation.AlphaAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import org.briarproject.mailbox.R; + + +public class QrCodeView extends FrameLayout { + + private final ImageView qrCodeImageView; + private boolean fullscreen = false; + private FullscreenListener listener; + + public QrCodeView(@NonNull Context context, + @Nullable AttributeSet attrs) { + super(context, attrs); + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.qr_code_view, this, true); + qrCodeImageView = findViewById(R.id.qr_code); + ImageView fullscreenButton = findViewById(R.id.fullscreen_button); + fullscreenButton.setOnClickListener(v -> { + fullscreen = !fullscreen; + if (!fullscreen) { + fullscreenButton.setImageResource( + R.drawable.ic_fullscreen_black_48dp); + } else { + fullscreenButton.setImageResource( + R.drawable.ic_fullscreen_exit_black_48dp); + } + if (listener != null) + listener.setFullscreen(fullscreen); + } + ); + } + + @UiThread + public void setQrCode(Bitmap qrCode) { + qrCodeImageView.setImageBitmap(qrCode); + // Simple fade-in animation + AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f); + anim.setDuration(200); + qrCodeImageView.startAnimation(anim); + } + + @UiThread + public void setFullscreenListener(FullscreenListener listener) { + this.listener = listener; + } + + public interface FullscreenListener { + void setFullscreen(boolean fullscreen); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/view/TextAvatarView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/view/TextAvatarView.java new file mode 100644 index 0000000000000000000000000000000000000000..07838858bf05c4dd8ad61112df7c04e714d53611 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/view/TextAvatarView.java @@ -0,0 +1,81 @@ +package org.briarproject.mailbox.view; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.support.annotation.UiThread; +import android.support.v7.widget.AppCompatTextView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.widget.FrameLayout; +import android.widget.TextView; + +import org.briarproject.bramble.api.identity.Author; +import org.briarproject.mailbox.R; + +import javax.annotation.Nullable; + +import de.hdodenhof.circleimageview.CircleImageView; +import im.delight.android.identicons.IdenticonDrawable; + +@UiThread +public class TextAvatarView extends FrameLayout { + + private final AppCompatTextView character; + private final CircleImageView background; + private final TextView badge; + + public TextAvatarView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + + LayoutInflater inflater = (LayoutInflater) context + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + inflater.inflate(R.layout.text_avatar_view, this, true); + character = findViewById(R.id.textAvatarView); + background = findViewById(R.id.avatarBackground); + badge = findViewById(R.id.unreadCountView); + badge.setVisibility(INVISIBLE); + } + + public TextAvatarView(Context context) { + this(context, null); + } + + public void setText(String text) { + character.setText(text.toUpperCase()); + } + + public void setUnreadCount(int count) { + if (count > 0) { + badge.setText(String.valueOf(count)); + badge.setVisibility(VISIBLE); + } else { + badge.setVisibility(INVISIBLE); + } + } + + public void setBackgroundBytes(byte[] bytes) { + int r = getByte(bytes, 0) * 3 / 4 + 96; + int g = getByte(bytes, 1) * 3 / 4 + 96; + int b = getByte(bytes, 2) * 3 / 4 + 96; + int color = Color.rgb(r, g, b); + + background.setImageDrawable(new ColorDrawable(color)); + } + + private byte getByte(byte[] bytes, int index) { + if (bytes == null) { + return -128; + } else { + return bytes[index % bytes.length]; + } + } + + public void setAuthorAvatar(Author author) { + Drawable drawable = new IdenticonDrawable(author.getId().getBytes()); + background.setImageDrawable(drawable); + character.setVisibility(GONE); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/view/TrustIndicatorView.java b/mailbox-android/src/main/java/org/briarproject/mailbox/view/TrustIndicatorView.java new file mode 100644 index 0000000000000000000000000000000000000000..38bc7e986ee66877be7a0e865c8209b356b8f448 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/view/TrustIndicatorView.java @@ -0,0 +1,53 @@ +package org.briarproject.mailbox.view; + +import android.content.Context; +import android.support.annotation.UiThread; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.widget.ImageView; + +import org.briarproject.bramble.api.identity.Author.Status; +import org.briarproject.mailbox.R; + +@UiThread +public class TrustIndicatorView extends ImageView { + + public TrustIndicatorView(Context context) { + super(context); + } + + public TrustIndicatorView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public TrustIndicatorView(Context context, AttributeSet attrs, + int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public void setTrustLevel(Status status) { + int res; + switch (status) { + case ANONYMOUS: + res = R.drawable.trust_indicator_anonymous; + break; + case UNVERIFIED: + res = R.drawable.trust_indicator_unverified; + break; + case VERIFIED: + res = R.drawable.trust_indicator_verified; + break; + case OURSELVES: + res = R.drawable.ic_our_identity; + break; + default: + res = R.drawable.trust_indicator_unknown; + } + setImageDrawable(ContextCompat.getDrawable(getContext(), res)); + setVisibility(VISIBLE); + + invalidate(); + requestLayout(); + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/BaseViewModel.java b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/BaseViewModel.java new file mode 100644 index 0000000000000000000000000000000000000000..78b091a0df11e2e646cd6647c6c29b22c7b48329 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/BaseViewModel.java @@ -0,0 +1,32 @@ +package org.briarproject.mailbox.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.AndroidViewModel; +import android.support.annotation.NonNull; + +import org.briarproject.mailbox.AndroidComponent; +import org.briarproject.mailbox.MailboxApplication; + +public abstract class BaseViewModel extends AndroidViewModel { + + public BaseViewModel( + @NonNull + Application application) { + super(application); + AndroidComponent applicationComponent = + ((MailboxApplication) getApplication()) + .getApplicationComponent(); + + ViewModelComponent viewModelComponent = + DaggerViewModelComponent.builder() + .androidComponent(applicationComponent) + .viewModelModule(new ViewModelModule()) + .build(); + + injectViewModel(viewModelComponent); + + } + + abstract void injectViewModel(ViewModelComponent viewModelComponent); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/MailboxOwnerStatusViewModel.java b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/MailboxOwnerStatusViewModel.java new file mode 100644 index 0000000000000000000000000000000000000000..c06eb57acf3639645c48049098e4dccd0df18036 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/MailboxOwnerStatusViewModel.java @@ -0,0 +1,147 @@ +package org.briarproject.mailbox.viewmodel; + +import android.app.Application; +import android.arch.lifecycle.MutableLiveData; +import android.support.annotation.NonNull; + +import org.briarproject.bramble.api.contact.Contact; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.contact.ContactManager; +import org.briarproject.bramble.api.contact.MailboxOwner; +import org.briarproject.bramble.api.contact.event.ContactAddedEvent; +import org.briarproject.bramble.api.contact.event.ContactRemovedEvent; +import org.briarproject.bramble.api.db.DatabaseExecutor; +import org.briarproject.bramble.api.event.Event; +import org.briarproject.bramble.api.event.EventBus; +import org.briarproject.bramble.api.event.EventListener; +import org.briarproject.bramble.api.lifecycle.LifecycleManager; +import org.briarproject.bramble.api.plugin.ConnectionRegistry; +import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent; +import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent; + +import java.util.Collection; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.inject.Inject; + +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.contact.ContactTypes.MAILBOX_OWNER; +import static org.briarproject.bramble.util.LogUtils.logException; + +public class MailboxOwnerStatusViewModel extends BaseViewModel implements + EventListener { + + public static final String TAG = + MailboxOwnerStatusViewModel.class.getName(); + private static final Logger LOG = Logger.getLogger(TAG); + public final MutableLiveData<MailboxOwnerStatus> + mailboxOwnerStatusLiveData = new MutableLiveData<>(); + @Inject + volatile ContactManager contactManager; + @Inject + volatile ConnectionRegistry connectionRegistry; + @Inject + volatile LifecycleManager lifecycleManager; + @Inject + @DatabaseExecutor + Executor databaseExecutor; + @Inject + EventBus eventBus; + private volatile MailboxOwnerStatus mailboxOwnerStatus; + + public MailboxOwnerStatusViewModel(@NonNull Application application) { + super(application); + loadOwner(); + eventBus.addListener(this); + } + + @Override + protected void onCleared() { + super.onCleared(); + eventBus.removeListener(this); + } + + @Override + void injectViewModel(ViewModelComponent viewModelComponent) { + viewModelComponent.inject(this); + } + + @Override + public void eventOccurred(Event e) { + if (e instanceof ContactConnectedEvent) + updateStatus(((ContactConnectedEvent) e).getContactId()); + else if (e instanceof ContactDisconnectedEvent) + updateStatus(((ContactDisconnectedEvent) e).getContactId()); + else if (e instanceof ContactAddedEvent) { + if (mailboxOwnerStatus == null) + loadOwner(); + } else if (e instanceof ContactRemovedEvent) { + if (mailboxOwnerStatus != null && + ((ContactRemovedEvent) e).getContactId() + .equals(mailboxOwnerStatus.getId())) { + mailboxOwnerStatus = null; + mailboxOwnerStatusLiveData.postValue(mailboxOwnerStatus); + } + } + + } + + private void updateStatus(ContactId contactId) { + if (mailboxOwnerStatus != null && + mailboxOwnerStatus.getId().equals(contactId)) { + mailboxOwnerStatus + .setOnline(connectionRegistry.isConnected(contactId)); + mailboxOwnerStatusLiveData.postValue(mailboxOwnerStatus); + } + } + + private void loadOwner() { + databaseExecutor.execute(() -> { + try { + lifecycleManager.waitForDatabase(); + Collection<Contact> result = + contactManager.getContactsByType(MAILBOX_OWNER); + if (!result.isEmpty()) { + MailboxOwner owner = + (MailboxOwner) result.iterator().next(); + mailboxOwnerStatus = + new MailboxOwnerStatus(owner, getStatus(owner)); + mailboxOwnerStatusLiveData.postValue(mailboxOwnerStatus); + } + } catch (Exception e) { + logException(LOG, WARNING, e); + } + }); + } + + private boolean getStatus(Contact owner) { + return connectionRegistry.isConnected(owner.getId()); + } + + public class MailboxOwnerStatus { + private final MailboxOwner mailboxOwner; + private boolean online; + + MailboxOwnerStatus(MailboxOwner mailboxOwner, boolean online) { + this.mailboxOwner = mailboxOwner; + this.online = online; + } + + public String getName() { + return mailboxOwner.getAuthor().getName(); + } + + public ContactId getId() { + return mailboxOwner.getId(); + } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean isOnline) { + online = isOnline; + } + } +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelComponent.java b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelComponent.java new file mode 100644 index 0000000000000000000000000000000000000000..d9a1641304bcff1e8499eb851db7854c5869677f --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelComponent.java @@ -0,0 +1,13 @@ +package org.briarproject.mailbox.viewmodel; + +import org.briarproject.mailbox.AndroidComponent; + +import dagger.Component; +@ViewModelScope +@Component(modules = {ViewModelModule.class}, + dependencies = {AndroidComponent.class}) +public interface ViewModelComponent { + + void inject(MailboxOwnerStatusViewModel mailboxOwnerStatusViewModel); + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelModule.java b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelModule.java new file mode 100644 index 0000000000000000000000000000000000000000..b21ee637868adb88a7c0f156e9aabcc280bc5a2b --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelModule.java @@ -0,0 +1,7 @@ +package org.briarproject.mailbox.viewmodel; + +import dagger.Module; + +@Module +public class ViewModelModule { +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelScope.java b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelScope.java new file mode 100644 index 0000000000000000000000000000000000000000..fe33771fd4fd37ff8ceb084b67667485c287f668 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/viewmodel/ViewModelScope.java @@ -0,0 +1,11 @@ +package org.briarproject.mailbox.viewmodel; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +import javax.inject.Scope; + +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface ViewModelScope { +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/widget/LinkDialogFragment.java b/mailbox-android/src/main/java/org/briarproject/mailbox/widget/LinkDialogFragment.java new file mode 100644 index 0000000000000000000000000000000000000000..abece2c3ce14f5f4abb3e1003d8b519330e57001 --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/widget/LinkDialogFragment.java @@ -0,0 +1,79 @@ +package org.briarproject.mailbox.widget; + + +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView; + +import org.briarproject.mailbox.R; + +import java.util.List; + +public class LinkDialogFragment extends DialogFragment { + + private static final String TAG = LinkDialogFragment.class.getName(); + + private String url; + + public static LinkDialogFragment newInstance(String url) { + LinkDialogFragment f = new LinkDialogFragment(); + + Bundle args = new Bundle(); + args.putString("url", url); + f.setArguments(args); + + return f; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + url = getArguments().getString("url"); + + setStyle(STYLE_NO_TITLE, R.style.BriarDialogTheme); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + + View v = inflater.inflate(R.layout.fragment_link_dialog, container, + false); + + TextView urlView = v.findViewById(R.id.urlView); + urlView.setText(url); + + // prepare normal intent or intent chooser + Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + PackageManager packageManager = getContext().getPackageManager(); + List activities = packageManager.queryIntentActivities(i, + PackageManager.MATCH_DEFAULT_ONLY); + boolean choice = activities.size() > 1; + Intent intent = choice ? Intent.createChooser(i, + getString(R.string.link_warning_open_link)) : i; + + Button openButton = v.findViewById(R.id.openButton); + openButton.setOnClickListener(v1 -> { + startActivity(intent); + getDialog().dismiss(); + }); + + Button cancelButton = v.findViewById(R.id.cancelButton); + cancelButton.setOnClickListener(v1 -> getDialog().cancel()); + + return v; + } + + public String getUniqueTag() { + return TAG; + } + +} diff --git a/mailbox-android/src/main/java/org/briarproject/mailbox/widget/TapSafeFrameLayout.java b/mailbox-android/src/main/java/org/briarproject/mailbox/widget/TapSafeFrameLayout.java new file mode 100644 index 0000000000000000000000000000000000000000..5a5163c64fd85eef58a947edb702d0692d2f39cf --- /dev/null +++ b/mailbox-android/src/main/java/org/briarproject/mailbox/widget/TapSafeFrameLayout.java @@ -0,0 +1,52 @@ +package org.briarproject.mailbox.widget; + +import android.content.Context; +import android.support.annotation.AttrRes; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.widget.FrameLayout; + +import org.briarproject.bramble.api.nullsafety.NotNullByDefault; +import org.briarproject.mailbox.util.UiUtils; + +import javax.annotation.Nullable; + +import static android.view.MotionEvent.FLAG_WINDOW_IS_OBSCURED; + +@NotNullByDefault +public class TapSafeFrameLayout extends FrameLayout { + + @Nullable + private OnTapFilteredListener listener; + + public TapSafeFrameLayout(Context context) { + super(context); + UiUtils.setFilterTouchesWhenObscured(this, false); + } + + public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + UiUtils.setFilterTouchesWhenObscured(this, false); + } + + public TapSafeFrameLayout(Context context, @Nullable AttributeSet attrs, + @AttrRes int defStyleAttr) { + super(context, attrs, defStyleAttr); + UiUtils.setFilterTouchesWhenObscured(this, false); + } + + public void setOnTapFilteredListener(OnTapFilteredListener listener) { + this.listener = listener; + } + + @Override + public boolean onFilterTouchEventForSecurity(MotionEvent e) { + boolean obscured = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0; + if (obscured && listener != null) return listener.shouldAllowTap(); + else return !obscured; + } + + public interface OnTapFilteredListener { + boolean shouldAllowTap(); + } +} diff --git a/mailbox-android/src/main/res/anim/fade_in.xml b/mailbox-android/src/main/res/anim/fade_in.xml new file mode 100644 index 0000000000000000000000000000000000000000..4d3bef4a381f2bc23b53a600fc47608e9a65a8a7 --- /dev/null +++ b/mailbox-android/src/main/res/anim/fade_in.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<alpha + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_longAnimTime" + android:fromAlpha="0.0" + android:interpolator="@android:interpolator/decelerate_quad" + android:toAlpha="1.0"/> diff --git a/mailbox-android/src/main/res/anim/fade_out.xml b/mailbox-android/src/main/res/anim/fade_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..84bad946f7b5b9c4c2f59ff106f1b079af96b4be --- /dev/null +++ b/mailbox-android/src/main/res/anim/fade_out.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<alpha + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_mediumAnimTime" + android:fromAlpha="1.0" + android:interpolator="@android:interpolator/accelerate_quad" + android:toAlpha="0.0"/> diff --git a/mailbox-android/src/main/res/anim/screen_new_in.xml b/mailbox-android/src/main/res/anim/screen_new_in.xml new file mode 100644 index 0000000000000000000000000000000000000000..6bdf53b16d438e3b8312164f3ee48ac4b43fd545 --- /dev/null +++ b/mailbox-android/src/main/res/anim/screen_new_in.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<set + xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- slide in from right --> + <translate + android:duration="@android:integer/config_mediumAnimTime" + android:fromXDelta="100%p" + android:interpolator="@android:interpolator/decelerate_quad" + android:toXDelta="0"/> + +</set> diff --git a/mailbox-android/src/main/res/anim/screen_new_out.xml b/mailbox-android/src/main/res/anim/screen_new_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..5411fa11baa7c16b1651f5f3354f38ffff187302 --- /dev/null +++ b/mailbox-android/src/main/res/anim/screen_new_out.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<set + xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- slide out to right --> + <translate + android:duration="@android:integer/config_mediumAnimTime" + android:fromXDelta="0" + android:interpolator="@android:interpolator/accelerate_quad" + android:toXDelta="100%p"/> + +</set> diff --git a/mailbox-android/src/main/res/anim/screen_old_in.xml b/mailbox-android/src/main/res/anim/screen_old_in.xml new file mode 100644 index 0000000000000000000000000000000000000000..2360d85b58d6b8f1372873804879fc1dfc65376c --- /dev/null +++ b/mailbox-android/src/main/res/anim/screen_old_in.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<alpha + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_mediumAnimTime" + android:fromAlpha="0.0" + android:interpolator="@android:interpolator/decelerate_quad" + android:toAlpha="1.0"/> diff --git a/mailbox-android/src/main/res/anim/screen_old_out.xml b/mailbox-android/src/main/res/anim/screen_old_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..84bad946f7b5b9c4c2f59ff106f1b079af96b4be --- /dev/null +++ b/mailbox-android/src/main/res/anim/screen_old_out.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<alpha + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_mediumAnimTime" + android:fromAlpha="1.0" + android:interpolator="@android:interpolator/accelerate_quad" + android:toAlpha="0.0"/> diff --git a/mailbox-android/src/main/res/anim/step_next_in.xml b/mailbox-android/src/main/res/anim/step_next_in.xml new file mode 100644 index 0000000000000000000000000000000000000000..6bdf53b16d438e3b8312164f3ee48ac4b43fd545 --- /dev/null +++ b/mailbox-android/src/main/res/anim/step_next_in.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<set + xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- slide in from right --> + <translate + android:duration="@android:integer/config_mediumAnimTime" + android:fromXDelta="100%p" + android:interpolator="@android:interpolator/decelerate_quad" + android:toXDelta="0"/> + +</set> diff --git a/mailbox-android/src/main/res/anim/step_next_out.xml b/mailbox-android/src/main/res/anim/step_next_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..5411fa11baa7c16b1651f5f3354f38ffff187302 --- /dev/null +++ b/mailbox-android/src/main/res/anim/step_next_out.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<set + xmlns:android="http://schemas.android.com/apk/res/android"> + + <!-- slide out to right --> + <translate + android:duration="@android:integer/config_mediumAnimTime" + android:fromXDelta="0" + android:interpolator="@android:interpolator/accelerate_quad" + android:toXDelta="100%p"/> + +</set> diff --git a/mailbox-android/src/main/res/anim/step_previous_in.xml b/mailbox-android/src/main/res/anim/step_previous_in.xml new file mode 100644 index 0000000000000000000000000000000000000000..2360d85b58d6b8f1372873804879fc1dfc65376c --- /dev/null +++ b/mailbox-android/src/main/res/anim/step_previous_in.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<alpha + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_mediumAnimTime" + android:fromAlpha="0.0" + android:interpolator="@android:interpolator/decelerate_quad" + android:toAlpha="1.0"/> diff --git a/mailbox-android/src/main/res/anim/step_previous_out.xml b/mailbox-android/src/main/res/anim/step_previous_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..84bad946f7b5b9c4c2f59ff106f1b079af96b4be --- /dev/null +++ b/mailbox-android/src/main/res/anim/step_previous_out.xml @@ -0,0 +1,7 @@ +<?xml version="1.0" encoding="utf-8"?> +<alpha + xmlns:android="http://schemas.android.com/apk/res/android" + android:duration="@android:integer/config_mediumAnimTime" + android:fromAlpha="1.0" + android:interpolator="@android:interpolator/accelerate_quad" + android:toAlpha="0.0"/> diff --git a/mailbox-android/src/main/res/color/button_text.xml b/mailbox-android/src/main/res/color/button_text.xml new file mode 100644 index 0000000000000000000000000000000000000000..1f1c03e9fe14711ca42acaa2cd7800b8afb99f1a --- /dev/null +++ b/mailbox-android/src/main/res/color/button_text.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector + xmlns:android="http://schemas.android.com/apk/res/android"> + <item + android:color="@color/briar_button_text_disabled" + android:state_enabled="false"/> + <item + android:color="#ffffffff"/> +</selector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/action_delete_black.xml b/mailbox-android/src/main/res/drawable/action_delete_black.xml new file mode 100644 index 0000000000000000000000000000000000000000..2cedb57ffb284f04221c928c12efaff010ef71bf --- /dev/null +++ b/mailbox-android/src/main/res/drawable/action_delete_black.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/action_delete_white.xml b/mailbox-android/src/main/res/drawable/action_delete_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..ad17d75a762d98f3c8a5da70fa9caecc927f5c2c --- /dev/null +++ b/mailbox-android/src/main/res/drawable/action_delete_white.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFFFF" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/alerts_and_states_error.xml b/mailbox-android/src/main/res/drawable/alerts_and_states_error.xml new file mode 100644 index 0000000000000000000000000000000000000000..02056c85cec5acdaae01308058a264b1c07b4a3f --- /dev/null +++ b/mailbox-android/src/main/res/drawable/alerts_and_states_error.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="128dp" + android:height="128dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/blogs.xml b/mailbox-android/src/main/res/drawable/blogs.xml new file mode 100644 index 0000000000000000000000000000000000000000..551d200455949a566b1f3bb98c62bde999a90466 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/blogs.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M13,12h7v1.5h-7zM13,9.5h7L20,11h-7zM13,14.5h7L20,16h-7zM21,4L3,4c-1.1,0 -2,0.9 -2,2v13c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,6c0,-1.1 -0.9,-2 -2,-2zM21,19h-9L12,6h9v13z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/border_explanation.xml b/mailbox-android/src/main/res/drawable/border_explanation.xml new file mode 100644 index 0000000000000000000000000000000000000000..f545133765c9a0f461e0bd666b372abf1a441006 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/border_explanation.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <solid + android:color="@android:color/transparent"/> + + <stroke + android:width="2dp" + android:color="@color/color_primary"/> + +</shape> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/bubble.xml b/mailbox-android/src/main/res/drawable/bubble.xml new file mode 100644 index 0000000000000000000000000000000000000000..8d2dd0c8088eae5f37ae0ab57dedeb32982512b2 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/bubble.xml @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <corners + android:radius="@dimen/unread_bubble_size"/> + + <padding + android:left="@dimen/unread_bubble_padding_horizontal" + android:right="@dimen/unread_bubble_padding_horizontal" + android:bottom="1px"/> + + <solid + android:color="@color/briar_accent"/> + + <stroke + android:color="@color/briar_text_primary_inverse" + android:width="@dimen/avatar_border_width"/> + +</shape> + diff --git a/mailbox-android/src/main/res/drawable/bubble_white.xml b/mailbox-android/src/main/res/drawable/bubble_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..bb79ab2fa740c59bac6b65c5c7d15713ac047ac7 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/bubble_white.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="rectangle"> + + <corners + android:radius="@dimen/unread_bubble_size"/> + + <solid + android:color="@color/briar_text_primary_inverse"/> + + <stroke + android:color="@color/briar_text_primary" + android:width="1dp"/> + +</shape> diff --git a/mailbox-android/src/main/res/drawable/chevron_down_white.xml b/mailbox-android/src/main/res/drawable/chevron_down_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..c7c475fea449154373e23019cccb1f7c82114402 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/chevron_down_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/chevron_up_white.xml b/mailbox-android/src/main/res/drawable/chevron_up_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..3715aa2f0d95d75993d5d987e5d76c3838058b3a --- /dev/null +++ b/mailbox-android/src/main/res/drawable/chevron_up_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/contact_connected.xml b/mailbox-android/src/main/res/drawable/contact_connected.xml new file mode 100644 index 0000000000000000000000000000000000000000..a5314250998a4145187e5b153dc1a676f07d930d --- /dev/null +++ b/mailbox-android/src/main/res/drawable/contact_connected.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.56" + android:viewportHeight="24" + android:viewportWidth="24"> + + <path + android:fillColor="#FF000000" + android:pathData="M12,2 C6.48,2,2,6.48,2,12 S6.48,22,12,22 S22,17.52,22,12 S17.52,2,12,2 Z M12,20 +C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z"/> + <path + android:pathData="M0,0 L24,0 L24,24 L0,24 Z"/> + <path + android:fillColor="#95d220" + android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 +C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 +C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 +C20.0698,10.7495,20.1616,12.4612,19.777,13.9758 +C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771 +C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="0.76779664"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/contact_disconnected.xml b/mailbox-android/src/main/res/drawable/contact_disconnected.xml new file mode 100644 index 0000000000000000000000000000000000000000..c1a088235a0df4d82940327128b21f2e79211d69 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/contact_disconnected.xml @@ -0,0 +1,10 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:alpha="0.56" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/contact_offline.xml b/mailbox-android/src/main/res/drawable/contact_offline.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac18913fcb5eb66321d44729888b43d03680cde3 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/contact_offline.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + + <path + android:fillColor="#2D3E50" + android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 +C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 +C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 +C20.0698,10.7495,20.1616,12.4612,19.777,13.9758 +C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771 +C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z" + android:strokeColor="#FFFFFF" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1"/> + +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/contact_online.xml b/mailbox-android/src/main/res/drawable/contact_online.xml new file mode 100644 index 0000000000000000000000000000000000000000..f68b831022c7acbe802b7f76664c1a4ddc13cec0 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/contact_online.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + + <path + android:fillColor="#95D220" + android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896 +C4.55452,7.53099,7.09451,4.8236,10.394,4.14714 +C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295 +C20.0698,10.7495,20.1616,12.4612,19.777,13.9758 +C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771 +C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z" + android:strokeColor="#FFFFFF" + android:strokeLineCap="round" + android:strokeLineJoin="round" + android:strokeWidth="1.5"/> + +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/forum_item_create_white.xml b/mailbox-android/src/main/res/drawable/forum_item_create_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..2a963d9abab1e68b4f0bfe009ead011584165c07 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/forum_item_create_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_add_white.xml b/mailbox-android/src/main/res/drawable/ic_add_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..b242968999731a98068dc0ee2d8d280ed251fede --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_add_white.xml @@ -0,0 +1,5 @@ +<vector android:height="24dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFFFF" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_backspace.xml b/mailbox-android/src/main/res/drawable/ic_backspace.xml new file mode 100644 index 0000000000000000000000000000000000000000..e4ecae7b16911a1c5dfd81e4cbc3832a5da3b771 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_backspace.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M22,3L7,3c-0.69,0 -1.23,0.35 -1.59,0.88L0,12l5.41,8.11c0.36,0.53 0.9,0.89 1.59,0.89h15c1.1,0 2,-0.9 2,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM19,15.59L17.59,17 14,13.41 10.41,17 9,15.59 12.59,12 9,8.41 10.41,7 14,10.59 17.59,7 19,8.41 15.41,12 19,15.59z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_check_white.xml b/mailbox-android/src/main/res/drawable/ic_check_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..59f823220d2daf783d765923dad866a8f68653cc --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_check_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_close.xml b/mailbox-android/src/main/res/drawable/ic_close.xml new file mode 100644 index 0000000000000000000000000000000000000000..bcc722dc42a76809f8d47b85eeb852b14a7cbc29 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="18dp" + android:height="18dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_contact_introduction.xml b/mailbox-android/src/main/res/drawable/ic_contact_introduction.xml new file mode 100644 index 0000000000000000000000000000000000000000..077ce4189f5bc8fda752a1aab9e1039493c1bcd0 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_contact_introduction.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9.01,14L2,14v2h7.01v3L13,15l-3.99,-4v3zM14.99,13v-3L22,10L22,8h-7.01L14.99,5L11,9l3.99,4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_contacts.xml b/mailbox-android/src/main/res/drawable/ic_contacts.xml new file mode 100644 index 0000000000000000000000000000000000000000..c033a04befa44faa962ae56aa8d62439f4596115 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_contacts.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_activity.xml b/mailbox-android/src/main/res/drawable/ic_emoji_activity.xml new file mode 100644 index 0000000000000000000000000000000000000000..3b74969d39614e1080ffcdf9bd7bc82b5994f4e8 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_activity.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M7.5,7.5C9.17,5.87 11.29,4.69 13.37,4.18C15.46,3.67 17.5,3.83 18.6,4C19.71,4.15 19.87,4.31 20.03,5.41C20.18,6.5 20.33,8.55 19.82,10.63C19.31,12.71 18.13,14.83 16.5,16.5C14.83,18.13 12.71,19.31 10.63,19.82C8.55,20.33 6.5,20.18 5.41,20.03C4.31,19.87 4.15,19.71 4,18.6C3.83,17.5 3.67,15.46 4.18,13.37C4.69,11.29 5.87,9.17 7.5,7.5M7.3,15.79L8.21,16.7L9.42,15.5L10.63,16.7L11.54,15.79L10.34,14.58L12,12.91L13.21,14.12L14.12,13.21L12.91,12L14.58,10.34L15.79,11.54L16.7,10.63L15.5,9.42L16.7,8.21L15.79,7.3L14.58,8.5L13.37,7.3L12.46,8.21L13.66,9.42L12,11.09L10.79,9.88L9.88,10.79L11.09,12L9.42,13.66L8.21,12.46L7.3,13.37L8.5,14.58L7.3,15.79Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_animals_nature.xml b/mailbox-android/src/main/res/drawable/ic_emoji_animals_nature.xml new file mode 100644 index 0000000000000000000000000000000000000000..29b9c0c452ba6226b4bd32d35df50c2fd5d2cb5e --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_animals_nature.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M18.7,12.4c-0.28,-0.16 -0.57,-0.29 -0.86,-0.4 0.29,-0.11 0.58,-0.24 0.86,-0.4 1.92,-1.11 2.99,-3.12 3,-5.19 -1.79,-1.03 -4.07,-1.11 -6,0 -0.28,0.16 -0.54,0.35 -0.78,0.54 0.05,-0.31 0.08,-0.63 0.08,-0.95 0,-2.22 -1.21,-4.15 -3,-5.19C10.21,1.85 9,3.78 9,6c0,0.32 0.03,0.64 0.08,0.95 -0.24,-0.2 -0.5,-0.39 -0.78,-0.55 -1.92,-1.11 -4.2,-1.03 -6,0 0,2.07 1.07,4.08 3,5.19 0.28,0.16 0.57,0.29 0.86,0.4 -0.29,0.11 -0.58,0.24 -0.86,0.4 -1.92,1.11 -2.99,3.12 -3,5.19 1.79,1.03 4.07,1.11 6,0 0.28,-0.16 0.54,-0.35 0.78,-0.54 -0.05,0.32 -0.08,0.64 -0.08,0.96 0,2.22 1.21,4.15 3,5.19 1.79,-1.04 3,-2.97 3,-5.19 0,-0.32 -0.03,-0.64 -0.08,-0.95 0.24,0.2 0.5,0.38 0.78,0.54 1.92,1.11 4.2,1.03 6,0 -0.01,-2.07 -1.08,-4.08 -3,-5.19zM12,16c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_emoticons.xml b/mailbox-android/src/main/res/drawable/ic_emoji_emoticons.xml new file mode 100644 index 0000000000000000000000000000000000000000..262be839facc62684e026abd8e1f5c0ecd7ae81a --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_emoticons.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + + <path + android:fillColor="#000000" + android:pathData="M15.4839,3.8557 C14.8224,3.89611,14.9476,5.06823,15.4034,5.47978 +C16.7652,6.93176,17.1508,8.98786,17.299,10.9051 +C17.4081,13.1214,17.2144,15.4608,16.1275,17.4387 +C15.7657,18.1699,14.7879,18.7118,14.9747,19.6345 +C15.2618,20.6719,16.1617,19.8774,16.5955,19.4508 +C18.5872,17.7088,19.4632,15.0228,19.5268,12.4373 +C19.5469,10.5193,19.295,8.516,18.3141,6.8329 +C17.6499,5.74222,16.9234,4.59896,15.8167,3.91355 +C15.6887,3.86763,15.5784,3.84987,15.4839,3.85564 Z M5.91182,7.65831 +A1.3631614,1.3631614,0,0,0,4.54866,9.02147 +A1.3631614,1.3631614,0,0,0,5.91182,10.3846 +A1.3631614,1.3631614,0,0,0,7.27498,9.02147 +A1.3631614,1.3631614,0,0,0,5.91182,7.65831 Z M8.98492,10.6595 L8.98492,12.7042 +L13.756,12.7042 L13.756,10.6595 L8.98494,10.6595 Z M5.83527,14.1306 +A1.3631614,1.3631614,0,0,0,4.47211,15.4938 +A1.3631614,1.3631614,0,0,0,5.83527,16.857 +A1.3631614,1.3631614,0,0,0,7.19843,15.4938 +A1.3631614,1.3631614,0,0,0,5.83527,14.1306 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_flags.xml b/mailbox-android/src/main/res/drawable/ic_emoji_flags.xml new file mode 100644 index 0000000000000000000000000000000000000000..c4b56853f89354bbb1f58e548189eb3eca3c9671 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_flags.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M14.4,6L14,4H5v17h2v-7h5.6l0.4,2h7V6z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_food_drink.xml b/mailbox-android/src/main/res/drawable/ic_emoji_food_drink.xml new file mode 100644 index 0000000000000000000000000000000000000000..625a7c12b4b128ece6dc3de898974d1ff969439e --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_food_drink.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M12,6c1.11,0 2,-0.9 2,-2 0,-0.38 -0.1,-0.73 -0.29,-1.03L12,0l-1.71,2.97c-0.19,0.3 -0.29,0.65 -0.29,1.03 0,1.1 0.9,2 2,2zM16.6,15.99l-1.07,-1.07 -1.08,1.07c-1.3,1.3 -3.58,1.31 -4.89,0l-1.07,-1.07 -1.09,1.07C6.75,16.64 5.88,17 4.96,17c-0.73,0 -1.4,-0.23 -1.96,-0.61L3,21c0,0.55 0.45,1 1,1h16c0.55,0 1,-0.45 1,-1v-4.61c-0.56,0.38 -1.23,0.61 -1.96,0.61 -0.92,0 -1.79,-0.36 -2.44,-1.01zM18,9h-5L13,7h-2v2L6,9c-1.66,0 -3,1.34 -3,3v1.54c0,1.08 0.88,1.96 1.96,1.96 0.52,0 1.02,-0.2 1.38,-0.57l2.14,-2.13 2.13,2.13c0.74,0.74 2.03,0.74 2.77,0l2.14,-2.13 2.13,2.13c0.37,0.37 0.86,0.57 1.38,0.57 1.08,0 1.96,-0.88 1.96,-1.96L20.99,12C21,10.34 19.66,9 18,9z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_objects.xml b/mailbox-android/src/main/res/drawable/ic_emoji_objects.xml new file mode 100644 index 0000000000000000000000000000000000000000..7b15232cff5598f12dbd38bea3dfebe3721d3bbe --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_objects.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#000000" + android:pathData="M5,16L3,5L8.5,12L12,5L15.5,12L21,5L19,16H5M19,19A1,1 0 0,1 18,20H6A1,1 0 0,1 5,19V18H19V19Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_recent.xml b/mailbox-android/src/main/res/drawable/ic_emoji_recent.xml new file mode 100644 index 0000000000000000000000000000000000000000..fc0081fc7b722e22997aea2b8f60117b085691ea --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_recent.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_smiley_people.xml b/mailbox-android/src/main/res/drawable/ic_emoji_smiley_people.xml new file mode 100644 index 0000000000000000000000000000000000000000..8af14098045c2fb9c31e54aa34a0bc7603d839f7 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_smiley_people.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_symbols.xml b/mailbox-android/src/main/res/drawable/ic_emoji_symbols.xml new file mode 100644 index 0000000000000000000000000000000000000000..524912968a581c263199fed9d65ae5835e47ab9d --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_symbols.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M12,7.77L18.39,18H5.61L12,7.77M12,4L2,20h20L12,4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_toggle.xml b/mailbox-android/src/main/res/drawable/ic_emoji_toggle.xml new file mode 100644 index 0000000000000000000000000000000000000000..32324f57b52089fffde0d36ecafdc7956b64dda4 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_toggle.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM15.5,11c0.83,0 1.5,-0.67 1.5,-1.5S16.33,8 15.5,8 14,8.67 14,9.5s0.67,1.5 1.5,1.5zM8.5,11c0.83,0 1.5,-0.67 1.5,-1.5S9.33,8 8.5,8 7,8.67 7,9.5 7.67,11 8.5,11zM12,17.5c2.33,0 4.31,-1.46 5.11,-3.5L6.89,14c0.8,2.04 2.78,3.5 5.11,3.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_emoji_travel_places.xml b/mailbox-android/src/main/res/drawable/ic_emoji_travel_places.xml new file mode 100644 index 0000000000000000000000000000000000000000..c52b6076fcbaa22f01c8108ee72035e59197ff64 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_emoji_travel_places.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#000000" + android:pathData="M18.92,6.01C18.72,5.42 18.16,5 17.5,5h-11c-0.66,0 -1.21,0.42 -1.42,1.01L3,12v8c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h12v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-8l-2.08,-5.99zM6.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5S5.67,13 6.5,13s1.5,0.67 1.5,1.5S7.33,16 6.5,16zM17.5,16c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM5,11l1.5,-4.5h11L19,11L5,11z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_forums_black_24dp.xml b/mailbox-android/src/main/res/drawable/ic_forums_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..5bb4d8ebce0599d382b7d9af54d6dd64754d3ced --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_forums_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_fullscreen_black_48dp.xml b/mailbox-android/src/main/res/drawable/ic_fullscreen_black_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..29b26803eb0d29731863f9c99917b0bee8eeba65 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_fullscreen_black_48dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="48dp" + android:height="48dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7,14L5,14v5h5v-2L7,17v-3zM5,10h2L7,7h3L10,5L5,5v5zM17,17h-3v2h5v-5h-2v3zM14,5v2h3v3h2L19,5h-5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_fullscreen_exit_black_48dp.xml b/mailbox-android/src/main/res/drawable/ic_fullscreen_exit_black_48dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..5b62d104c1dba38e6b6b60aba19014442437b9ed --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_fullscreen_exit_black_48dp.xml @@ -0,0 +1,4 @@ +<vector android:height="48dp" android:viewportHeight="24.0" + android:viewportWidth="24.0" android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FF000000" android:pathData="M5,16h3v3h2v-5L5,14v2zM8,8L5,8v2h5L10,5L8,5v3zM14,19h2v-3h3v-2h-5v5zM16,8L16,5h-2v5h5L19,8h-3z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_group.xml b/mailbox-android/src/main/res/drawable/ic_group.xml new file mode 100644 index 0000000000000000000000000000000000000000..b7516903236a0186d3a43bd38acd33777464a4ce --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_group.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_group_white.xml b/mailbox-android/src/main/res/drawable/ic_group_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..f6e32475bd91839f3d140ff1afc791f660edaacb --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_group_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_help_outline_white.xml b/mailbox-android/src/main/res/drawable/ic_help_outline_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..b39381493b87b03f58002ab4e17ab9c258bfeb24 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_help_outline_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M11,18h2v-2h-2v2zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM12,6c-2.21,0 -4,1.79 -4,4h2c0,-1.1 0.9,-2 2,-2s2,0.9 2,2c0,2 -3,1.75 -3,5h2c0,-2.25 3,-2.5 3,-5 0,-2.21 -1.79,-4 -4,-4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_info_white.xml b/mailbox-android/src/main/res/drawable/ic_info_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..7c4d881ba07a28b17decfc68fcb644a73013ffd3 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_info_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_keyboard.xml b/mailbox-android/src/main/res/drawable/ic_keyboard.xml new file mode 100644 index 0000000000000000000000000000000000000000..dfcb91016bddc552adb0055f1cb98add832ffa91 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_keyboard.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M20,5L4,5c-1.1,0 -1.99,0.9 -1.99,2L2,17c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,7c0,-1.1 -0.9,-2 -2,-2zM11,8h2v2h-2L11,8zM11,11h2v2h-2v-2zM8,8h2v2L8,10L8,8zM8,11h2v2L8,13v-2zM7,13L5,13v-2h2v2zM7,10L5,10L5,8h2v2zM16,17L8,17v-2h8v2zM16,13h-2v-2h2v2zM16,10h-2L14,8h2v2zM19,13h-2v-2h2v2zM19,10h-2L17,8h2v2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_more_vert_accent.xml b/mailbox-android/src/main/res/drawable/ic_more_vert_accent.xml new file mode 100644 index 0000000000000000000000000000000000000000..13b8c957f9eb1a70c9d2c1b1dba7bc4c13e99e31 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_more_vert_accent.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF2D3E50" + android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_our_identity.xml b/mailbox-android/src/main/res/drawable/ic_our_identity.xml new file mode 100644 index 0000000000000000000000000000000000000000..f8e38d95b4b82e992c4c16177e8aff536c442b12 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_our_identity.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,5.9c1.16,0 2.1,0.94 2.1,2.1s-0.94,2.1 -2.1,2.1S9.9,9.16 9.9,8s0.94,-2.1 2.1,-2.1m0,9c2.97,0 6.1,1.46 6.1,2.1v1.1L5.9,18.1L5.9,17c0,-0.64 3.13,-2.1 6.1,-2.1M12,4C9.79,4 8,5.79 8,8s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,13c-2.67,0 -8,1.34 -8,4v3h16v-3c0,-2.66 -5.33,-4 -8,-4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_repeat.xml b/mailbox-android/src/main/res/drawable/ic_repeat.xml new file mode 100644 index 0000000000000000000000000000000000000000..a7771172a07213c956911c78c12eeaffc039ddc6 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_repeat.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M7,7h10v3l4,-4 -4,-4v3L5,5v6h2L7,7zM17,17L7,17v-3l-4,4 4,4v-3h12v-6h-2v4z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_rss_feed.xml b/mailbox-android/src/main/res/drawable/ic_rss_feed.xml new file mode 100644 index 0000000000000000000000000000000000000000..167b7d7b976c976cc09053e3a130164f85a8acec --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_rss_feed.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="30dp" + android:height="30dp" + android:viewportHeight="30" + android:viewportWidth="30"> + + <path + android:fillColor="#ffa500" + android:pathData="M0,0 L30,0 L30,30 L0,30 L0,0 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M8.9322,18.0339 C10.6078,18.0339,11.9661,19.3922,11.9661,21.0678 +C11.9661,22.7434,10.6078,24.1017,8.9322,24.1017 +C7.25663,24.1017,5.8983,22.7434,5.8983,21.0678 +C5.8983,19.3922,7.25663,18.0339,8.9322,18.0339 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M5.8983,15 A9.1016949,9.1016949,0,0,1,15,24.1017 L18.0339,24.1017 +A12.135593,12.135593,0,0,0,5.8983,11.9661 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M5.8983,8.9322 A15.169492,15.169492,0,0,1,21.0678,24.1017 L24.1017,24.1017 +A18.20339,18.20339,0,0,0,5.8983,5.8983 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/ic_settings_black_24dp.xml b/mailbox-android/src/main/res/drawable/ic_settings_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..11f1920e25810891c58c7636ea558e36315cfdaa --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_settings_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M19.43,12.98c0.04,-0.32 0.07,-0.64 0.07,-0.98s-0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.39,-0.3 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98s0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.39,0.3 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64l-2.11,-1.65zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5s1.57,-3.5 3.5,-3.5 3.5,1.57 3.5,3.5 -1.57,3.5 -3.5,3.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_signout_black_24dp.xml b/mailbox-android/src/main/res/drawable/ic_signout_black_24dp.xml new file mode 100644 index 0000000000000000000000000000000000000000..05569dbdf34c6d668d7f8f2a4d4c120fcf988c2d --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_signout_black_24dp.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M13,8.2l-1,-1 -4,4 -4,-4 -1,1 4,4 -4,4 1,1 4,-4 4,4 1,-1 -4,-4 4,-4zM19,1H9c-1.1,0 -2,0.9 -2,2v3h2V4h10v16H9v-2H7v3c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2V3c0,-1.1 -0.9,-2 -2,-2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_visibility.xml b/mailbox-android/src/main/res/drawable/ic_visibility.xml new file mode 100644 index 0000000000000000000000000000000000000000..80395495695264e60033525f74f14628efd8b362 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_visibility.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_visibility_off.xml b/mailbox-android/src/main/res/drawable/ic_visibility_off.xml new file mode 100644 index 0000000000000000000000000000000000000000..681832c9a38403081e2f513b6f1d5068abd28927 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_visibility_off.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/ic_visibility_white.xml b/mailbox-android/src/main/res/drawable/ic_visibility_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..c64e5d7a12af86504a13eb2ca511a2bf37870381 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/ic_visibility_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/introduction_white.xml b/mailbox-android/src/main/res/drawable/introduction_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..ac4328d1210e12e8446ff705716d5364a3532887 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/introduction_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M21,8V7l-3,2 -3,-2v1l3,2 3,-2zm1,-5H2C0.9,3 0,3.9 0,5v14c0,1.1 0.9,2 2,2h20c1.1,0 1.99,-0.9 1.99,-2L24,5c0,-1.1 -0.9,-2 -2,-2zM8,6c1.66,0 3,1.34 3,3s-1.34,3 -3,3 -3,-1.34 -3,-3 1.34,-3 3,-3zm6,12H2v-1c0,-2 4,-3.1 6,-3.1s6,1.1 6,3.1v1zm8,-6h-8V6h8v6z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/level_indicator_circle.xml b/mailbox-android/src/main/res/drawable/level_indicator_circle.xml new file mode 100644 index 0000000000000000000000000000000000000000..4b486911196b8d561ba9eee8fdf2538818f85037 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/level_indicator_circle.xml @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="utf-8"?> +<shape + xmlns:android="http://schemas.android.com/apk/res/android" + android:shape="oval"> + + <solid android:color="@color/window_background"/> + + <stroke + android:width="2dp" + android:color="@color/thread_indicator"/> +</shape> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/list_item_thread_background.xml b/mailbox-android/src/main/res/drawable/list_item_thread_background.xml new file mode 100644 index 0000000000000000000000000000000000000000..4d71497abdf9488bb45749d683a4f712f3f55876 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/list_item_thread_background.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<selector + xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:drawable="@color/thread_item_background" + android:state_activated="false"/> + + <item + android:drawable="@color/thread_item_highlight" + android:state_activated="true"/> + +</selector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/message_delivered.xml b/mailbox-android/src/main/res/drawable/message_delivered.xml new file mode 100644 index 0000000000000000000000000000000000000000..88535626864df7f3c7d0363e126009f441b1bb48 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/message_delivered.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zm4.24,-1.41L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/message_delivered_white.xml b/mailbox-android/src/main/res/drawable/message_delivered_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..720dab1f7101da2f5d3d04724491c6829680eadb --- /dev/null +++ b/mailbox-android/src/main/res/drawable/message_delivered_white.xml @@ -0,0 +1,5 @@ +<vector android:height="16dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFFFF" android:pathData="M18,7l-1.41,-1.41 -6.34,6.34 1.41,1.41L18,7zm4.24,-1.41L11.66,16.17 7.48,12l-1.41,1.41L11.66,19l12,-12 -1.42,-1.41zM0.41,13.41L6,19l1.41,-1.41L1.83,12 0.41,13.41z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/message_sent.xml b/mailbox-android/src/main/res/drawable/message_sent.xml new file mode 100644 index 0000000000000000000000000000000000000000..a205b178da5f71ddb6c7f3b19c001f7d74636841 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/message_sent.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/message_sent_white.xml b/mailbox-android/src/main/res/drawable/message_sent_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..59e6d6d1dde7e9662152b1ae9dc6b3b7ac6bef80 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/message_sent_white.xml @@ -0,0 +1,5 @@ +<vector android:height="16dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillColor="#FFFFFFFF" android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/message_stored.xml b/mailbox-android/src/main/res/drawable/message_stored.xml new file mode 100644 index 0000000000000000000000000000000000000000..f1a1a31b0876b4a4b0e07c0f27372c29d72cef8c --- /dev/null +++ b/mailbox-android/src/main/res/drawable/message_stored.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="16dp" + android:height="16dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/message_stored_white.xml b/mailbox-android/src/main/res/drawable/message_stored_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..71ee22feaa013232c6218686372d94c418ebcace --- /dev/null +++ b/mailbox-android/src/main/res/drawable/message_stored_white.xml @@ -0,0 +1,5 @@ +<vector android:height="16dp" + android:viewportHeight="24.0" android:viewportWidth="24.0" + android:width="16dp" xmlns:android="http://schemas.android.com/apk/res/android"> + <path android:fillAlpha=".9" android:fillColor="#FFFFFF" android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/navigation_drawer_header.xml b/mailbox-android/src/main/res/drawable/navigation_drawer_header.xml new file mode 100644 index 0000000000000000000000000000000000000000..9771b2caef3627e384db232143b048e1a7076219 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/navigation_drawer_header.xml @@ -0,0 +1,129 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="138dp" + android:height="50dp" + android:viewportHeight="50" + android:viewportWidth="138"> + + <path + android:fillColor="#87c214" + android:pathData="M230.022,214.001 L239.302,214.001 L239.302,223.281 L230.022,223.281 +L230.022,214.001 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M235.543,203.979 C237.603,203.979,239.301,205.678,239.301,207.737 +L239.301,250.141 C239.301,252.201,237.602,253.899,235.543,253.899 +L233.781,253.899 C231.721,253.899,230.023,252.2,230.023,250.141 L230.023,207.737 +C230.023,205.677,231.7,203.979,233.76,203.979 L235.544,203.979 M235.544,202.493 +L233.782,202.493 C230.873,202.493,228.537,204.85,228.537,207.738 +L228.537,250.142 C228.537,253.03,230.894,255.387,233.782,255.387 +L235.544,255.387 C238.432,255.387,240.789,253.03,240.789,250.142 +L240.789,207.738 C240.768,204.85,238.432,202.493,235.544,202.493 +L235.544,202.493 Z"/> + <path + android:fillColor="#87c214" + android:pathData="M250.619,234.598 L259.899,234.598 L259.899,243.878 L250.619,243.878 +L250.619,234.598 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M256.14,203.979 C258.2,203.979,259.898,205.678,259.898,207.737 L259.898,250.141 +C259.898,252.201,258.221,253.899,256.14,253.899 L254.378,253.899 +C252.318,253.899,250.62,252.2,250.62,250.141 L250.62,207.737 +C250.62,205.677,252.319,203.979,254.378,203.979 L256.14,203.979 M256.14,202.493 +L254.378,202.493 C251.49,202.493,249.133,204.85,249.133,207.738 L249.133,250.142 +C249.133,253.03,251.49,255.387,254.378,255.387 L256.14,255.387 +C259.028,255.387,261.385,253.03,261.385,250.142 L261.385,207.738 +C261.364,204.85,259.028,202.493,256.14,202.493 L256.14,202.493 Z"/> + <path + android:fillColor="#95d220" + android:pathData="M230.022,234.598 L239.302,234.598 L239.302,243.878 L230.022,243.878 +L230.022,234.598 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M266.142,234.598 C268.202,234.598,269.9,236.275,269.9,238.356 L269.9,240.118 +C269.9,242.178,268.201,243.876,266.142,243.876 L223.759,243.876 +C221.699,243.876,220.001,242.177,220.001,240.118 L220.001,238.356 +C220.001,236.296,221.678,234.598,223.759,234.598 L266.142,234.598 +M266.142,233.112 L223.759,233.112 +C220.871,233.112,218.514,235.448,218.514,238.336 L218.514,240.098 +C218.514,242.986,220.871,245.343,223.759,245.343 L266.163,245.343 +C269.051,245.343,271.408,242.986,271.408,240.098 L271.408,238.336 +C271.387,235.448,269.03,233.112,266.142,233.112 L266.142,233.112 Z"/> + <path + android:fillColor="#95d220" + android:pathData="M250.619,214.001 L259.899,214.001 L259.899,223.281 L250.619,223.281 +L250.619,214.001 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M266.142,214.001 C268.202,214.001,269.9,215.678,269.9,217.759 L269.9,219.521 +C269.9,221.581,268.201,223.279,266.142,223.279 L223.759,223.279 +C221.677,223.259,220,221.582,220,219.522 L220,217.76 +C220,215.7,221.677,214.002,223.758,214.002 L266.141,214.002 M266.141,212.516 +L223.758,212.516 C220.87,212.516,218.513,214.852,218.513,217.74 L218.513,219.502 +C218.513,222.39,220.87,224.747,223.758,224.747 L266.162,224.747 +C269.05,224.747,271.407,222.39,271.407,219.502 L271.407,217.74 +C271.386,214.852,269.029,212.516,266.141,212.516 L266.141,212.516 Z"/> + <path + android:fillColor="#ffffff" + android:pathData="M57.0977,30.6992 L57.0977,50 L66.1661,50 +C70.3917,50,72.641,47.8764,72.6622,44.5215 +C72.6622,42.3769,71.6445,40.7415,69.6485,39.8496 L69.6485,39.8086 +C71.1561,38.8743,71.8145,37.7053,71.8145,35.9004 +C71.8145,33.1612,69.9664,30.6992,65.9532,30.6992 L57.0977,30.6992 Z +M75.8477,30.6992 L75.8477,50 L78.1192,50 L78.1192,42.2715 L77.8868,42.0371 +L82.4727,42.0371 C85.0208,42.0371,86.4857,42.951,87.42,44.9258 L89.8203,50 +L92.3672,50 L89.4375,43.9688 C88.8005,42.6098,87.8232,41.7611,86.9102,41.3789 +L86.9102,41.3359 C89.0336,40.7838,90.7754,38.9155,90.7754,36.3886 +C90.7754,32.5877,87.8237,30.6991,84.2988,30.6991 L75.8476,30.6991 Z +M94.7247,30.6992 L94.7247,50 L96.9981,50 L96.9981,30.6992 L94.7247,30.6992 Z +M108.145,30.6992 L99.6504,50 L102.135,50 L104.174,45.3496 L104.047,45.1152 +L114.621,45.1152 L114.494,45.3496 L116.533,50 L119.017,50 L110.544,30.6992 +L108.144,30.6992 Z M121.481,30.6992 L121.481,50 L123.752,50 L123.752,42.2715 +L123.518,42.0371 L128.106,42.0371 +C130.654,42.0371,132.119,42.951,133.053,44.9258 L135.451,50 L138,50 +L135.07,43.9688 C134.433,42.6098,133.456,41.7611,132.543,41.3789 +L132.543,41.3359 C134.666,40.7838,136.408,38.9155,136.408,36.3886 +C136.408,32.5877,133.456,30.6991,129.931,30.6991 L121.48,30.6991 Z +M59.1587,32.8437 L65.7192,32.8437 C68.1823,32.8437,69.479,33.8404,69.479,35.8789 +C69.479,37.5989,68.4797,38.9805,65.7192,38.9805 L59.1587,38.9805 +L59.3931,38.7461 L59.3931,33.0762 L59.1587,32.8438 Z M77.8872,32.8437 +L84.3208,32.8437 C86.699,32.8437,88.4617,33.819,88.4829,36.3671 +C88.4829,38.4056,86.8485,39.871,84.0669,39.871 L77.8872,39.871 L78.1196,39.6386 +L78.1196,33.0761 L77.8872,32.8437 Z M123.54,32.8437 L129.974,32.8437 +C132.331,32.8437,134.115,33.819,134.115,36.3671 +C134.115,38.4056,132.502,39.871,129.72,39.871 L123.54,39.871 L123.774,39.6386 +L123.774,33.0761 L123.54,32.8437 Z M109.335,33.0546 L109.378,33.0546 +L109.95,34.9882 L113.348,42.7382 L113.58,42.9706 L105.109,42.9706 +L105.341,42.7382 L108.739,34.9882 L109.333,33.0546 Z M59.1377,41.1249 +L66.1455,41.1249 C68.9909,41.1249,70.3076,42.4402,70.3076,44.4999 +C70.3076,46.6445,69.1185,47.8339,66.167,47.8339 L59.1377,47.8339 +L59.3721,47.5995 L59.3721,41.3573 L59.1377,41.1249 Z"/> + <path + android:fillColor="#87c214" + android:pathData="M13.8086,0.00000870181 C11.7448,0.00000870181,10.0425,1.70197,10.0425,3.7658 +L10.0425,8.55303 L19.3194,8.55303 L19.3194,3.7658 +C19.3194,1.70197,17.6382,0.00001,15.5744,0.00001 L13.8087,0.00001 Z +M34.4469,0.00000870181 C32.3831,0.00000870181,30.6807,1.70197,30.6807,3.7658 +L30.6807,29.1913 L39.9576,29.1913 L39.9576,3.7658 +C39.9576,1.70197,38.2764,0.00001,36.2126,0.00001 L34.4469,0.00001 Z +M10.0425,20.8087 L10.0425,46.2341 +C10.0425,48.2979,11.7236,49.9999,13.8087,49.9999 L15.5744,49.9999 +C17.6382,49.9999,19.3406,48.2979,19.3406,46.2341 L19.3406,20.8086 +L10.0425,20.8086 Z M30.6808,41.4469 L30.6808,46.2341 +C30.6808,48.2979,32.3832,49.9999,34.447,49.9999 L36.2127,49.9999 +C38.2765,49.9999,39.9789,48.2979,39.9789,46.2341 L39.9789,41.4469 +L30.6808,41.4469 Z"/> + <path + android:fillColor="#95d220" + android:pathData="M3.76588,10.0424 C1.70205,10.0424,0.00014,11.7235,0.00014,13.8086 +L0.00014,15.5743 C0.00014,17.6381,1.68077,19.3405,3.76588,19.3405 +L29.1914,19.3405 L29.1914,10.0424 L3.7659,10.0424 Z M41.4471,10.0424 +L41.4471,19.3405 L46.2343,19.3405 +C48.2981,19.3405,50.0001,17.6594,50.0001,15.5743 L50.0001,13.8086 +C50.0001,11.7235,48.2981,10.0424,46.2343,10.0424 L41.4471,10.0424 Z +M3.76579,30.6807 C1.70205,30.6807,-0.00008,32.3618,-0.00008,34.4469 +L-0.00008,36.2126 C-0.00008,38.2764,1.68056,39.9788,3.76567,39.9788 +L8.5529,39.9788 L8.5529,30.6807 L3.76567,30.6807 Z M20.8087,30.6807 +L20.8087,39.9788 L46.2342,39.9788 C48.298,39.9788,50,38.2764,50,36.2126 +L50,34.4469 C50,32.3618,48.298,30.6807,46.2342,30.6807 L20.8087,30.6807 Z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_blog.xml b/mailbox-android/src/main/res/drawable/notification_blog.xml new file mode 100644 index 0000000000000000000000000000000000000000..d5218f632940886038a1de4cf218d0634d054e88 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_blog.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M13,12h7v1.5h-7zM13,9.5h7L20,11h-7zM13,14.5h7L20,16h-7zM21,4L3,4c-1.1,0 -2,0.9 -2,2v13c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,6c0,-1.1 -0.9,-2 -2,-2zM21,19h-9L12,6h9v13z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_forum.xml b/mailbox-android/src/main/res/drawable/notification_forum.xml new file mode 100644 index 0000000000000000000000000000000000000000..5bd071e1d601d69783a719b0289cc30665b4aee9 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_forum.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M21,6h-2v9L6,15v2c0,0.55 0.45,1 1,1h11l4,4L22,7c0,-0.55 -0.45,-1 -1,-1zM17,12L17,3c0,-0.55 -0.45,-1 -1,-1L3,2c-0.55,0 -1,0.45 -1,1v14l4,-4h10c0.55,0 1,-0.45 1,-1z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_introduction.xml b/mailbox-android/src/main/res/drawable/notification_introduction.xml new file mode 100644 index 0000000000000000000000000000000000000000..48c8339f02f2a2b0ace16a40fe25807dc188f89e --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_introduction.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M20,0L4,0v2h16L20,0zM4,24h16v-2L4,22v2zM20,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2zM12,6.75c1.24,0 2.25,1.01 2.25,2.25s-1.01,2.25 -2.25,2.25S9.75,10.24 9.75,9 10.76,6.75 12,6.75zM17,17L7,17v-1.5c0,-1.67 3.33,-2.5 5,-2.5s5,0.83 5,2.5L17,17z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_ongoing.xml b/mailbox-android/src/main/res/drawable/notification_ongoing.xml new file mode 100644 index 0000000000000000000000000000000000000000..56ca2a343839c45b6a39f8094606719f0870fd0a --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_ongoing.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="329.40625" + android:viewportWidth="329.40625"> + <path + android:fillColor="#ffffff" + android:pathData="M164.7,0C73.7,0 0,73.7 0,164.7c0,91 73.7,164.7 164.7,164.7 91,0 164.7,-73.8 164.7,-164.7 0,-91 -73.8,-164.7 -164.7,-164.7zM111.7,46.6 L120,46.6c9.7,0 17.7,8 17.7,17.7l0,22.5 -43.7,0 0,-22.5c0,-9.7 8,-17.7 17.7,-17.7zM208.7,46.6 L217,46.6c9.8,0 17.7,8 17.7,17.7l0,119.5 -43.7,0 0,-119.5c0,-9.7 8,-17.7 17.7,-17.7zM64.5,93.8l119.5,0 0,43.7 -119.5,0c-9.8,0 -17.7,-8 -17.7,-17.7l0,-8.3c0,-9.8 8,-17.7 17.7,-17.7zM241.7,93.8 L264.2,93.8c9.7,0 17.6,8 17.7,17.7l0,8.3c0,9.8 -8,17.7 -17.7,17.7l-22.5,0 0,-43.7zM94,144.5l43.7,0 0,119.5c0,9.7 -8,17.7 -17.7,17.7l-8.3,0c-9.8,0 -17.7,-8 -17.7,-17.7l0,-119.5zM64.5,190.8 L87,190.8 87,234.5 64.5,234.5c-9.8,0 -17.7,-8 -17.7,-17.7l0,-8.3c0,-9.8 8,-17.7 17.7,-17.7zM144.7,190.8 L264.2,190.8c9.7,0 17.6,8 17.7,17.7l0,8.3c0,9.7 -8,17.7 -17.7,17.7l-119.5,0 0,-43.7zM191,241.5 L234.7,241.5 234.7,264c0,9.7 -8,17.7 -17.7,17.7l-8.3,0c-9.7,0 -17.7,-8 -17.7,-17.7l0,-22.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_private_group.xml b/mailbox-android/src/main/res/drawable/notification_private_group.xml new file mode 100644 index 0000000000000000000000000000000000000000..55cbde9eeed748f9685ba6738ad3df16f158cfc5 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_private_group.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M16,11c1.66,0 2.99,-1.34 2.99,-3S17.66,5 16,5c-1.66,0 -3,1.34 -3,3s1.34,3 3,3zM8,11c1.66,0 2.99,-1.34 2.99,-3S9.66,5 8,5C6.34,5 5,6.34 5,8s1.34,3 3,3zM8,13c-2.33,0 -7,1.17 -7,3.5L1,19h14v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5zM16,13c-0.29,0 -0.62,0.02 -0.97,0.05 1.16,0.84 1.97,1.97 1.97,3.45L17,19h6v-2.5c0,-2.33 -4.67,-3.5 -7,-3.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_private_message.xml b/mailbox-android/src/main/res/drawable/notification_private_message.xml new file mode 100644 index 0000000000000000000000000000000000000000..b42ea09e2211e8f48e8e950c8918509095b0c260 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_private_message.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/notification_reminder.xml b/mailbox-android/src/main/res/drawable/notification_reminder.xml new file mode 100644 index 0000000000000000000000000000000000000000..1defd7d3d3583a62e15eac36d7e8066b389903da --- /dev/null +++ b/mailbox-android/src/main/res/drawable/notification_reminder.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24" + android:viewportWidth="24"> + <path + android:fillColor="#ffffff" + android:pathData="M 12 0 A 12 12 0 0 0 4.875 2.3613281 L 6.9316406 4.4160156 C 7.0875805 3.8805807 7.5639651 3.4765625 8.1464844 3.4765625 L 8.7480469 3.4765625 C 9.4535014 3.4765625 10.035156 4.0582174 10.035156 4.7636719 L 10.035156 6.4003906 L 8.9140625 6.4003906 L 9.4238281 6.9101562 L 13.404297 6.9101562 L 13.404297 10.085938 L 12.601562 10.085938 L 13.914062 11.398438 L 13.914062 4.7636719 C 13.914062 4.0582174 14.495717 3.4765625 15.201172 3.4765625 L 15.802734 3.4765625 C 16.515461 3.4765625 17.089844 4.0582174 17.089844 4.7636719 L 17.089844 13.455078 L 15.96875 13.455078 L 16.478516 13.964844 L 19.236328 13.964844 C 19.941782 13.964844 20.516165 14.546498 20.523438 15.251953 L 20.523438 15.853516 C 20.523438 16.436036 20.119418 16.91242 19.583984 17.068359 L 21.638672 19.125 A 12 12 0 0 0 24 12 A 12 12 0 0 0 12 0 z M 1.2617188 1.3632812 L 0 2.6269531 L 2.3125 4.9472656 A 12 12 0 0 0 0 12 A 12 12 0 0 0 12 24 A 12 12 0 0 0 19.027344 21.707031 L 21.314453 24 L 22.576172 22.734375 L 2.7519531 2.8554688 L 1.9863281 2.0898438 L 1.2617188 1.3632812 z M 17.599609 6.9101562 L 19.236328 6.9101562 C 19.941782 6.9101562 20.516165 7.4918111 20.523438 8.1972656 L 20.523438 8.7988281 C 20.523438 9.511555 19.941782 10.085937 19.236328 10.085938 L 17.599609 10.085938 L 17.599609 6.9101562 z M 4.359375 6.9980469 L 7.4394531 10.085938 L 4.7128906 10.085938 C 4.0001632 10.085938 3.4257813 9.504282 3.4257812 8.7988281 L 3.4257812 8.1972656 C 3.4257812 7.6133228 3.8294199 7.1540656 4.359375 6.9980469 z M 6.859375 10.595703 L 7.9472656 10.595703 L 10.035156 12.689453 L 10.035156 19.287109 C 10.035156 19.992562 9.4535014 20.574219 8.7480469 20.574219 L 8.1464844 20.574219 C 7.4337573 20.574219 6.859375 19.992563 6.859375 19.287109 L 6.859375 10.595703 z M 4.7128906 13.964844 L 6.3496094 13.964844 L 6.3496094 17.140625 L 4.7128906 17.140625 C 4.0001632 17.140625 3.4257813 16.558971 3.4257812 15.853516 L 3.4257812 15.251953 C 3.4257812 14.539226 4.007436 13.964844 4.7128906 13.964844 z M 10.544922 13.964844 L 11.306641 13.964844 L 14.474609 17.140625 L 10.544922 17.140625 L 10.544922 13.964844 z M 13.914062 17.650391 L 14.982422 17.650391 L 16.992188 19.666016 C 16.827053 20.182975 16.371277 20.574219 15.802734 20.574219 L 15.201172 20.574219 C 14.495717 20.574219 13.914063 19.992563 13.914062 19.287109 L 13.914062 17.650391 z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/qr_code_explanation.xml b/mailbox-android/src/main/res/drawable/qr_code_explanation.xml new file mode 100644 index 0000000000000000000000000000000000000000..157025c222f4979db4f70804cf72b8e199a4e17c --- /dev/null +++ b/mailbox-android/src/main/res/drawable/qr_code_explanation.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="400dp" + android:height="100dp" + android:viewportHeight="49.5" + android:viewportWidth="194.8"> + <path + android:fillColor="#000000" + android:pathData="M30.1 16.5l-9 0 0 -5c0 -2.4 -2 -4.4 -4.4 -4.4L4.4 7.1C2 7.1 0 9.1 0 11.5l0 24.2c0 2.4 2 4.4 4.4 4.4l9 0 0 5c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.2c0.1 -2.4 -1.9 -4.4 -4.3 -4.4zm-27.4 16.1l0 -20.9 15.8 0 0 20.9 -15.8 0zm10.7 4.6l-5.8 0 0 -1.5 5.8 0 0 1.5zm13.5 9.4l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -1.9 0.5 0c2.4 0 4.4 -2 4.4 -4.4l0 -14.6 10.8 0 0 20.9z"/> + <path + android:fillColor="#000000" + android:pathData="M101.2 16.5l-8.3 0 0 -4.4c0 -1.4 -1.2 -2.6 -2.6 -2.6l-3.9 0 -2.1 -2.5 -6.9 0 -2.2 2.5 -3.8 0c-1.4 0 -2.6 1.2 -2.6 2.6l0 13.3c0 1.4 1.2 2.6 2.6 2.6l13.1 0 0 17.2c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.3c0.2 -2.4 -1.8 -4.4 -4.3 -4.4zm-26.4 2.4c0 -3.3 2.7 -6 6 -6 3.3 0 6 2.7 6 6 0 3.3 -2.7 6 -6 6 -3.3 0 -6 -2.7 -6 -6zm23.2 27.7l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -14.1 3.1 0c1.4 0 2.6 -1.2 2.6 -2.6l0 -4.2 10.1 0 0 20.9z"/> + <path + android:fillColor="#000000" + android:pathData="M84.600003 18.9a3.8 3.8 0 0 1 -3.8 3.8 3.8 3.8 0 0 1 -3.8 -3.8 3.8 3.8 0 0 1 3.8 -3.8 3.8 3.8 0 0 1 3.8 3.8z"/> + <path + android:fillColor="#000000" + android:pathData="M175.3 16.5l-9.8 0 0 -5.7c0 -1.4 -1.2 -2.6 -2.6 -2.6l-19.3 0c-1.4 0 -2.6 1.2 -2.6 2.6l0 14.4c0 1.4 1.2 2.6 2.6 2.6l15.1 0 0 17.3c0 2.4 2 4.4 4.4 4.4l12.2 0c2.4 0 4.4 -2 4.4 -4.4l0 -24.2c0.1 -2.4 -1.9 -4.4 -4.4 -4.4zm-12.4 -5.9l-9.6 6 -9.6 -6 19.2 0zm-19.4 14.8l0 -12.3 9.8 6.1 9.8 -6.1 0 12.3 -19.6 0zm28.6 21.2l-5.8 0 0 -1.5 5.8 0 0 1.5zm5 -4.6l-15.8 0 0 -14.2 1.6 0c1.4 0 2.6 -1.2 2.6 -2.6l0 -4.1 11.6 0 0 20.9z"/> + <path + android:fillColor="#ff0000" + android:pathData="M101.4 17.8l2 2 7.4 -7.3 7.3 7.3 2.1 -2 -7.4 -7.4 7.4 -7.3 -2.1 -2.1 -7.3 7.4 -7.4 -7.4 -2 2.1 7.3 7.3z"/> + <path + android:fillColor="#ff0000" + android:pathData="M176 17.8l2.1 2 7.3 -7.3 7.4 7.3 2 -2 -7.3 -7.4 7.3 -7.3 -2 -2.1 -7.4 7.4 -7.3 -7.4 -2.1 2.1 7.3 7.3z"/> + <path + android:fillColor="#08b124" + android:pathData="M35.8 18.8l0 0L52.5 2.1 50.5 0 35.6 14.8 28.5 7.7l-2.1 2.1 9.2 9.1z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/qr_code_intro.xml b/mailbox-android/src/main/res/drawable/qr_code_intro.xml new file mode 100644 index 0000000000000000000000000000000000000000..6b09db32dd279901f6b64b19a03ecff6bf58beb6 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/qr_code_intro.xml @@ -0,0 +1,24 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="409dp" + android:height="162dp" + android:viewportHeight="161.7" + android:viewportWidth="409.2"> + <path + android:fillColor="#FF000000" + android:pathData="M369.8,157.4l-4.3,-4.3l-7.1,-2.4c-3.9,-1.3 -8.7,-3 -10.7,-3.7l-3.7,-1.3l3.5,-0.2c8.2,-0.4 13,-4 14.3,-10.9c0.8,-4.1 1.1,-17.3 0.8,-33c-0.2,-8.1 -0.2,-15.4 0,-16.3c0.1,-0.9 0.5,-2.4 0.9,-3.4c1.2,-3.5 0.3,-11.9 -1.9,-17.6c-0.3,-0.9 -1.9,-4.2 -3.5,-7.4c-4.2,-8.2 -4.5,-8.9 -4.9,-10.5c-0.5,-1.8 -0.2,-5.4 0.5,-6.8c0.7,-1.3 2.2,-2.9 3.2,-3.5c1.3,-0.7 2.6,0.1 4.7,2.9c3.4,4.5 14,19.4 15.7,22.2c3.7,6 6,11.2 8,18.8c0.7,2.5 1.9,7 2.7,10.1c0.8,3.1 2.7,10.2 4.1,15.8l2.6,10.2l4.6,5.2c2.6,2.9 5.8,6.5 7.2,8c1.4,1.6 2.5,3 2.5,3.2c0,0.3 -34.5,29.3 -34.9,29.3C374.2,161.7 372.2,159.7 369.8,157.4zM275.9,141c-1.3,-0.6 -2.2,-1.4 -2.9,-2.3c-2.1,-2.7 -2,2.4 -1.9,-68.5l0.1,-64l0.7,-1.2c1,-1.9 2,-2.9 3.7,-3.9l1.6,-0.9l37.8,-0.1c42.5,-0.1 39.4,-0.2 42.1,2.2c0.9,0.8 1.8,2 2.2,2.9c0.7,1.6 0.7,1.6 0.8,14.2l0.1,12.6l-1.8,-0.1c-1.4,-0.1 -2.1,0 -3.2,0.5c-2,1 -3.9,2.9 -5.1,5.1l-1,2l0,-12.8l0,-12.8h-33.6h-33.6v51.3v51.3h33.6h33.6l0.1,-34.4c0.1,-33 0.1,-34.4 0.6,-32.9c0.3,0.8 1.8,4 3.4,7c5.5,10.6 5.4,9.9 5.4,47.2c0,27.6 -0.1,30 -1.7,33.1c-1.1,2.2 -2.7,3.7 -5.1,4.7l-1.7,0.7L314,141.8l-36.2,0.1L275.9,141L275.9,141zM318.3,135.9c2.9,-1.3 4.5,-3.7 4.4,-6.6c0,-4.1 -3.1,-7.2 -7.1,-7.2c-2.1,0 -3.6,0.6 -5.2,2.2c-2.2,2.2 -2.8,5.4 -1.3,8.3c0.7,1.4 2.5,3 4,3.5C314.6,136.6 317,136.6 318.3,135.9z"/> + <path + android:fillColor="#FF000000" + android:pathData="M39.4,157.4l4.3,-4.3l7.1,-2.4c3.9,-1.3 8.7,-3 10.7,-3.7l3.7,-1.3l-3.5,-0.2c-8.2,-0.4 -13,-4 -14.3,-10.9c-0.8,-4.1 -1.1,-17.3 -0.8,-33c0.2,-8.1 0.2,-15.4 0,-16.3c-0.1,-0.9 -0.5,-2.4 -0.9,-3.4c-1.2,-3.5 -0.3,-11.9 1.9,-17.6c0.3,-0.9 1.9,-4.2 3.5,-7.4c4.2,-8.2 4.5,-8.9 4.9,-10.5c0.5,-1.8 0.2,-5.4 -0.5,-6.8c-0.7,-1.3 -2.2,-2.9 -3.2,-3.5c-1.3,-0.7 -2.6,0.1 -4.7,2.9c-3.4,4.5 -14,19.4 -15.7,22.2c-3.7,6 -6,11.2 -8,18.8c-0.7,2.5 -1.9,7 -2.7,10.1c-0.8,3.1 -2.7,10.2 -4.1,15.8l-2.6,10.2l-4.6,5.2c-2.6,2.9 -5.8,6.5 -7.2,8s-2.5,3 -2.5,3.2c0,0.3 34.5,29.3 34.9,29.3C35,161.7 37.1,159.7 39.4,157.4zM133.3,141c1.3,-0.6 2.2,-1.4 2.9,-2.3c2.1,-2.7 2,2.4 1.9,-68.5l-0.1,-64l-0.7,-1.2c-1,-1.9 -2,-2.9 -3.7,-3.9l-1.6,-0.9l-37.8,-0.1c-42.5,-0.1 -39.4,-0.2 -42.1,2.2c-0.9,0.8 -1.8,2 -2.2,2.9c-0.7,1.6 -0.7,1.6 -0.8,14.2L49,32l1.8,-0.1c1.4,-0.1 2.1,0 3.2,0.5c2,1 3.9,2.9 5.1,5.1l1,2l0,-12.8l0,-12.8h33.6h33.6v51.3v51.3L93.8,116.5L60.2,116.5l-0.1,-34.4c-0.1,-33 -0.1,-34.4 -0.6,-32.9c-0.3,0.8 -1.8,4 -3.4,7c-5.5,10.6 -5.4,9.9 -5.4,47.2c0,27.6 0.1,30 1.7,33.1c1.1,2.2 2.7,3.7 5.1,4.7l1.7,0.7l36.2,0.1l36.2,0.1L133.3,141L133.3,141zM90.9,135.9c-2.9,-1.3 -4.5,-3.7 -4.4,-6.6c0,-4.1 3.1,-7.2 7.1,-7.2c2.1,0 3.6,0.6 5.2,2.2c2.2,2.2 2.8,5.4 1.3,8.3c-0.7,1.4 -2.5,3 -4,3.5C94.6,136.6 92.3,136.6 90.9,135.9z"/> + <path + android:fillColor="#FF000000" + android:pathData="M80.5,63h2.3v2.3h2.3v2.3L73.6,67.6v-2.3h2.3v-4.6h4.6L80.5,63L80.5,63zM110.5,83.8h2.3v-2.3h-2.3L110.5,83.8zM82.8,63h2.3v-2.3h-2.3L82.8,63zM115.1,83.8h2.3v-2.3h-2.3L115.1,83.8zM87.4,86.1L92,86.1L92,83.8h-4.6L87.4,86.1zM108.2,86.1L108.2,83.8h-2.3v2.3L108.2,86.1zM99,86.1h2.3v-4.6L99,81.5L99,86.1zM80.5,56.1v2.3h6.9v-2.3L80.5,56.1zM78.2,58.4v-2.3h-4.6v4.6h2.3v-2.3L78.2,58.4zM85.1,53.8L69,53.8v-16.1h16.1L85.1,53.8zM82.8,40L71.3,40v11.5h11.5L82.8,40zM73.6,81.5h6.9v-6.9h-6.9L73.6,81.5zM96.6,79.1v2.3L99,81.4v-2.3L96.6,79.1zM80.5,42.3h-6.9v6.9h6.9L80.5,42.3zM117.4,37.7L117.4,53.8L101.3,53.8v-16.1L117.4,37.7zM115.1,40L103.6,40v11.5h11.5L115.1,40zM69,69.9h16.1v16.1L69,86L69,69.9zM71.3,83.8h11.5v-11.5L71.3,72.3L71.3,83.8zM71.3,56.1L69,56.1v11.5h2.3L71.3,56.1zM101.3,67.6v2.3h2.3v-2.3L101.3,67.6zM94.3,76.9v-2.3L92,74.6v2.3h-4.6v4.6L92,81.5v2.3h2.3v-4.6h2.3v-2.3L94.3,76.9zM87.4,46.9L92,46.9v-2.3h-4.6L87.4,46.9zM105.9,65.3h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3L105.9,65.3zM108.2,72.2h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9L108.2,72.2zM108.2,72.2h2.3v-4.6h-2.3L108.2,72.2zM89.7,72.2v-2.3L92,69.9v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3L99,58.4v-9.2h-2.3v-4.6L99,44.6v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3L92,40v4.6h2.3v6.9h2.3v2.3h-2.3v4.6L92,58.4L92,53.8h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6L92,60.7v4.6h-2.3v2.3h-2.3v6.9L92,74.5v-2.3L89.7,72.2zM115.1,74.5v-2.3h-4.6v2.3L115.1,74.5zM112.8,42.3h-6.9v6.9h6.9L112.8,42.3zM94.3,72.2L99,72.2v-2.3h-2.3v-2.3h-2.3L94.4,72.2zM99,67.6v-2.3h-2.3v2.3L99,67.6zM112.8,58.4h4.6v-2.3h-4.6L112.8,58.4zM115.1,76.9h2.3v-2.3h-2.3L115.1,76.9zM115.1,63h2.3v-2.3h-2.3L115.1,63zM94.3,51.5L92,51.5v2.3h2.3L94.3,51.5zM94.3,51.5"/> + <path + android:fillColor="#FF000000" + android:pathData="M303.5,63h2.3v2.3h2.3v2.3h-11.5v-2.3h2.3v-4.6h4.6L303.5,63L303.5,63zM333.5,83.8h2.3v-2.3h-2.3L333.5,83.8zM305.8,63h2.3v-2.3h-2.3L305.8,63zM338.1,83.8h2.3v-2.3h-2.3L338.1,83.8zM310.4,86.1h4.6L315,83.8h-4.6L310.4,86.1zM331.2,86.1L331.2,83.8h-2.3v2.3L331.2,86.1zM322,86.1h2.3v-4.6L322,81.5L322,86.1zM303.5,56.1v2.3h6.9v-2.3L303.5,56.1zM301.2,58.4v-2.3h-4.6v4.6h2.3v-2.3L301.2,58.4zM308.1,53.8L292,53.8v-16.1h16.1L308.1,53.8zM305.8,40h-11.5v11.5h11.5L305.8,40zM296.6,81.5h6.9v-6.9h-6.9L296.6,81.5zM319.6,79.1v2.3h2.3v-2.3L319.6,79.1zM303.5,42.3h-6.9v6.9h6.9L303.5,42.3zM340.4,37.7L340.4,53.8h-16.1v-16.1L340.4,37.7zM338.1,40h-11.5v11.5h11.5L338.1,40zM292,69.9h16.1v16.1L292,86L292,69.9zM294.3,83.8h11.5v-11.5h-11.5L294.3,83.8zM294.3,56.1L292,56.1v11.5h2.3L294.3,56.1zM324.3,67.6v2.3h2.3v-2.3L324.3,67.6zM317.3,76.9v-2.3L315,74.6v2.3h-4.6v4.6h4.6v2.3h2.3v-4.6h2.3v-2.3L317.3,76.9zM310.4,46.9h4.6v-2.3h-4.6L310.4,46.9zM328.9,65.3h4.6v2.3h2.3v-6.9h-2.3v-4.6h-2.3v6.9h-6.9v2.3h2.3v2.3h2.3L328.9,65.3zM331.2,72.2h-2.3v-2.3h-2.3v4.6h-6.9v2.3h4.6v4.6h2.3v2.3h2.3v-4.6h9.2v-2.3h-6.9L331.2,72.2zM331.2,72.2h2.3v-4.6h-2.3L331.2,72.2zM312.7,72.2v-2.3h2.3v-2.3h2.3v-2.3h2.3v-4.6h6.9v-4.6h-2.3v2.3L322,58.4v-9.2h-2.3v-4.6h2.3v-6.9h-2.3v4.6h-2.3v-4.6h-6.9v4.6h2.3v-2.3h2.3v4.6h2.3v6.9h2.3v2.3h-2.3v4.6L315,58.4L315,53.8h-2.3v-2.3h-2.3v4.6h2.3v2.3h-2.3v6.9h2.3v-4.6h2.3v4.6h-2.3v2.3h-2.3v6.9h4.6v-2.3L312.7,72.2zM338.1,74.5v-2.3h-4.6v2.3L338.1,74.5zM335.8,42.3h-6.9v6.9h6.9L335.8,42.3zM317.3,72.2h4.6v-2.3h-2.3v-2.3h-2.3L317.3,72.2zM322,67.6v-2.3h-2.3v2.3L322,67.6zM335.8,58.4h4.6v-2.3h-4.6L335.8,58.4zM338.1,76.9h2.3v-2.3h-2.3L338.1,76.9zM338.1,63h2.3v-2.3h-2.3L338.1,63zM317.3,51.5L315,51.5v2.3h2.3L317.3,51.5zM317.3,51.5"/> + <path + android:fillColor="#FF000000" + android:pathData="M179.6,48.9l-20.6,18l20.6,16.7v-5.2L199,78.4v-24.3h-19.3L179.7,48.9z"/> + <path + android:fillColor="#FF000000" + android:pathData="M229.4,83.7l20.6,-18l-20.6,-16.7v5.2L210,54.2v24.3h19.3L229.3,83.7z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/social_send_now_white.xml b/mailbox-android/src/main/res/drawable/social_send_now_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..9c8a759a345a0d6c101a4076e77d9d12c385d1d2 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/social_send_now_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="42dp" + android:height="42dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/social_share_white.xml b/mailbox-android/src/main/res/drawable/social_share_white.xml new file mode 100644 index 0000000000000000000000000000000000000000..a7bbe28605e06d56c0f84d04c906a9195ab20d5f --- /dev/null +++ b/mailbox-android/src/main/res/drawable/social_share_white.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FFFFFFFF" + android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/splash_screen.xml b/mailbox-android/src/main/res/drawable/splash_screen.xml new file mode 100644 index 0000000000000000000000000000000000000000..a8c8a1c36e2ff25c12ec56ec3ca999affdc35205 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/splash_screen.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="235dp" + android:height="310dp" + android:viewportHeight="310" + android:viewportWidth="235"> + + <path + android:fillColor="#87c214" + android:pathData="M47.2,47.2 L90.9,47.2 L90.9,90.9 L47.2,90.9 L47.2,47.2 Z"/> + <path + android:fillColor="#87c214" + android:pathData="M64.9004,0 C55.2004,0,47.1992,7.99922,47.1992,17.6992 L47.1992,40.1992 +L90.8008,40.1992 L90.8008,17.6992 +C90.8008,7.99922,82.8992,0,73.1992,0 L64.9004,0 Z M161.9,0 +C152.2,0,144.199,7.99922,144.199,17.6992 L144.199,137.199 L187.801,137.199 +L187.801,17.6992 C187.801,7.99922,179.899,0,170.199,0 L161.9,0 Z +M47.1992,97.8008 L47.1992,217.301 C47.1992,227.001,55.1004,235,64.9004,235 +L73.1992,235 C82.8992,235,90.9004,227.001,90.9004,217.301 L90.9004,97.8008 +L47.1992,97.8008 Z M144.199,194.801 L144.199,217.301 +C144.199,227.001,152.2,235,161.9,235 L170.199,235 +C179.899,235,187.9,227.001,187.9,217.301 L187.9,194.801 L144.199,194.801 Z"/> + <path + android:fillColor="#87c214" + android:pathData="M144.2,144.2 L187.9,144.2 L187.9,187.9 L144.2,187.9 L144.2,144.2 Z"/> + <path + android:fillColor="#95d220" + android:pathData="M17.6992,47.1992 C7.99922,47.1992,0,55.1004,0,64.9004 L0,73.1992 +C0,82.8992,7.89922,90.9004,17.6992,90.9004 L137.199,90.9004 L137.199,47.1992 +L17.6992,47.1992 Z M194.801,47.1992 L194.801,90.9004 L217.301,90.9004 +C227.001,90.9004,235,82.9992,235,73.1992 L235,64.9004 +C235,55.1004,227.001,47.1992,217.301,47.1992 L194.801,47.1992 Z M17.6992,144.199 +C7.99922,144.199,0,152.1,0,161.9 L0,170.199 +C0,179.899,7.89922,187.9,17.6992,187.9 L40.1992,187.9 L40.1992,144.199 +L17.6992,144.199 Z M97.8008,144.199 L97.8008,187.9 L217.301,187.9 +C227.001,187.9,235,179.899,235,170.199 L235,161.9 +C235,152.1,227.001,144.199,217.301,144.199 L97.8008,144.199 Z"/> + <path + android:fillColor="#000000" + android:pathData="M0,253.9 L0,310 L26.2656,310 C38.6498,310,45.1426,303.8,45.1426,294.1 +C45.1426,287.8,42.2457,283.1,36.4531,280.5 L36.4531,280.4 +C40.8475,277.7,42.7461,274.3,42.7461,269 C42.7461,261,37.2532,253.9,25.668,253.9 +L0,253.9 Z M54.5313,253.9 L54.5313,310 L61.1211,310 L61.1211,287.5 +L60.4238,286.801 L73.7051,286.801 +C81.0956,286.801,85.2917,289.399,87.9883,295.199 L94.9785,310 L102.369,310 +L94.0801,292.5 C92.2824,288.6,89.3857,286.1,86.7891,285 L86.7891,284.9 +C92.8813,283.3,97.9746,277.8,97.9746,270.5 +C97.9746,259.4,89.3865,253.9,79.0996,253.9 L54.5313,253.9 Z M109.26,253.9 +L109.26,310 L115.852,310 L115.852,253.9 L109.26,253.9 Z M148.012,253.9 +L123.342,310 L130.533,310 L136.525,296.5 L136.227,295.801 L166.887,295.801 +L166.588,296.5 L172.58,310 L179.771,310 L155.002,253.9 L148.012,253.9 Z +M187.16,253.9 L187.16,310 L193.752,310 L193.752,287.5 L193.053,286.801 +L206.336,286.801 C213.727,286.801,217.923,289.399,220.619,295.199 L227.609,310 +L235,310 L226.711,292.5 C224.913,288.6,222.017,286.1,219.42,285 L219.42,284.9 +C225.512,283.3,230.605,277.8,230.605,270.5 +C230.605,259.4,222.017,253.9,211.73,253.9 L187.16,253.9 Z M5.89258,260.1 +L24.9688,260.1 C32.1596,260.1,35.9531,263,35.9531,269 +C35.9531,274,32.9585,278,24.9688,278 L5.89258,278 L6.5918,277.301 +L6.5918,260.801 L5.89258,260.1 Z M60.4238,260.1 L79.0996,260.1 +C85.8909,260.1,91.0837,262.9,91.1836,270.4 +C91.1836,276.4,86.4901,280.6,78.4004,280.6 L60.4238,280.6 L61.1211,279.9 +L61.1211,260.801 L60.4238,260.1 Z M192.953,260.1 L211.629,260.1 +C218.52,260.1,223.715,262.9,223.715,270.4 +C223.715,276.4,219.021,280.6,210.932,280.6 L192.953,280.6 L193.652,279.9 +L193.652,260.801 L192.953,260.1 Z M151.605,260.801 L151.707,260.801 +L153.404,266.4 L163.291,288.9 L163.99,289.6 L139.322,289.6 L140.021,288.9 +L149.908,266.4 L151.605,260.801 Z M5.89258,284.199 L26.2656,284.199 +C34.555,284.199,38.3516,288,38.3516,294.1 +C38.3516,300.3,34.8547,303.801,26.2656,303.801 L5.89258,303.801 L6.5918,303.1 +L6.5918,284.9 L5.89258,284.199 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/startup_lock.xml b/mailbox-android/src/main/res/drawable/startup_lock.xml new file mode 100644 index 0000000000000000000000000000000000000000..181a64cc9162141b4bfe208b94a70bf94d4b31da --- /dev/null +++ b/mailbox-android/src/main/res/drawable/startup_lock.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="64dp" + android:height="64dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M18,8h-1L17,6c0,-2.76 -2.24,-5 -5,-5S7,3.24 7,6v2L6,8c-1.1,0 -2,0.9 -2,2v10c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2L20,10c0,-1.1 -0.9,-2 -2,-2zM12,17c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2zM15.1,8L8.9,8L8.9,6c0,-1.71 1.39,-3.1 3.1,-3.1 1.71,0 3.1,1.39 3.1,3.1v2z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/startup_migration.xml b/mailbox-android/src/main/res/drawable/startup_migration.xml new file mode 100644 index 0000000000000000000000000000000000000000..a5021ef0dad493614ddb3e068540d5cf992d6e77 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/startup_migration.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="64dp" + android:height="64dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M21,10.12h-6.78l2.74,-2.82c-2.73,-2.7 -7.15,-2.8 -9.88,-0.1 -2.73,2.71 -2.73,7.08 0,9.79 2.73,2.71 7.15,2.71 9.88,0C18.32,15.65 19,14.08 19,12.1h2c0,1.98 -0.88,4.55 -2.64,6.29 -3.51,3.48 -9.21,3.48 -12.72,0 -3.5,-3.47 -3.53,-9.11 -0.02,-12.58 3.51,-3.47 9.14,-3.47 12.65,0L21,3v7.12zM12.5,8v4.25l3.5,2.08 -0.72,1.21L11,13V8h1.5z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/transport_bt.xml b/mailbox-android/src/main/res/drawable/transport_bt.xml new file mode 100644 index 0000000000000000000000000000000000000000..2a8774c8c4e451bbb6275780db63f9a5a5d90c52 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/transport_bt.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M17.71,7.71L12,2h-1v7.59L6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 11,14.41V22h1l5.71,-5.71 -4.3,-4.29 4.3,-4.29zM13,5.83l1.88,1.88L13,9.59V5.83zm1.88,10.46L13,18.17v-3.76l1.88,1.88z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/transport_lan.xml b/mailbox-android/src/main/res/drawable/transport_lan.xml new file mode 100644 index 0000000000000000000000000000000000000000..60844d4d4d4a8ff174c83cdda09c5cfe8830d4ab --- /dev/null +++ b/mailbox-android/src/main/res/drawable/transport_lan.xml @@ -0,0 +1,13 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillAlpha=".3" + android:fillColor="#FF000000" + android:pathData="M12.01,21.49L23.64,7c-0.45,-0.34 -4.93,-4 -11.64,-4C5.28,3 0.81,6.66 0.36,7l11.63,14.49 0.01,0.01 0.01,-0.01z"/> + <path + android:fillColor="#FF000000" + android:pathData="M3.53,10.95l8.46,10.54 0.01,0.01 0.01,-0.01 8.46,-10.54C20.04,10.62 16.81,8 12,8c-4.81,0 -8.04,2.62 -8.47,2.95z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/transport_tor.xml b/mailbox-android/src/main/res/drawable/transport_tor.xml new file mode 100644 index 0000000000000000000000000000000000000000..ce8f0060f3b96f547645c15790a68b530736d70f --- /dev/null +++ b/mailbox-android/src/main/res/drawable/transport_tor.xml @@ -0,0 +1,9 @@ +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:viewportHeight="24.0" + android:viewportWidth="24.0"> + <path + android:fillColor="#FF000000" + android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zm6.93,6h-2.95c-0.32,-1.25 -0.78,-2.45 -1.38,-3.56 1.84,0.63 3.37,1.91 4.33,3.56zM12,4.04c0.83,1.2 1.48,2.53 1.91,3.96h-3.82c0.43,-1.43 1.08,-2.76 1.91,-3.96zM4.26,14C4.1,13.36 4,12.69 4,12s0.1,-1.36 0.26,-2h3.38c-0.08,0.66 -0.14,1.32 -0.14,2 0,0.68 0.06,1.34 0.14,2H4.26zm0.82,2h2.95c0.32,1.25 0.78,2.45 1.38,3.56 -1.84,-0.63 -3.37,-1.9 -4.33,-3.56zm2.95,-8H5.08c0.96,-1.66 2.49,-2.93 4.33,-3.56C8.81,5.55 8.35,6.75 8.03,8zM12,19.96c-0.83,-1.2 -1.48,-2.53 -1.91,-3.96h3.82c-0.43,1.43 -1.08,2.76 -1.91,3.96zM14.34,14H9.66c-0.09,-0.66 -0.16,-1.32 -0.16,-2 0,-0.68 0.07,-1.35 0.16,-2h4.68c0.09,0.65 0.16,1.32 0.16,2 0,0.68 -0.07,1.34 -0.16,2zm0.25,5.56c0.6,-1.11 1.06,-2.31 1.38,-3.56h2.95c-0.96,1.65 -2.49,2.93 -4.33,3.56zM16.36,14c0.08,-0.66 0.14,-1.32 0.14,-2 0,-0.68 -0.06,-1.34 -0.14,-2h3.38c0.16,0.64 0.26,1.31 0.26,2s-0.1,1.36 -0.26,2h-3.38z"/> +</vector> diff --git a/mailbox-android/src/main/res/drawable/trust_indicator_anonymous.xml b/mailbox-android/src/main/res/drawable/trust_indicator_anonymous.xml new file mode 100644 index 0000000000000000000000000000000000000000..82214b8d4fccc90ac9c85f520467d160cebdf546 --- /dev/null +++ b/mailbox-android/src/main/res/drawable/trust_indicator_anonymous.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="31dp" + android:height="12dp" + android:viewportHeight="20" + android:viewportWidth="49"> + <path + android:fillColor="#b7b7b7" + android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 +L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 +L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 +L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194 +L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797 +L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797 +L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797 +L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 +L10.5752,8.38194 L10.5752,11.8208 Z"/> + <path + android:fillColor="#b7b7b7" + android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 +L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 +L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 +L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194 +L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797 +L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797 +L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797 +L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 +L26.0752,8.38194 L26.0752,11.8208 Z"/> + <path + android:fillColor="#b7b7b7" + android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 +L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 +L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 +L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194 +L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797 +L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797 +L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797 +L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194 +L41.5752,8.38194 L41.5752,11.8208 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/trust_indicator_unknown.xml b/mailbox-android/src/main/res/drawable/trust_indicator_unknown.xml new file mode 100644 index 0000000000000000000000000000000000000000..63e6ab7afaedca0935498be5f0847429846b665f --- /dev/null +++ b/mailbox-android/src/main/res/drawable/trust_indicator_unknown.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="31dp" + android:height="12dp" + android:viewportHeight="20" + android:viewportWidth="49"> + <path + android:fillColor="#c34032" + android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 +L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 +L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 +L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194 +L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797 +L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797 +L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797 +L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 +L10.5752,8.38194 L10.5752,11.8208 Z"/> + <path + android:fillColor="#b7b7b7" + android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 +L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 +L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 +L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194 +L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797 +L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797 +L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797 +L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 +L26.0752,8.38194 L26.0752,11.8208 Z"/> + <path + android:fillColor="#b7b7b7" + android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 +L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 +L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 +L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194 +L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797 +L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797 +L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797 +L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194 +L41.5752,8.38194 L41.5752,11.8208 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/trust_indicator_unverified.xml b/mailbox-android/src/main/res/drawable/trust_indicator_unverified.xml new file mode 100644 index 0000000000000000000000000000000000000000..97af93df956bd198b07ba367c960741a7cf7becc --- /dev/null +++ b/mailbox-android/src/main/res/drawable/trust_indicator_unverified.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="31dp" + android:height="12dp" + android:viewportHeight="20" + android:viewportWidth="49"> + <path + android:fillColor="#fcd53a" + android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 +L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 +L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 +L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194 +L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797 +L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797 +L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797 +L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 +L10.5752,8.38194 L10.5752,11.8208 Z"/> + <path + android:fillColor="#fcd53a" + android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 +L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 +L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 +L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194 +L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797 +L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797 +L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797 +L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 +L26.0752,8.38194 L26.0752,11.8208 Z"/> + <path + android:fillColor="#b7b7b7" + android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 +L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 +L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 +L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194 +L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797 +L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797 +L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797 +L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194 +L41.5752,8.38194 L41.5752,11.8208 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/drawable/trust_indicator_verified.xml b/mailbox-android/src/main/res/drawable/trust_indicator_verified.xml new file mode 100644 index 0000000000000000000000000000000000000000..5b37c223ad2a2cba22690643cf43d0ea90eb81cc --- /dev/null +++ b/mailbox-android/src/main/res/drawable/trust_indicator_verified.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="31dp" + android:height="12dp" + android:viewportHeight="20" + android:viewportWidth="49"> + <path + android:fillColor="#7fac49" + android:pathData="M15.5002,13.8797 L15.5002,11.8208 L12.9502,11.8208 L12.9502,8.38194 +L15.5002,8.38194 L15.5002,6.32312 L12.9502,6.32312 L12.9502,2.49959 +L10.5752,2.49959 L10.5752,6.32312 L7.42514,6.32312 L7.42514,2.49959 +L5.05014,2.49959 L5.05014,6.32312 L2.50016,6.32312 L2.50016,8.38194 +L5.05014,8.38194 L5.05014,11.8208 L2.50016,11.8208 L2.50016,13.8797 +L5.05014,13.8797 L5.05014,17.4996 L7.42514,17.4996 L7.42514,13.8797 +L10.5752,13.8797 L10.5752,17.4996 L12.9502,17.4996 L12.9502,13.8797 +L15.5002,13.8797 Z M10.5752,11.8208 L7.42514,11.8208 L7.42514,8.38194 +L10.5752,8.38194 L10.5752,11.8208 Z"/> + <path + android:fillColor="#7fac49" + android:pathData="M31.0002,13.8797 L31.0002,11.8208 L28.4502,11.8208 L28.4502,8.38194 +L31.0002,8.38194 L31.0002,6.32312 L28.4502,6.32312 L28.4502,2.49959 +L26.0752,2.49959 L26.0752,6.32312 L22.9251,6.32312 L22.9251,2.49959 +L20.5501,2.49959 L20.5501,6.32312 L18.0002,6.32312 L18.0002,8.38194 +L20.5501,8.38194 L20.5501,11.8208 L18.0002,11.8208 L18.0002,13.8797 +L20.5501,13.8797 L20.5501,17.4996 L22.9251,17.4996 L22.9251,13.8797 +L26.0752,13.8797 L26.0752,17.4996 L28.4502,17.4996 L28.4502,13.8797 +L31.0002,13.8797 Z M26.0752,11.8208 L22.9251,11.8208 L22.9251,8.38194 +L26.0752,8.38194 L26.0752,11.8208 Z"/> + <path + android:fillColor="#7fac49" + android:pathData="M46.5002,13.8797 L46.5002,11.8208 L43.9502,11.8208 L43.9502,8.38194 +L46.5002,8.38194 L46.5002,6.32312 L43.9502,6.32312 L43.9502,2.49959 +L41.5752,2.49959 L41.5752,6.32312 L38.4251,6.32312 L38.4251,2.49959 +L36.0501,2.49959 L36.0501,6.32312 L33.5002,6.32312 L33.5002,8.38194 +L36.0501,8.38194 L36.0501,11.8208 L33.5002,11.8208 L33.5002,13.8797 +L36.0501,13.8797 L36.0501,17.4996 L38.4251,17.4996 L38.4251,13.8797 +L41.5752,13.8797 L41.5752,17.4996 L43.9502,17.4996 L43.9502,13.8797 +L46.5002,13.8797 Z M41.5752,11.8208 L38.4251,11.8208 L38.4251,8.38194 +L41.5752,8.38194 L41.5752,11.8208 Z"/> +</vector> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout-land/fragment_keyagreement_id.xml b/mailbox-android/src/main/res/layout-land/fragment_keyagreement_id.xml new file mode 100644 index 0000000000000000000000000000000000000000..9064b852779f2f8df67be0d5ffae4f8c996e2af7 --- /dev/null +++ b/mailbox-android/src/main/res/layout-land/fragment_keyagreement_id.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + android:id="@+id/scrollView" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <android.support.constraint.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/margin_large"> + + <ImageView + android:id="@+id/diagram" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:adjustViewBounds="true" + android:padding="@dimen/margin_medium" + android:scaleType="fitCenter" + android:src="@drawable/qr_code_intro" + android:tint="@color/color_primary" + app:layout_constraintBottom_toBottomOf="@id/explanationText" + app:layout_constraintEnd_toStartOf="@id/explanationText" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <ImageView + android:id="@+id/explanationImage" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:adjustViewBounds="true" + android:paddingLeft="@dimen/margin_large" + android:paddingRight="@dimen/margin_large" + android:paddingTop="@dimen/margin_large" + android:scaleType="fitCenter" + android:src="@drawable/qr_code_explanation" + app:layout_constraintBottom_toTopOf="@id/explanationText" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/diagram" + app:layout_constraintTop_toTopOf="parent" + tools:ignore="ContentDescription"/> + + <TextView + android:id="@+id/explanationText" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:padding="@dimen/margin_large" + android:text="@string/face_to_face" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/diagram" + app:layout_constraintTop_toBottomOf="@id/explanationImage"/> + + <View + android:id="@+id/explanationBorder" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="@drawable/border_explanation" + app:layout_constraintBottom_toBottomOf="@id/explanationText" + app:layout_constraintEnd_toEndOf="@id/explanationImage" + app:layout_constraintStart_toStartOf="@id/explanationImage" + app:layout_constraintTop_toTopOf="@id/explanationImage"/> + + <android.support.constraint.Barrier + android:id="@+id/barrier" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:barrierDirection="bottom" + app:constraint_referenced_ids="diagram,explanationBorder"/> + + <Button + android:id="@+id/continueButton" + style="@style/BriarButton.Default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal" + android:layout_marginTop="@dimen/margin_medium" + android:text="@string/continue_button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toBottomOf="@id/barrier"/> + + </android.support.constraint.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_change_password.xml b/mailbox-android/src/main/res/layout/activity_change_password.xml new file mode 100644 index 0000000000000000000000000000000000000000..379dd758746b69b529b6c3a8f4634068979029f6 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_change_password.xml @@ -0,0 +1,108 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".login.ChangePasswordActivity"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="@dimen/margin_activity_vertical" + android:paddingEnd="@dimen/margin_activity_horizontal" + android:paddingLeft="@dimen/margin_activity_horizontal" + android:paddingRight="@dimen/margin_activity_horizontal" + android:paddingStart="@dimen/margin_activity_horizontal" + android:paddingTop="@dimen/margin_activity_vertical"> + + <android.support.design.widget.TextInputLayout + android:id="@+id/current_password_entry_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + app:errorEnabled="true" + app:hintEnabled="false" + app:passwordToggleEnabled="true"> + + <EditText + android:id="@+id/current_password_entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/current_password" + android:inputType="textPassword" + android:maxLines="1"/> + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:id="@+id/new_password_entry_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/current_password_entry_wrapper" + android:layout_centerHorizontal="true" + app:errorEnabled="true" + app:hintEnabled="false" + app:passwordToggleEnabled="true"> + + <EditText + android:id="@+id/new_password_entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/choose_new_password" + android:inputType="textPassword" + android:maxLines="1"/> + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:id="@+id/new_password_confirm_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/new_password_entry_wrapper" + android:layout_centerHorizontal="true" + app:errorEnabled="true" + app:hintEnabled="false" + app:passwordToggleEnabled="true"> + + <EditText + android:id="@+id/new_password_confirm" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/confirm_new_password" + android:imeOptions="actionDone" + android:inputType="textPassword" + android:maxLines="1"/> + </android.support.design.widget.TextInputLayout> + + <org.briarproject.mailbox.login.StrengthMeter + android:id="@+id/strength_meter" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/new_password_confirm_wrapper" + android:layout_centerHorizontal="true" + android:visibility="invisible"/> + + <Button + android:id="@+id/change_password" + style="@style/BriarButton.Default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/strength_meter" + android:layout_centerHorizontal="true" + android:layout_marginTop="@dimen/margin_medium" + android:enabled="false" + android:text="@string/change_password" + tools:enabled="true"/> + + <ProgressBar + android:id="@+id/progress_wheel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@id/change_password" + android:layout_centerHorizontal="true" + android:visibility="invisible"/> + + </RelativeLayout> + +</ScrollView> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_dev_report.xml b/mailbox-android/src/main/res/layout/activity_dev_report.xml new file mode 100644 index 0000000000000000000000000000000000000000..7b9293eb480e72469173321cae18ef89f1d41127 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_dev_report.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <LinearLayout + android:id="@+id/report_form" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:visibility="visible" + tools:context=".api.android.reporting.DevReportActivity"> + + <include layout="@layout/toolbar"/> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_marginEnd="@dimen/margin_large" + android:layout_marginLeft="@dimen/margin_large" + android:layout_marginRight="@dimen/margin_large" + android:layout_marginStart="@dimen/margin_large"> + + <android.support.design.widget.TextInputLayout + android:id="@+id/user_comment_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/user_comment" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textMultiLine|textCapSentences" + tools:hint="@string/describe_crash"/> + + </android.support.design.widget.TextInputLayout> + + <android.support.design.widget.TextInputLayout + android:id="@+id/user_email_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/user_comment_layout" + android:layout_marginTop="@dimen/margin_small"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/user_email" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/optional_contact_email" + android:inputType="textEmailAddress" + android:maxLines="1"/> + + </android.support.design.widget.TextInputLayout> + + <CheckBox + android:id="@+id/include_debug_report" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/user_email_layout" + android:layout_marginTop="@dimen/margin_small" + android:layout_toLeftOf="@+id/chevron" + android:checked="false" + android:text="@string/include_debug_report_crash"/> + + <Button + android:id="@+id/chevron" + style="@style/BriarButtonFlat.Positive" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignTop="@+id/include_debug_report" + android:text="@string/show"/> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/include_debug_report" + android:layout_marginTop="@dimen/margin_small"> + + <LinearLayout + android:id="@+id/report_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center_horizontal" + android:orientation="vertical" + android:paddingBottom="@dimen/listitem_height_one_line_avatar" + android:paddingEnd="@dimen/margin_large" + android:paddingLeft="@dimen/margin_large" + android:paddingRight="@dimen/margin_large" + android:paddingStart="@dimen/margin_large" + android:paddingTop="@dimen/margin_small" + android:visibility="gone"/> + + </ScrollView> + + <ProgressBar + android:id="@+id/progress_wheel" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_below="@+id/include_debug_report" + android:layout_centerHorizontal="true" + android:indeterminate="true" + android:visibility="gone"/> + + </RelativeLayout> + </LinearLayout> + + <RelativeLayout + android:id="@+id/request_report" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:clickable="true" + android:gravity="center" + android:padding="@dimen/margin_large" + android:visibility="invisible"> + + <TextView + android:id="@+id/crashed" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:gravity="center" + android:text="@string/briar_crashed" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_large"/> + + <TextView + android:id="@+id/fault" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/crashed" + android:layout_marginTop="@dimen/margin_large" + android:gravity="center" + android:text="@string/not_your_fault" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_large"/> + + <TextView + android:id="@+id/pleaseSend" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/fault" + android:layout_marginTop="@dimen/margin_large" + android:gravity="center" + android:text="@string/please_send_report" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_large"/> + + <TextView + android:id="@+id/encrypted" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/pleaseSend" + android:layout_marginBottom="@dimen/margin_large" + android:layout_marginTop="@dimen/margin_large" + android:gravity="center" + android:text="@string/report_is_encrypted" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_large"/> + + <Button + android:id="@+id/declineButton" + style="@style/BriarButtonFlat.Negative" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_below="@+id/encrypted" + android:text="@string/close"/> + + <Button + android:id="@+id/acceptButton" + style="@style/BriarButtonFlat.Positive" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_below="@+id/encrypted" + android:text="@string/send_report"/> + + </RelativeLayout> + +</FrameLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_expired.xml b/mailbox-android/src/main/res/layout/activity_expired.xml new file mode 100644 index 0000000000000000000000000000000000000000..c3f54489fb958b8f3ac61080eeccfcd8e15ef1ba --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_expired.xml @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_large" + android:orientation="vertical"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_medium" + android:gravity="center" + android:text="@string/expiry_date_reached" + android:textSize="@dimen/text_size_large"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_medium" + android:gravity="center" + android:text="@string/download_briar" + android:textSize="@dimen/text_size_large"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_medium" + android:gravity="center" + android:text="@string/create_new_account" + android:textSize="@dimen/text_size_large"/> + + <Button + android:id="@+id/download_briar_button" + style="@style/BriarButton.Default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_medium" + android:text="@string/download_briar_button"/> + </LinearLayout> +</ScrollView> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_fragment_container.xml b/mailbox-android/src/main/res/layout/activity_fragment_container.xml new file mode 100644 index 0000000000000000000000000000000000000000..e6c20760fb5078de5def86eec38402fc2db9042a --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_fragment_container.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + android:id="@+id/fragmentContainer" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"/> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_fragment_container_toolbar.xml b/mailbox-android/src/main/res/layout/activity_fragment_container_toolbar.xml new file mode 100644 index 0000000000000000000000000000000000000000..080ffa5d8e8e5e91c9e0c3d08ae384b33aad3f80 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_fragment_container_toolbar.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".api.android.keyagreement.KeyAgreementActivity"> + + <include layout="@layout/toolbar"/> + + <FrameLayout + android:id="@+id/fragmentContainer" + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + +</LinearLayout> diff --git a/mailbox-android/src/main/res/layout/activity_nav_drawer.xml b/mailbox-android/src/main/res/layout/activity_nav_drawer.xml new file mode 100644 index 0000000000000000000000000000000000000000..c649a0d80d0fc2cbff17e5e4634c73808a09d852 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_nav_drawer.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.v4.widget.DrawerLayout + android:id="@+id/drawer_layout" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context=".navdrawer.NavDrawerActivity"> + + <!-- The first child(root) is the content view --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <include layout="@layout/toolbar"/> + + <RelativeLayout + android:id="@+id/expiryWarning" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/briar_warning_background" + android:orientation="horizontal" + android:padding="@dimen/margin_medium" + android:visibility="gone" + tools:visibility="visible"> + + <TextView + android:id="@+id/expiryWarningText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_toLeftOf="@+id/expiryWarningClose" + android:text="@plurals/expiry_warning" + android:textColor="@color/briar_text_primary_inverse" + android:textSize="@dimen/text_size_small"/> + + <ImageView + android:id="@+id/expiryWarningClose" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_alignParentRight="true" + android:layout_centerInParent="true" + android:contentDescription="@string/close" + android:scaleType="center" + android:src="@drawable/ic_close" + android:tint="@color/briar_text_tertiary_inverse"/> + + </RelativeLayout> + + <FrameLayout + android:id="@+id/fragmentContainer" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + </LinearLayout> + + <!-- The second child is the menu --> + <include + layout="@layout/navigation_menu" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_gravity="start"/> + +</android.support.v4.widget.DrawerLayout> diff --git a/mailbox-android/src/main/res/layout/activity_open_database.xml b/mailbox-android/src/main/res/layout/activity_open_database.xml new file mode 100644 index 0000000000000000000000000000000000000000..3efce6d655b760c077fa0cad05ab462dcba2511f --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_open_database.xml @@ -0,0 +1,48 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/imageView" + android:layout_width="128dp" + android:layout_height="128dp" + android:scaleType="center" + android:src="@drawable/startup_lock" + android:tint="@color/briar_accent" + app:layout_constraintBottom_toTopOf="@+id/textView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.5" + app:layout_constraintVertical_chainStyle="packed" + tools:ignore="ContentDescription"/> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="@+id/imageView" + app:layout_constraintEnd_toEndOf="@+id/imageView" + app:layout_constraintStart_toStartOf="@+id/imageView" + app:layout_constraintTop_toTopOf="@+id/imageView"/> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:paddingLeft="@dimen/margin_large" + android:paddingRight="@dimen/margin_large" + android:text="@string/startup_open_database" + android:textSize="@dimen/text_size_large" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imageView"/> + +</android.support.constraint.ConstraintLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_password.xml b/mailbox-android/src/main/res/layout/activity_password.xml new file mode 100644 index 0000000000000000000000000000000000000000..022d31c4cab36a5302298b02c428c0711f0a78f3 --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_password.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:paddingBottom="@dimen/margin_activity_vertical" + android:paddingEnd="@dimen/margin_activity_horizontal" + android:paddingLeft="@dimen/margin_activity_horizontal" + android:paddingRight="@dimen/margin_activity_horizontal" + android:paddingStart="@dimen/margin_activity_horizontal" + android:paddingTop="@dimen/margin_activity_vertical"> + + <android.support.design.widget.TextInputLayout + android:id="@+id/password_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + app:errorEnabled="true" + app:hintEnabled="false" + app:passwordToggleEnabled="true"> + + <EditText + android:id="@+id/edit_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/enter_password" + android:imeOptions="actionDone" + android:inputType="textPassword" + android:maxLines="1"/> + </android.support.design.widget.TextInputLayout> + + <Button + android:id="@+id/btn_sign_in" + style="@style/BriarButton" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@id/password_layout" + android:layout_marginTop="@dimen/margin_medium" + android:onClick="onSignInClick" + android:text="@string/sign_in_button"/> + + <ProgressBar + android:id="@+id/progress_wheel" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignTop="@id/btn_sign_in" + android:layout_centerHorizontal="true" + android:visibility="invisible"/> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/btn_sign_in" + android:layout_centerHorizontal="true" + android:layout_marginTop="@dimen/margin_large" + android:clickable="true" + android:onClick="onForgottenPasswordClick" + android:text="@string/forgotten_password" + android:textColor="?android:attr/textColorLink"/> + + </RelativeLayout> + +</ScrollView> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/activity_settings.xml b/mailbox-android/src/main/res/layout/activity_settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..51fede89cd7820b155d1876faeb9a035418305aa --- /dev/null +++ b/mailbox-android/src/main/res/layout/activity_settings.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <fragment + android:id="@+id/fragment" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:name="org.briarproject.mailbox.settings.SettingsFragment"/> +</FrameLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/briar_recycler_view.xml b/mailbox-android/src/main/res/layout/briar_recycler_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..ad50120413de8d3940360c59d3539be2c1acda86 --- /dev/null +++ b/mailbox-android/src/main/res/layout/briar_recycler_view.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v7.widget.RecyclerView + android:id="@+id/recyclerView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical" + tools:listitem="@layout/list_item_contact"/> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true"/> + + <TextView + android:id="@+id/emptyView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerInParent="true" + android:gravity="center" + android:padding="@dimen/margin_activity_horizontal" + android:text="@string/no_data" + android:textSize="@dimen/text_size_large" + tools:text="@string/no_contacts"/> + +</RelativeLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/checkbox.xml b/mailbox-android/src/main/res/layout/checkbox.xml new file mode 100644 index 0000000000000000000000000000000000000000..eb3dfe6a44b0f61eaff19e08dbb4970d7592d24e --- /dev/null +++ b/mailbox-android/src/main/res/layout/checkbox.xml @@ -0,0 +1,16 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <CheckBox + android:id="@+id/checkbox" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_activity_horizontal" + android:layout_marginTop="@dimen/margin_activity_vertical" + android:checked="false" + android:text="@string/don_t_ask_again"/> + +</FrameLayout> diff --git a/mailbox-android/src/main/res/layout/contact_avatar_status.xml b/mailbox-android/src/main/res/layout/contact_avatar_status.xml new file mode 100644 index 0000000000000000000000000000000000000000..0d47e786e6451d0231a944b4e81da7827fb220c2 --- /dev/null +++ b/mailbox-android/src/main/res/layout/contact_avatar_status.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="32dp" + android:layout_height="32dp" + tools:showIn="@layout/activity_conversation"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/contactAvatar" + style="@style/BriarAvatar" + android:layout_width="30dp" + android:layout_height="30dp" + app:civ_border_color="@color/action_bar_text" + tools:src="@mipmap/ic_launcher_round"/> + + <ImageView + android:id="@+id/contactStatus" + android:layout_width="15dp" + android:layout_height="15dp" + android:layout_gravity="bottom|right" + android:scaleType="fitCenter" + tools:ignore="ContentDescription" + tools:src="@drawable/contact_online"/> + +</FrameLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/dialog_screen_filter.xml b/mailbox-android/src/main/res/layout/dialog_screen_filter.xml new file mode 100644 index 0000000000000000000000000000000000000000..b883d16e71408101e42e4f0e890932ca29bc8493 --- /dev/null +++ b/mailbox-android/src/main/res/layout/dialog_screen_filter.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + android:padding="@dimen/margin_large"> + + <ScrollView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1"> + + <TextView + android:id="@+id/screen_filter_message" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + + </ScrollView> + + <CheckBox + android:id="@+id/screen_filter_checkbox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0" + android:layout_marginTop="@dimen/margin_large" + android:text="@string/screen_filter_allow"/> + +</LinearLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/fragment_error.xml b/mailbox-android/src/main/res/layout/fragment_error.xml new file mode 100644 index 0000000000000000000000000000000000000000..b705051ccdd57d7d9b980ae534b3b99efc20023b --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_error.xml @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v7.widget.AppCompatImageView + android:id="@+id/errorIcon" + android:layout_width="128dp" + android:layout_height="128dp" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:src="@drawable/alerts_and_states_error" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:tint="?attr/colorControlNormal" + tools:ignore="ContentDescription"/> + + <TextView + android:id="@+id/errorTitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:layout_marginTop="8dp" + android:text="@string/sorry" + android:textSize="@dimen/text_size_xlarge" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/errorIcon"/> + + <TextView + android:id="@+id/errorMessage" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="16dp" + android:layout_marginLeft="16dp" + android:layout_marginRight="16dp" + android:layout_marginStart="16dp" + android:layout_marginTop="8dp" + android:textSize="@dimen/text_size_medium" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/errorTitle" + tools:text="@string/qr_code_unsupported"/> + +</android.support.constraint.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_keyagreement_id.xml b/mailbox-android/src/main/res/layout/fragment_keyagreement_id.xml new file mode 100644 index 0000000000000000000000000000000000000000..c8f9b0999bcda1eb032d1f06bf4d69844523cd2d --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_keyagreement_id.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + android:id="@+id/scrollView" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <android.support.constraint.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/margin_large"> + + <ImageView + android:id="@+id/diagram" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:paddingBottom="@dimen/margin_large" + android:scaleType="fitCenter" + android:src="@drawable/qr_code_intro" + android:tint="@color/color_primary" + app:layout_constraintBottom_toTopOf="@id/explanationImage" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <ImageView + android:id="@+id/explanationImage" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:paddingLeft="@dimen/margin_large" + android:paddingRight="@dimen/margin_large" + android:paddingTop="@dimen/margin_large" + android:scaleType="fitCenter" + android:src="@drawable/qr_code_explanation" + app:layout_constraintBottom_toTopOf="@id/explanationText" + app:layout_constraintEnd_toEndOf="@id/diagram" + app:layout_constraintStart_toStartOf="@id/diagram" + app:layout_constraintTop_toBottomOf="@id/diagram" + tools:ignore="ContentDescription"/> + + <TextView + android:id="@+id/explanationText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="@dimen/margin_large" + android:text="@string/face_to_face" + app:layout_constrainedWidth="true" + app:layout_constraintEnd_toEndOf="@id/explanationImage" + app:layout_constraintStart_toStartOf="@id/explanationImage" + app:layout_constraintTop_toBottomOf="@id/explanationImage"/> + + <View + android:id="@+id/explanationBorder" + android:layout_width="0dp" + android:layout_height="0dp" + android:background="@drawable/border_explanation" + app:layout_constraintBottom_toBottomOf="@id/explanationText" + app:layout_constraintEnd_toEndOf="@id/explanationImage" + app:layout_constraintStart_toStartOf="@id/explanationImage" + app:layout_constraintTop_toTopOf="@id/explanationImage"/> + + <Button + android:id="@+id/continueButton" + style="@style/BriarButton.Default" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_medium" + android:text="@string/continue_button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="@id/explanationImage" + app:layout_constraintStart_toStartOf="@id/explanationImage" + app:layout_constraintTop_toBottomOf="@id/explanationText"/> + + </android.support.constraint.ConstraintLayout> + +</ScrollView> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml b/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml new file mode 100644 index 0000000000000000000000000000000000000000..ce8f275a3b0e81afe8aed63141deeef6c311c087 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_keyagreement_qr.xml @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <org.briarproject.mailbox.keyagreement.CameraView + android:id="@+id/camera_view" + android:layout_width="match_parent" + android:layout_height="match_parent"/> + + <LinearLayout + android:id="@+id/camera_overlay" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:baselineAligned="false" + android:orientation="vertical"> + + <LinearLayout + android:id="@+id/status_container" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical" + android:padding="@dimen/margin_medium" + android:visibility="invisible" + tools:visibility="visible"> + + <ProgressBar + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <TextView + android:id="@+id/connect_status" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:paddingTop="@dimen/margin_large" + tools:text="Connection failed"/> + </LinearLayout> + + <org.briarproject.mailbox.view.QrCodeView + android:id="@+id/qr_code_view" + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1" + android:background="@android:color/white"/> + </LinearLayout> +</FrameLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_link_dialog.xml b/mailbox-android/src/main/res/layout/fragment_link_dialog.xml new file mode 100644 index 0000000000000000000000000000000000000000..328185e424e33fcf135927cc423dee680e48ef7b --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_link_dialog.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="@dimen/margin_large"> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/link_warning_title" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_large" + android:textStyle="bold"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:text="@string/link_warning_intro" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_medium"/> + + <TextView + android:id="@+id/urlView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:textIsSelectable="true" + android:typeface="monospace" + tools:text="http://very.bad.site.com"/> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_large" + android:text="@string/link_warning_text" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_medium"/> + + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <Button + android:id="@+id/cancelButton" + style="@style/BriarButtonFlat.Positive" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:text="@string/cancel"/> + + <Button + android:id="@+id/openButton" + style="@style/BriarButtonFlat.Negative" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="0.5" + android:text="@string/link_warning_open_link"/> + + </LinearLayout> + +</LinearLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/fragment_overview.xml b/mailbox-android/src/main/res/layout/fragment_overview.xml new file mode 100644 index 0000000000000000000000000000000000000000..082da4c8b71946a029db6c64820cfc2dbec3a76b --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_overview.xml @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:context="org.briarproject.mailbox.navdrawer.NavDrawerActivity"> + + <TextView + android:id="@+id/ownerText" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:text="Paired with:" + android:textColor="@color/briar_text_primary" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <TextView + android:id="@+id/ownerName" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:text="@string/mailbox_unpaired" + android:textColor="@color/briar_text_primary" + app:layout_constraintStart_toEndOf="@id/ownerText" + app:layout_constraintTop_toTopOf="parent"/> + + <ImageView + android:id="@+id/ownerStatus" + android:layout_width="15dp" + android:layout_height="15dp" + android:layout_margin="8dp" + android:scaleType="fitCenter" + app:layout_constraintBottom_toBottomOf="@+id/ownerName" + app:layout_constraintStart_toEndOf="@id/ownerName" + app:layout_constraintTop_toTopOf="@id/ownerName" + tools:ignore="ContentDescription" + tools:src="@drawable/contact_offline"/> + + <Switch + android:id="@+id/btToggle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:text="@string/transport_bt" + app:layout_constraintTop_toBottomOf="@id/ownerText"/> + + <Switch + android:id="@+id/wifiToggle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:text="@string/transport_lan" + app:layout_constraintTop_toBottomOf="@id/btToggle"/> + + <Switch + android:id="@+id/torToggle" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:text="@string/transport_tor" + app:layout_constraintTop_toBottomOf="@id/wifiToggle"/> + +</android.support.constraint.ConstraintLayout> diff --git a/mailbox-android/src/main/res/layout/fragment_setup_author_name.xml b/mailbox-android/src/main/res/layout/fragment_setup_author_name.xml new file mode 100644 index 0000000000000000000000000000000000000000..eae7ece48b4acac51abf35f8e2d1457bc8402a45 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_setup_author_name.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <android.support.constraint.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/margin_activity_vertical" + android:paddingEnd="@dimen/margin_activity_horizontal" + android:paddingLeft="@dimen/margin_activity_horizontal" + android:paddingRight="@dimen/margin_activity_horizontal" + android:paddingStart="@dimen/margin_activity_horizontal" + android:paddingTop="@dimen/margin_activity_vertical"> + + <android.support.design.widget.TextInputLayout + android:id="@+id/nickname_entry_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_medium" + app:errorEnabled="true" + app:hintEnabled="false" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/nickname_entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/choose_nickname" + android:imeOptions="actionNext" + android:inputType="text|textCapWords" + android:maxLines="1"/> + + <requestFocus/> + </android.support.design.widget.TextInputLayout> + + <Button + android:id="@+id/next" + style="@style/BriarButton.Default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/margin_activity_horizontal" + android:enabled="false" + android:text="@string/setup_next" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/nickname_entry_wrapper" + app:layout_constraintVertical_bias="1.0" + tools:enabled="true"/> + + </android.support.constraint.ConstraintLayout> + +</ScrollView> diff --git a/mailbox-android/src/main/res/layout/fragment_setup_doze.xml b/mailbox-android/src/main/res/layout/fragment_setup_doze.xml new file mode 100644 index 0000000000000000000000000000000000000000..970a66178f15f7386720d9a087a905aa01c190f9 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_setup_doze.xml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <android.support.constraint.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/margin_activity_vertical" + android:paddingEnd="@dimen/margin_activity_horizontal" + android:paddingLeft="@dimen/margin_activity_horizontal" + android:paddingRight="@dimen/margin_activity_horizontal" + android:paddingStart="@dimen/margin_activity_horizontal" + android:paddingTop="@dimen/margin_activity_vertical"> + + <org.briarproject.mailbox.login.DozeView + android:id="@+id/dozeView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"/> + + <org.briarproject.mailbox.login.HuaweiView + android:id="@+id/huaweiView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/dozeView"/> + + <Button + android:id="@+id/next" + style="@style/BriarButton.Default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:enabled="false" + android:text="@string/create_account_button" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/huaweiView" + app:layout_constraintVertical_bias="1.0" + tools:enabled="true"/> + + <ProgressBar + android:id="@+id/progress" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/next" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="@+id/next"/> + + </android.support.constraint.ConstraintLayout> + +</ScrollView> diff --git a/mailbox-android/src/main/res/layout/fragment_setup_password.xml b/mailbox-android/src/main/res/layout/fragment_setup_password.xml new file mode 100644 index 0000000000000000000000000000000000000000..694f7dfc16f02323ee7fdc1b44edbdc555517d05 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_setup_password.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fillViewport="true"> + + <android.support.constraint.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/margin_activity_vertical" + android:paddingEnd="@dimen/margin_activity_horizontal" + android:paddingLeft="@dimen/margin_activity_horizontal" + android:paddingRight="@dimen/margin_activity_horizontal" + android:paddingStart="@dimen/margin_activity_horizontal" + android:paddingTop="@dimen/margin_activity_vertical"> + + <android.support.design.widget.TextInputLayout + android:id="@+id/password_entry_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:errorEnabled="true" + app:hintEnabled="false" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:passwordToggleEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/password_entry" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/choose_password" + android:imeOptions="actionNext" + android:inputType="textPassword" + android:maxLines="1"> + + <requestFocus/> + </android.support.design.widget.TextInputEditText> + + </android.support.design.widget.TextInputLayout> + + <org.briarproject.mailbox.login.StrengthMeter + android:id="@+id/strength_meter" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:visibility="invisible" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/password_entry_wrapper" + tools:visibility="visible"/> + + <android.support.design.widget.TextInputLayout + android:id="@+id/password_confirm_wrapper" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + app:errorEnabled="true" + app:hintEnabled="false" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/strength_meter" + app:passwordToggleEnabled="true"> + + <android.support.design.widget.TextInputEditText + android:id="@+id/password_confirm" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:hint="@string/confirm_password" + android:inputType="textPassword" + android:maxLines="1"/> + </android.support.design.widget.TextInputLayout> + + <Button + android:id="@+id/next" + style="@style/BriarButton.Default" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:enabled="false" + android:text="@string/setup_next" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/password_confirm_wrapper" + app:layout_constraintVertical_bias="1.0" + tools:enabled="true"/> + + <ProgressBar + android:id="@+id/progress" + style="?android:attr/progressBarStyle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintRight_toRightOf="parent" + app:layout_constraintTop_toBottomOf="@+id/password_confirm_wrapper" + app:layout_constraintVertical_bias="1.0"/> + + </android.support.constraint.ConstraintLayout> + +</ScrollView> diff --git a/mailbox-android/src/main/res/layout/fragment_sign_out.xml b/mailbox-android/src/main/res/layout/fragment_sign_out.xml new file mode 100644 index 0000000000000000000000000000000000000000..73fd8f4e535d5dbef40998790205cd2659703a30 --- /dev/null +++ b/mailbox-android/src/main/res/layout/fragment_sign_out.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.constraint.ConstraintLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/imageView" + android:layout_width="128dp" + android:layout_height="128dp" + android:scaleType="center" + android:src="@drawable/startup_lock" + android:tint="@color/briar_primary" + app:layout_constraintBottom_toTopOf="@+id/textView" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_bias="0.5" + app:layout_constraintVertical_chainStyle="packed" + tools:ignore="ContentDescription"/> + + <ProgressBar + android:id="@+id/progressBar" + style="?android:attr/progressBarStyleLarge" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="@+id/imageView" + app:layout_constraintEnd_toEndOf="@+id/imageView" + app:layout_constraintStart_toStartOf="@+id/imageView" + app:layout_constraintTop_toTopOf="@+id/imageView"/> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="8dp" + android:text="@string/progress_title_logout" + android:textSize="@dimen/text_size_large" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/imageView"/> + +</android.support.constraint.ConstraintLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/list.xml b/mailbox-android/src/main/res/layout/list.xml new file mode 100644 index 0000000000000000000000000000000000000000..9b80689c7281a69e2eba2eb68d8901f59c6177e5 --- /dev/null +++ b/mailbox-android/src/main/res/layout/list.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="utf-8"?> +<org.briarproject.mailbox.view.BriarRecyclerView + android:id="@+id/list" + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + app:scrollToEnd="false"/> diff --git a/mailbox-android/src/main/res/layout/list_item_contact.xml b/mailbox-android/src/main/res/layout/list_item_contact.xml new file mode 100644 index 0000000000000000000000000000000000000000..bac38bf1869ce80e16b20a8e0db892a74e030630 --- /dev/null +++ b/mailbox-android/src/main/res/layout/list_item_contact.xml @@ -0,0 +1,95 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:focusable="true" + android:orientation="vertical"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingBottom="@dimen/listitem_horizontal_margin" + android:paddingTop="@dimen/listitem_horizontal_margin"> + + <FrameLayout + android:id="@+id/avatarFrameView" + android:layout_width="@dimen/listitem_picture_frame_size" + android:layout_height="@dimen/listitem_picture_frame_size" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarView" + style="@style/BriarAvatar" + android:layout_width="@dimen/listitem_picture_size" + android:layout_height="@dimen/listitem_picture_size" + android:layout_gravity="bottom|left" + tools:src="@mipmap/ic_launcher_round"/> + + <TextView + android:id="@+id/unreadCountView" + android:layout_width="wrap_content" + android:layout_height="@dimen/unread_bubble_size" + android:layout_gravity="right|top" + android:background="@drawable/bubble" + android:gravity="center" + android:minWidth="@dimen/unread_bubble_size" + android:textColor="@color/briar_text_primary_inverse" + android:textSize="@dimen/unread_bubble_text_size" + android:textStyle="bold" + tools:text="123"/> + + </FrameLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin" + android:layout_toEndOf="@+id/avatarFrameView" + android:layout_toLeftOf="@+id/bulbView" + android:layout_toRightOf="@+id/avatarFrameView" + android:orientation="vertical"> + + <org.thoughtcrime.securesms.components.emoji.EmojiTextView + android:id="@+id/nameView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="2" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_medium" + tools:text="This is a name of a contact"/> + + <TextView + android:id="@+id/dateView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorSecondary" + android:textSize="@dimen/text_size_small" + tools:text="Dec 24"/> + + </LinearLayout> + + <ImageView + android:id="@+id/bulbView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginRight="@dimen/listitem_horizontal_margin" + tools:src="@drawable/contact_connected"/> + + </RelativeLayout> + + <View style="@style/Divider.ContactList"/> + +</LinearLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/list_item_contact_small.xml b/mailbox-android/src/main/res/layout/list_item_contact_small.xml new file mode 100644 index 0000000000000000000000000000000000000000..01bfc431aa659737eef3dfc18e1bd1e78f065e87 --- /dev/null +++ b/mailbox-android/src/main/res/layout/list_item_contact_small.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:paddingBottom="@dimen/margin_medium" + android:paddingTop="@dimen/margin_medium"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarView" + style="@style/BriarAvatar" + android:layout_width="@dimen/listitem_picture_size_small" + android:layout_height="@dimen/listitem_picture_size_small" + android:layout_gravity="center_vertical" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin" + tools:src="@mipmap/ic_launcher_round"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiTextView + android:id="@+id/nameView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_gravity="center_vertical" + android:layout_marginLeft="@dimen/margin_medium" + android:layout_marginStart="@dimen/margin_medium" + android:layout_weight="1" + android:maxLines="2" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_medium" + tools:text="This is a name of a contact"/> + + <ImageView + android:id="@+id/bulbView" + android:layout_width="@dimen/listitem_horizontal_margin" + android:layout_height="@dimen/listitem_horizontal_margin" + android:layout_gravity="center_vertical" + android:layout_marginRight="@dimen/listitem_horizontal_margin" + tools:src="@drawable/contact_connected"/> + +</LinearLayout> diff --git a/mailbox-android/src/main/res/layout/list_item_crash.xml b/mailbox-android/src/main/res/layout/list_item_crash.xml new file mode 100644 index 0000000000000000000000000000000000000000..63439eddb09a90a94a222471f0d0c1cf1c008481 --- /dev/null +++ b/mailbox-android/src/main/res/layout/list_item_crash.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal"> + + <CheckBox + android:id="@+id/include_in_report" + android:layout_width="wrap_content" + android:layout_height="wrap_content"/> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginBottom="@dimen/margin_small" + android:gravity="bottom" + android:textSize="@dimen/text_size_large" + android:textColor="?android:attr/textColorPrimary" + tools:text="Crash log entry title"/> + </LinearLayout> + + <TextView + android:id="@+id/content" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginBottom="@dimen/margin_medium" + android:textColor="?android:attr/textColorSecondary" + tools:text="Crash log entry value"/> + +</LinearLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/list_item_selectable_contact.xml b/mailbox-android/src/main/res/layout/list_item_selectable_contact.xml new file mode 100644 index 0000000000000000000000000000000000000000..df31c253d1dc2da5ac1f3359782a33a59dbfedeb --- /dev/null +++ b/mailbox-android/src/main/res/layout/list_item_selectable_contact.xml @@ -0,0 +1,69 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <RelativeLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackground" + android:padding="@dimen/listitem_horizontal_margin"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarView" + style="@style/BriarAvatar" + android:layout_width="@dimen/listitem_selectable_picture_size" + android:layout_height="@dimen/listitem_selectable_picture_size" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + tools:src="@mipmap/ic_launcher_round"/> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerVertical="true" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin" + android:layout_toEndOf="@+id/avatarView" + android:layout_toLeftOf="@+id/checkBox" + android:layout_toRightOf="@+id/avatarView" + android:orientation="vertical"> + + <org.thoughtcrime.securesms.components.emoji.EmojiTextView + android:id="@+id/nameView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="?android:attr/textColorPrimary" + android:textSize="@dimen/text_size_large" + tools:text="This is a name of a contact with a long name"/> + + <TextView + android:id="@+id/infoView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:maxLines="2" + android:text="@string/forum_invitation_already_sharing" + android:textColor="?android:attr/textColorTertiary" + android:textSize="@dimen/text_size_small" + tools:visibility="visible"/> + + </LinearLayout> + + <CheckBox + android:id="@+id/checkBox" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:clickable="false"/> + + </RelativeLayout> + + <View style="@style/Divider.ContactList"/> + +</LinearLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/list_item_transport.xml b/mailbox-android/src/main/res/layout/list_item_transport.xml new file mode 100644 index 0000000000000000000000000000000000000000..ab796704971fbd47d86f7163cb3a8e5ff81549b3 --- /dev/null +++ b/mailbox-android/src/main/res/layout/list_item_transport.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:padding="@dimen/margin_small" + android:gravity="center_horizontal"> + + <ImageView + android:id="@+id/imageView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:padding="@dimen/margin_small" + tools:src="@drawable/transport_tor"/> + + <TextView + android:id="@+id/textView" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:padding="@dimen/margin_small" + android:textColor="?android:attr/textColorSecondary" + tools:text="@string/transport_tor"/> + +</LinearLayout> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/navigation_header.xml b/mailbox-android/src/main/res/layout/navigation_header.xml new file mode 100644 index 0000000000000000000000000000000000000000..a54b9b6a0f63042b5e0268d7e179cd73c79215ef --- /dev/null +++ b/mailbox-android/src/main/res/layout/navigation_header.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="85dp" + android:background="@color/briar_primary" + tools:showIn="@layout/navigation_menu"> + + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:layout_marginBottom="@dimen/margin_medium" + android:layout_marginLeft="@dimen/margin_medium" + android:contentDescription="@string/app_name" + android:src="@drawable/navigation_drawer_header"/> + +</FrameLayout> diff --git a/mailbox-android/src/main/res/layout/navigation_menu.xml b/mailbox-android/src/main/res/layout/navigation_menu.xml new file mode 100644 index 0000000000000000000000000000000000000000..88808226070f84eec94f04e26ceae0c4e3f84686 --- /dev/null +++ b/mailbox-android/src/main/res/layout/navigation_menu.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="?android:attr/windowBackground" + android:fillViewport="true" + android:orientation="vertical"> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical"> + + <android.support.design.widget.NavigationView + android:id="@+id/navigation" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + app:elevation="0dp" + app:headerLayout="@layout/navigation_header" + app:itemIconTint="?attr/colorControlNormal" + app:itemTextColor="?android:textColorPrimary" + app:menu="@menu/navigation_drawer"/> + + <View + android:layout_width="match_parent" + android:layout_height="0dp" + android:layout_weight="1"/> + + <include + layout="@layout/transports_list" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom"/> + + </LinearLayout> + +</ScrollView> diff --git a/mailbox-android/src/main/res/layout/power_view.xml b/mailbox-android/src/main/res/layout/power_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..b04c7dd7bf7bada6859b4bc58b30cd5dabf343f3 --- /dev/null +++ b/mailbox-android/src/main/res/layout/power_view.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:parentTag="android.support.constraint.ConstraintLayout"> + + <TextView + android:id="@+id/textView" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:textSize="@dimen/text_size_medium" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="@string/setup_huawei_text"/> + + <android.support.v7.widget.AppCompatImageView + android:id="@+id/checkImage" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_margin="8dp" + android:src="@drawable/ic_check_white" + android:visibility="invisible" + app:layout_constraintBottom_toBottomOf="@+id/button" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="@+id/button" + app:tint="?attr/colorControlNormal" + tools:ignore="ContentDescription"/> + + <Button + android:id="@+id/button" + style="@style/BriarButton.Default" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_margin="8dp" + app:layout_constraintEnd_toStartOf="@+id/helpButton" + app:layout_constraintStart_toEndOf="@+id/checkImage" + app:layout_constraintTop_toBottomOf="@+id/textView" + tools:text="@string/setup_huawei_button"/> + + <ImageButton + android:id="@+id/helpButton" + style="@style/BriarButtonFlat.Positive" + android:layout_width="24dp" + android:layout_height="24dp" + android:layout_margin="8dp" + android:contentDescription="@string/help" + android:src="@drawable/ic_help_outline_white" + android:tint="@color/briar_button_positive" + app:layout_constraintBottom_toBottomOf="@+id/button" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@+id/button"/> + +</merge> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/preferences_category.xml b/mailbox-android/src/main/res/layout/preferences_category.xml new file mode 100644 index 0000000000000000000000000000000000000000..ced92b8ae9d84f5aff7581afea2236b7768416b3 --- /dev/null +++ b/mailbox-android/src/main/res/layout/preferences_category.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="utf-8"?> +<TextView + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:id="@android:id/title" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginLeft="16dp" + android:layout_marginStart="16dp" + android:textSize="14sp" + android:textStyle="bold" + android:textColor="@color/preference_category" + tools:text="This is a category"/> \ No newline at end of file diff --git a/mailbox-android/src/main/res/layout/qr_code_view.xml b/mailbox-android/src/main/res/layout/qr_code_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..9062428d0e6b3456b06b1e91a8e952cd3963e44d --- /dev/null +++ b/mailbox-android/src/main/res/layout/qr_code_view.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + tools:showIn="@layout/fragment_keyagreement_qr"> + + <ProgressBar + style="?android:attr/progressBarStyleLarge" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center"/> + + <android.support.constraint.ConstraintLayout + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:id="@+id/qr_code" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:contentDescription="@string/qr_code" + android:scaleType="fitCenter"/> + + <ImageView + android:id="@+id/fullscreen_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="@dimen/margin_small" + android:alpha="0.54" + android:background="?selectableItemBackground" + android:contentDescription="@string/show_qr_code_fullscreen" + android:src="@drawable/ic_fullscreen_black_48dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintRight_toRightOf="parent"/> + + </android.support.constraint.ConstraintLayout> +</merge> diff --git a/mailbox-android/src/main/res/layout/splash.xml b/mailbox-android/src/main/res/layout/splash.xml new file mode 100644 index 0000000000000000000000000000000000000000..bab1dc6fb5bbce2da76fbe48bdf4d70c18d6cd91 --- /dev/null +++ b/mailbox-android/src/main/res/layout/splash.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <ImageView + android:src="@drawable/splash_screen" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_margin="@dimen/margin_xxlarge" + android:contentDescription="@string/app_name"/> + +</FrameLayout> diff --git a/mailbox-android/src/main/res/layout/text_avatar_view.xml b/mailbox-android/src/main/res/layout/text_avatar_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..de7f3b868f670a1d08e76a6da76d11305622dbcb --- /dev/null +++ b/mailbox-android/src/main/res/layout/text_avatar_view.xml @@ -0,0 +1,44 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + tools:showIn="@layout/list_item_forum"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarBackground" + style="@style/BriarAvatar" + android:layout_width="@dimen/avatar_forum_size" + android:layout_height="@dimen/avatar_forum_size" + android:layout_gravity="bottom|left" + android:src="@color/briar_button_positive"/> + + <android.support.v7.widget.AppCompatTextView + android:id="@+id/textAvatarView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginRight="@dimen/listitem_picture_frame_offset_horizontal" + android:layout_marginTop="@dimen/listitem_picture_frame_offset_vertical" + android:maxLength="1" + android:shadowColor="@color/forum_avatar_shadow" + android:shadowDx="0" + android:shadowDy="1.5" + android:shadowRadius="1.5" + android:textColor="@color/briar_text_primary_inverse" + android:textSize="@dimen/avatar_text_size" + tools:text="T"/> + + <TextView + android:id="@+id/unreadCountView" + android:layout_width="wrap_content" + android:layout_height="@dimen/unread_bubble_size" + android:layout_gravity="right|top" + android:background="@drawable/bubble" + android:gravity="center" + android:minWidth="@dimen/unread_bubble_size" + android:textColor="@color/briar_text_primary_inverse" + android:textSize="@dimen/unread_bubble_text_size" + android:textStyle="bold" + tools:text="12"/> + +</merge> diff --git a/mailbox-android/src/main/res/layout/text_input_view.xml b/mailbox-android/src/main/res/layout/text_input_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..f5d989acce24500684aa848205c359d7f5fff004 --- /dev/null +++ b/mailbox-android/src/main/res/layout/text_input_view.xml @@ -0,0 +1,62 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:showIn="@layout/activity_conversation"> + + <View + style="@style/Divider.Horizontal" + android:layout_alignParentTop="true"/> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@color/card_background"> + + <org.thoughtcrime.securesms.components.emoji.EmojiToggle + android:id="@+id/emoji_toggle" + android:layout_width="@dimen/text_input_height" + android:layout_height="@dimen/text_input_height" + android:background="?attr/selectableItemBackground" + android:padding="@dimen/margin_small" + android:scaleType="center" + app:tint="?attr/colorControlNormal"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiEditText + android:id="@+id/input_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:background="@android:color/transparent" + android:inputType="textMultiLine|textCapSentences" + android:maxLines="3" + android:minHeight="@dimen/text_input_height" + android:textColor="?android:attr/textColorPrimary" + android:textColorHint="?android:attr/textColorTertiary"/> + + <android.support.v7.widget.AppCompatImageButton + android:id="@+id/btn_send" + android:layout_width="@dimen/text_input_height" + android:layout_height="@dimen/text_input_height" + android:background="?attr/selectableItemBackground" + android:clickable="true" + android:contentDescription="@string/send" + android:enabled="false" + android:focusable="true" + android:padding="@dimen/margin_small" + android:src="@drawable/social_send_now_white" + app:tint="@color/briar_accent"/> + + </LinearLayout> + + <org.thoughtcrime.securesms.components.emoji.EmojiDrawer + android:id="@+id/emoji_drawer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_below="@+id/input_text" + android:visibility="gone"/> + +</merge> diff --git a/mailbox-android/src/main/res/layout/text_input_view_large.xml b/mailbox-android/src/main/res/layout/text_input_view_large.xml new file mode 100644 index 0000000000000000000000000000000000000000..60155836cc4597dcf6f6909bb0f487c601dba030 --- /dev/null +++ b/mailbox-android/src/main/res/layout/text_input_view_large.xml @@ -0,0 +1,73 @@ +<?xml version="1.0" encoding="utf-8"?> +<merge + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + tools:showIn="@layout/fragment_reblog"> + + <android.support.v7.widget.CardView + android:id="@+id/input_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/margin_medium" + android:layout_marginLeft="@dimen/margin_medium" + android:layout_marginRight="@dimen/margin_medium" + android:layout_marginStart="@dimen/margin_medium" + android:layout_marginTop="@dimen/margin_medium" + android:elevation="@dimen/cardview_default_elevation" + android:minHeight="@dimen/text_input_height" + app:cardCornerRadius="0dp" + app:cardUseCompatPadding="false"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="horizontal"> + + <org.thoughtcrime.securesms.components.emoji.EmojiToggle + android:id="@+id/emoji_toggle" + android:layout_width="@dimen/text_input_height" + android:layout_height="@dimen/text_input_height" + android:layout_gravity="bottom" + android:background="?attr/selectableItemBackground" + android:padding="@dimen/margin_small" + android:scaleType="center" + app:tint="?attr/colorControlNormal"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiEditText + android:id="@+id/input_text" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:background="@android:color/transparent" + android:gravity="bottom" + android:inputType="textMultiLine|textLongMessage|textCapSentences|textAutoCorrect" + android:minHeight="@dimen/text_input_height" + android:paddingBottom="10dp" + android:paddingEnd="@dimen/margin_small" + android:paddingRight="@dimen/margin_small" + android:paddingTop="@dimen/margin_small" + android:textColor="?android:attr/textColorPrimary" + android:textColorHint="?android:attr/textColorTertiary" + tools:ignore="RtlSymmetry"/> + + </LinearLayout> + + </android.support.v7.widget.CardView> + + <Button + android:id="@+id/btn_send" + style="@style/BriarButton" + android:layout_marginEnd="@dimen/margin_small" + android:layout_marginLeft="@dimen/margin_small" + android:layout_marginRight="@dimen/margin_small" + android:layout_marginStart="@dimen/margin_small"/> + + <org.thoughtcrime.securesms.components.emoji.EmojiDrawer + android:id="@+id/emoji_drawer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone"/> + +</merge> diff --git a/mailbox-android/src/main/res/layout/toolbar.xml b/mailbox-android/src/main/res/layout/toolbar.xml new file mode 100644 index 0000000000000000000000000000000000000000..9a17ba10dd162b140bba9db8fbe1b307e621e47b --- /dev/null +++ b/mailbox-android/src/main/res/layout/toolbar.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<android.support.design.widget.AppBarLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + style="@style/BriarToolbar" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + +</android.support.design.widget.AppBarLayout> diff --git a/mailbox-android/src/main/res/layout/transports_list.xml b/mailbox-android/src/main/res/layout/transports_list.xml new file mode 100644 index 0000000000000000000000000000000000000000..70ff190c6cfbb4d8be6c97dc3d569850cafadf50 --- /dev/null +++ b/mailbox-android/src/main/res/layout/transports_list.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + tools:showIn="@layout/navigation_menu"> + + <View style="@style/Divider.Horizontal"/> + + <GridView + android:id="@+id/transportsView" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:listSelector="@android:color/transparent" + android:numColumns="3" + android:padding="@dimen/margin_medium" + tools:listitem="@layout/list_item_transport"/> + +</LinearLayout> diff --git a/mailbox-android/src/main/res/menu/dev_report_actions.xml b/mailbox-android/src/main/res/menu/dev_report_actions.xml new file mode 100644 index 0000000000000000000000000000000000000000..0567cb5d69e956c9309c6274fe93fb8ffd822d38 --- /dev/null +++ b/mailbox-android/src/main/res/menu/dev_report_actions.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_send_report" + android:icon="@drawable/social_send_now_white" + android:title="@string/send_report" + app:showAsAction="always"/> + +</menu> \ No newline at end of file diff --git a/mailbox-android/src/main/res/menu/help_action.xml b/mailbox-android/src/main/res/menu/help_action.xml new file mode 100644 index 0000000000000000000000000000000000000000..a0e4c3d2c8a3a82923caa7a5ba2cdd723605c3d4 --- /dev/null +++ b/mailbox-android/src/main/res/menu/help_action.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_help" + android:icon="@drawable/ic_info_white" + android:title="@string/more_info" + app:showAsAction="always"/> + +</menu> \ No newline at end of file diff --git a/mailbox-android/src/main/res/menu/navigation_drawer.xml b/mailbox-android/src/main/res/menu/navigation_drawer.xml new file mode 100644 index 0000000000000000000000000000000000000000..178b7f74e7f03027102e7601af52a3bf8ab64bee --- /dev/null +++ b/mailbox-android/src/main/res/menu/navigation_drawer.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu + xmlns:android="http://schemas.android.com/apk/res/android"> + + <group android:checkableBehavior="single"> + <item + android:id="@+id/nav_btn_overview" + android:title="@string/overview"/> + <item + android:id="@+id/nav_btn_settings" + android:icon="@drawable/ic_settings_black_24dp" + android:title="@string/settings_button"/> + <item + android:id="@+id/nav_btn_signout" + android:icon="@drawable/ic_signout_black_24dp" + android:title="@string/sign_out_button"/> + </group> + +</menu> \ No newline at end of file diff --git a/mailbox-android/src/main/res/menu/overview_actions.xml b/mailbox-android/src/main/res/menu/overview_actions.xml new file mode 100644 index 0000000000000000000000000000000000000000..c5d0dc76ab40cd91b89391236e33f809d04f09a2 --- /dev/null +++ b/mailbox-android/src/main/res/menu/overview_actions.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="utf-8"?> +<menu + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + + <item + android:id="@+id/action_add_contact" + android:icon="@drawable/ic_add_white" + android:title="@string/add_contact_title" + app:showAsAction="ifRoom"/> + +</menu> \ No newline at end of file diff --git a/mailbox-android/src/main/res/mipmap-hdpi/ic_launcher_round.png b/mailbox-android/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..2a1df44f1f3d87ffdb578f3a750301fde865715d Binary files /dev/null and b/mailbox-android/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/mailbox-android/src/main/res/mipmap-mdpi/ic_launcher_round.png b/mailbox-android/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..a0c366016e1d532a0725f6a403509cd03da5e5df Binary files /dev/null and b/mailbox-android/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/mailbox-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/mailbox-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..50071b876f47b1c86493b07120e5891161db7bf9 Binary files /dev/null and b/mailbox-android/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/mailbox-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/mailbox-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..1ef39ff9e2633073e3676a17f84f9394a331aeed Binary files /dev/null and b/mailbox-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/mailbox-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/mailbox-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..4cad5b99332a00880d4241be371f926429198b24 Binary files /dev/null and b/mailbox-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/mailbox-android/src/main/res/values/arrays.xml b/mailbox-android/src/main/res/values/arrays.xml new file mode 100644 index 0000000000000000000000000000000000000000..d4001a3c9ae19ca54dbb014535dc2273c07508ea --- /dev/null +++ b/mailbox-android/src/main/res/values/arrays.xml @@ -0,0 +1,70 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string-array name="boolean_array"> + <item>true</item> + <item>false</item> + </string-array> + <string-array name="bt_setting_names"> + <item>@string/bluetooth_setting_enabled</item> + <item>@string/bluetooth_setting_disabled</item> + </string-array> + <string-array name="tor_network_setting_names"> + <item>@string/tor_network_setting_never</item> + <item>@string/tor_network_setting_wifi</item> + <item>@string/tor_network_setting_always</item> + </string-array> + <string-array name="tor_network_setting_values"> + <item>0</item> + <item>1</item> + <item>2</item> + </string-array> + <string-array name="pref_language_values"> + <item>default</item> + <item>en-US</item> + <item>ast</item> + <item>bg</item> + <item>br</item> + <item>ca</item> + <item>cs</item> + <item>de</item> + <item>es</item> + <item>eu</item> + <item>fa</item> + <item>fi</item> + <item>fr</item> + <item>gl</item> + <item>he</item> + <item>hi</item> + <item>it</item> + <item>ja</item> + <item>ms</item> + <item>nb</item> + <item>nl</item> + <item>oc</item> + <item>pl</item> + <item>pt-BR</item> + <item>ro</item> + <item>ru</item> + <item>sq</item> + <item>sr</item> + <item>sv</item> + <item>tr</item> + <item>zh-CN</item> + </string-array> + <string-array name="pref_theme_entries"> + <item>@string/pref_theme_light</item> + <item>@string/pref_theme_dark</item> + <item>@string/pref_theme_auto</item> + <item>@string/pref_theme_system</item> + </string-array> + <string name="pref_theme_light_value">light</string> + <string name="pref_theme_dark_value">dark</string> + <string name="pref_theme_auto_value">auto</string> + <string name="pref_theme_system_value">system</string> + <string-array name="pref_theme_values"> + <item>@string/pref_theme_light_value</item> + <item>@string/pref_theme_dark_value</item> + <item>@string/pref_theme_auto_value</item> + <item>@string/pref_theme_system_value</item> + </string-array> +</resources> diff --git a/mailbox-android/src/main/res/values/attrs.xml b/mailbox-android/src/main/res/values/attrs.xml new file mode 100644 index 0000000000000000000000000000000000000000..8655d2126fc21b9c7a3fcc732e7c4ea30b7c0486 --- /dev/null +++ b/mailbox-android/src/main/res/values/attrs.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <declare-styleable name="BriarRecyclerView"> + <attr name="scrollToEnd" format="boolean" /> + <attr name="emptyText" format="string" /> + </declare-styleable> + + <declare-styleable name="AuthorView"> + <attr name="persona" format="enum"> + <enum name="normal" value="0"/> + <enum name="reblogger" value="1"/> + <enum name="commenter" value="2"/> + <enum name="list" value="3"/> + <enum name="rss_feed" value="4"/> + <enum name="rss_feed_reblogged" value="5"/> + </attr> + </declare-styleable> + + <declare-styleable name="TextInputView"> + <attr name="hint" format="string"/> + </declare-styleable> + + <declare-styleable name="LargeTextInputView"> + <attr name="buttonText" format="string"/> + <attr name="maxLines" format="integer"/> + <attr name="fillHeight" format="boolean"/> + </declare-styleable> + + <declare-styleable name="UnreadMessageButton"> + <attr name="direction" format="enum"> + <enum name="up" value="0"/> + <enum name="down" value="1"/> + </attr> + </declare-styleable> + +</resources> \ No newline at end of file diff --git a/mailbox-android/src/main/res/values/color.xml b/mailbox-android/src/main/res/values/color.xml new file mode 100644 index 0000000000000000000000000000000000000000..5087a56666c71b282d9e232342e717f40649a05f --- /dev/null +++ b/mailbox-android/src/main/res/values/color.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="briar_blue">#2D3E50</color> + <color name="briar_blue_dark">#0F1720</color> + <color name="briar_blue_light">#4F6C8C</color> + <color name="briar_blue_light2">#5a7da3</color> + <color name="briar_green_light">#95D220</color> + <color name="briar_link">#06B9FF</color> + + <color name="window_background">#fffafafa</color> + <color name="card_background">@color/cardview_light_background</color> + <color name="action_bar_text">#FFFFFF</color> + <color name="private_message_date_inverse">#e0e0e0</color> + <color name="forum_avatar_shadow">#99000000</color> + + <color name="briar_primary">@color/briar_blue</color> + <color name="briar_primary_dark">@color/briar_blue_dark</color> + + <color name="briar_accent">@color/briar_blue</color> + + <color name="color_primary">#dd000000</color> + + <!-- text colors --> + <color name="briar_text_link">@color/briar_link</color> + <color name="briar_text_primary">#df000000</color> + <color name="briar_text_primary_inverse">#ffffff</color> + <color name="briar_text_secondary_inverse">#b4ffffff</color> + <color name="briar_text_tertiary_inverse">#80ffffff</color> + <color name="preference_category">@color/briar_blue_light</color> + + <color name="briar_button_positive">@color/briar_link</color> + <color name="briar_button_negative">#ff0000</color> + <color name="briar_button_text_disabled">#28000000</color> + <color name="briar_warning_background">#ff0000</color> + + <color name="thread_indicator">#9e9e9e</color> + <color name="thread_item_background">#eceff1</color> + <color name="thread_item_highlight">#ffffff</color> + <color name="divider">#c1c1c1</color> + + <color name="spinner_border">#61000000</color> <!-- 38% Black --> +</resources> \ No newline at end of file diff --git a/mailbox-android/src/main/res/values/dimens.xml b/mailbox-android/src/main/res/values/dimens.xml new file mode 100644 index 0000000000000000000000000000000000000000..d8327da7dacd7befca20ddab2d593631f3a19245 --- /dev/null +++ b/mailbox-android/src/main/res/values/dimens.xml @@ -0,0 +1,65 @@ +<resources> + + <!-- Default screen margins, per the Android Design guidelines. --> + <dimen name="margin_activity_horizontal">16dp</dimen> + <dimen name="margin_activity_vertical">16dp</dimen> + + <dimen name="margin_separator">1dp</dimen> + <dimen name="margin_tiny">2dp</dimen> + <dimen name="margin_small">4dp</dimen> + <dimen name="margin_medium">8dp</dimen> + <dimen name="margin_large">16dp</dimen> + <dimen name="margin_xlarge">32dp</dimen> + <dimen name="margin_xxlarge">64dp</dimen> + + <!-- v2 dimens --> + <dimen name="text_size_tiny">12sp</dimen> + <dimen name="text_size_small">14sp</dimen> + <dimen name="text_size_medium">16sp</dimen> + <dimen name="text_size_large">20sp</dimen> + <dimen name="text_size_xlarge">34sp</dimen> + + <dimen name="listitem_horizontal_margin">16dp</dimen> + <dimen name="listitem_vertical_margin">10dp</dimen> + <dimen name="listitem_height_one_line_avatar">56dp</dimen> + <dimen name="listitem_picture_size">48dp</dimen> + <dimen name="listitem_picture_size_small">23dp</dimen> + <dimen name="listitem_picture_frame_size">51dp</dimen> + <dimen name="listitem_picture_frame_offset_horizontal">1dp</dimen> + <dimen name="listitem_picture_frame_offset_vertical">2dp</dimen> + <dimen name="listitem_selectable_picture_size">40dp</dimen> + <dimen name="listitem_group_member_indentation">32dp</dimen> + <dimen name="avatar_forum_size">48dp</dimen> + <dimen name="avatar_border_width">2dp</dimen> + <dimen name="avatar_text_size">30sp</dimen> + <dimen name="button_size">48dp</dimen> + + <dimen name="unread_bubble_text_size">12sp</dimen> + <dimen name="unread_bubble_padding_horizontal">6dp</dimen> + <dimen name="unread_bubble_size">19dp</dimen> + + <dimen name="message_bubble_margin_tail">3dp</dimen> + <dimen name="message_bubble_margin_non_tail">30dp</dimen> + <dimen name="message_bubble_timestamp_margin">7dp</dimen> + <dimen name="forum_nested_line_width">2dp</dimen> + <dimen name="forum_nested_indicator">24dp</dimen> + + <dimen name="blogs_avatar_normal_size">30dp</dimen> + <dimen name="blogs_avatar_icon_size">15dp</dimen> + <dimen name="blogs_avatar_comment_size">20dp</dimen> + + <!-- Emoji --> + <dimen name="text_input_height">42dp</dimen> + <dimen name="conversation_item_body_text_size">16sp</dimen> + <dimen name="emoji_drawer_size">32sp</dimen> + <dimen name="emoji_drawer_indicator_height">2dp</dimen> + <dimen name="emoji_drawer_item_padding">5dp</dimen> + <dimen name="emoji_drawer_left_right_padding">2dp</dimen> + + <!-- Keyboard Sizes --> + <dimen name="min_keyboard_size">50dp</dimen> + <dimen name="default_custom_keyboard_size">220dp</dimen> + <dimen name="min_custom_keyboard_size">110dp</dimen> + <dimen name="min_custom_keyboard_top_margin">170dp</dimen> + +</resources> diff --git a/mailbox-android/src/main/res/values/emoji.xml b/mailbox-android/src/main/res/values/emoji.xml new file mode 100644 index 0000000000000000000000000000000000000000..ab8e62d011f28e504cc41b3f8c78d371226021d0 --- /dev/null +++ b/mailbox-android/src/main/res/values/emoji.xml @@ -0,0 +1,1352 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2015 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<resources> + <array + name="emoji_symbols" + format="string"> + <item>1f3c1</item> + <item>1f3f3</item> + <item>1f3f4</item> + <item>1f6a9</item> + <item>1f3e7</item> + <item>1f6ae</item> + <item>1f6b0</item> + <item>267f</item> + <item>1f6b9</item> + <item>1f6ba</item> + <item>1f6bb</item> + <item>1f6bc</item> + <item>1f6be</item> + <item>1f6c2</item> + <item>1f6c3</item> + <item>1f6c4</item> + <item>1f6c5</item> + <item>26a0</item> + <item>1f6b8</item> + <item>26d4</item> + <item>1f6ab</item> + <item>1f6b3</item> + <item>1f6ad</item> + <item>1f6af</item> + <item>1f6b1</item> + <item>1f6b7</item> + <item>2622</item> + <item>2623</item> + <item>2b06</item> + <item>2197</item> + <item>27a1</item> + <item>2198</item> + <item>2b07</item> + <item>2199</item> + <item>2b05</item> + <item>2196</item> + <item>2195</item> + <item>2194</item> + <item>21a9</item> + <item>21aa</item> + <item>2934</item> + <item>2935</item> + <item>1f503</item> + <item>1f504</item> + <item>1f519</item> + <item>1f51a</item> + <item>1f51b</item> + <item>1f51c</item> + <item>1f51d</item> + <item>1f6d0</item> + <item>269b</item> + <item>1f549</item> + <item>2721</item> + <item>2638</item> + <item>262f</item> + <item>271d</item> + <item>2626</item> + <item>262a</item> + <item>262e</item> + <item>1f54e</item> + <item>1f52f</item> + <item>267b</item> + <item>1f4db</item> + <item>269c</item> + <item>1f530</item> + <item>1f531</item> + <item>2b55</item> + <item>2705</item> + <item>2611</item> + <item>2714</item> + <item>2716</item> + <item>274c</item> + <item>274e</item> + <item>2795</item> + <item>2796</item> + <item>2797</item> + <item>27b0</item> + <item>27bf</item> + <item>303d</item> + <item>2733</item> + <item>2734</item> + <item>2747</item> + <item>1f4b1</item> + <item>1f4b2</item> + <item>203c</item> + <item>2049</item> + <item>2753</item> + <item>2754</item> + <item>2755</item> + <item>2757</item> + <item>3030</item> + <item>a9</item> + <item>ae</item> + <item>2122</item> + <item>2648</item> + <item>2649</item> + <item>264a</item> + <item>264b</item> + <item>264c</item> + <item>264d</item> + <item>264e</item> + <item>264f</item> + <item>2650</item> + <item>2651</item> + <item>2652</item> + <item>2653</item> + <item>26ce</item> + <item>1f500</item> + <item>1f501</item> + <item>1f502</item> + <item>25b6</item> + <item>23e9</item> + <item>23ed</item> + <item>23ef</item> + <item>25c0</item> + <item>23ea</item> + <item>23ee</item> + <item>1f53c</item> + <item>23eb</item> + <item>1f53d</item> + <item>23ec</item> + <item>23f8</item> + <item>23f9</item> + <item>23fa</item> + <item>23cf</item> + <item>1f3a6</item> + <item>1f505</item> + <item>1f506</item> + <item>1f4f6</item> + <item>1f4f5</item> + <item>1f4f3</item> + <item>1f4f4</item> + <item>23,20e3</item> + <item>2a,20e3</item> + <item>30,20e3</item> + <item>31,20e3</item> + <item>32,20e3</item> + <item>33,20e3</item> + <item>34,20e3</item> + <item>35,20e3</item> + <item>36,20e3</item> + <item>37,20e3</item> + <item>38,20e3</item> + <item>39,20e3</item> + <item>1f51f</item> + <item>1f4af</item> + <item>1f51e</item> + <item>1f520</item> + <item>1f521</item> + <item>1f522</item> + <item>1f523</item> + <item>1f524</item> + <item>1f170</item> + <item>1f18e</item> + <item>1f171</item> + <item>1f191</item> + <item>1f192</item> + <item>1f193</item> + <item>2139</item> + <item>1f194</item> + <item>24c2</item> + <item>1f195</item> + <item>1f196</item> + <item>1f17e</item> + <item>1f197</item> + <item>1f17f</item> + <item>1f198</item> + <item>1f199</item> + <item>1f19a</item> + <item>1f201</item> + <item>1f202</item> + <item>1f237</item> + <item>1f236</item> + <item>1f22f</item> + <item>1f250</item> + <item>1f239</item> + <item>1f21a</item> + <item>1f232</item> + <item>1f251</item> + <item>1f238</item> + <item>1f234</item> + <item>1f233</item> + <item>3297</item> + <item>3299</item> + <item>1f23a</item> + <item>1f235</item> + <item>25aa</item> + <item>25ab</item> + <item>25fb</item> + <item>25fc</item> + <item>25fd</item> + <item>25fe</item> + <item>2b1b</item> + <item>2b1c</item> + <item>1f536</item> + <item>1f537</item> + <item>1f538</item> + <item>1f539</item> + <item>1f53a</item> + <item>1f53b</item> + <item>1f4a0</item> + <item>1f518</item> + <item>1f532</item> + <item>1f533</item> + <item>26aa</item> + <item>26ab</item> + <item>1f534</item> + <item>1f535</item> + </array> + <array + name="emoji_animals_nature" + format="string"> + <item>1f435</item> + <item>1f412</item> + <item>1f436</item> + <item>1f415</item> + <item>1f429</item> + <item>1f43a</item> + <item>1f431</item> + <item>1f408</item> + <item>1f981</item> + <item>1f42f</item> + <item>1f405</item> + <item>1f406</item> + <item>1f434</item> + <item>1f40e</item> + <item>1f984</item> + <item>1f42e</item> + <item>1f402</item> + <item>1f403</item> + <item>1f404</item> + <item>1f437</item> + <item>1f416</item> + <item>1f417</item> + <item>1f43d</item> + <item>1f40f</item> + <item>1f411</item> + <item>1f410</item> + <item>1f42a</item> + <item>1f42b</item> + <item>1f418</item> + <item>1f42d</item> + <item>1f401</item> + <item>1f400</item> + <item>1f439</item> + <item>1f430</item> + <item>1f407</item> + <item>1f43f</item> + <item>1f43b</item> + <item>1f428</item> + <item>1f43c</item> + <item>1f43e</item> + <item>1f983</item> + <item>1f414</item> + <item>1f413</item> + <item>1f423</item> + <item>1f424</item> + <item>1f425</item> + <item>1f426</item> + <item>1f427</item> + <item>1f54a</item> + <item>1f438</item> + <item>1f40a</item> + <item>1f422</item> + <item>1f40d</item> + <item>1f432</item> + <item>1f409</item> + <item>1f433</item> + <item>1f40b</item> + <item>1f42c</item> + <item>1f41f</item> + <item>1f420</item> + <item>1f421</item> + <item>1f419</item> + <item>1f41a</item> + <item>1f980</item> + <item>1f40c</item> + <item>1f41b</item> + <item>1f41c</item> + <item>1f41d</item> + <item>1f41e</item> + <item>1f577</item> + <item>1f578</item> + <item>1f982</item> + <item>1f490</item> + <item>1f338</item> + <item>1f4ae</item> + <item>1f3f5</item> + <item>1f339</item> + <item>1f33a</item> + <item>1f33b</item> + <item>1f33c</item> + <item>1f337</item> + <item>2618</item> + <item>1f331</item> + <item>1f332</item> + <item>1f333</item> + <item>1f334</item> + <item>1f335</item> + <item>1f33e</item> + <item>1f33f</item> + <item>1f340</item> + <item>1f341</item> + <item>1f342</item> + <item>1f343</item> + </array> + <array + name="emoji_smiley_people" + format="string"> + <item>1f600</item> + <item>1f601</item> + <item>1f602</item> + <item>1f603</item> + <item>1f604</item> + <item>1f605</item> + <item>1f606</item> + <item>1f609</item> + <item>1f60a</item> + <item>1f60b</item> + <item>1f60e</item> + <item>1f60d</item> + <item>1f618</item> + <item>1f617</item> + <item>1f619</item> + <item>1f61a</item> + <item>263a</item> + <item>1f642</item> + <item>1f917</item> + <item>1f607</item> + <item>1f914</item> + <item>1f610</item> + <item>1f611</item> + <item>1f636</item> + <item>1f644</item> + <item>1f60f</item> + <item>1f623</item> + <item>1f625</item> + <item>1f62e</item> + <item>1f910</item> + <item>1f62f</item> + <item>1f62a</item> + <item>1f62b</item> + <item>1f634</item> + <item>1f60c</item> + <item>1f913</item> + <item>1f61b</item> + <item>1f61c</item> + <item>1f61d</item> + <item>2639</item> + <item>1f641</item> + <item>1f612</item> + <item>1f613</item> + <item>1f614</item> + <item>1f615</item> + <item>1f616</item> + <item>1f643</item> + <item>1f637</item> + <item>1f912</item> + <item>1f915</item> + <item>1f911</item> + <item>1f632</item> + <item>1f61e</item> + <item>1f61f</item> + <item>1f624</item> + <item>1f622</item> + <item>1f62d</item> + <item>1f626</item> + <item>1f627</item> + <item>1f628</item> + <item>1f629</item> + <item>1f62c</item> + <item>1f630</item> + <item>1f631</item> + <item>1f633</item> + <item>1f635</item> + <item>1f621</item> + <item>1f620</item> + <item>1f608</item> + <item>1f47f</item> + <item>1f479</item> + <item>1f47a</item> + <item>1f480</item> + <item>2620</item> + <item>1f47b</item> + <item>1f47d</item> + <item>1f47e</item> + <item>1f916</item> + <item>1f4a9</item> + <item>1f63a</item> + <item>1f638</item> + <item>1f639</item> + <item>1f63b</item> + <item>1f63c</item> + <item>1f63d</item> + <item>1f640</item> + <item>1f63f</item> + <item>1f63e</item> + <item>1f648</item> + <item>1f649</item> + <item>1f64a</item> + <item>1f466</item> + <item>1f467</item> + <item>1f468</item> + <item>1f469</item> + <item>1f474</item> + <item>1f475</item> + <item>1f476</item> + <item>1f471</item> + <item>1f46e</item> + <item>1f472</item> + <item>1f473</item> + <item>1f477</item> + <item>26d1</item> + <item>1f478</item> + <item>1f482</item> + <item>1f575</item> + <item>1f385</item> + <item>1f47c</item> + <item>1f46f</item> + <item>1f486</item> + <item>1f487</item> + <item>1f470</item> + <item>1f64d</item> + <item>1f64e</item> + <item>1f645</item> + <item>1f646</item> + <item>1f481</item> + <item>1f64b</item> + <item>1f647</item> + <item>1f64c</item> + <item>1f64f</item> + <item>1f5e3</item> + <item>1f464</item> + <item>1f465</item> + <item>1f6b6</item> + <item>1f3c3</item> + <item>1f483</item> + <item>1f574</item> + <item>1f46b</item> + <item>1f46c</item> + <item>1f46d</item> + <item>1f48f</item> + <item>1f468,200d,2764,fe0f,200d,1f48b,200d,1f468</item> + <item>1f469,200d,2764,fe0f,200d,1f48b,200d,1f469</item> + <item>1f491</item> + <item>1f468,200d,2764,fe0f,200d,1f468</item> + <item>1f469,200d,2764,fe0f,200d,1f469</item> + <item>1f46a</item> + <item>1f468,200d,1f468,200d,1f466</item> + <item>1f468,200d,1f468,200d,1f466,200d,1f466</item> + <item>1f468,200d,1f468,200d,1f467</item> + <item>1f468,200d,1f468,200d,1f467,200d,1f466</item> + <item>1f468,200d,1f468,200d,1f467,200d,1f467</item> + <item>1f468,200d,1f469,200d,1f466</item> + <item>1f468,200d,1f469,200d,1f466,200d,1f466</item> + <item>1f468,200d,1f469,200d,1f467</item> + <item>1f468,200d,1f469,200d,1f467,200d,1f466</item> + <item>1f468,200d,1f469,200d,1f467,200d,1f467</item> + <item>1f469,200d,1f469,200d,1f466</item> + <item>1f469,200d,1f469,200d,1f466,200d,1f466</item> + <item>1f469,200d,1f469,200d,1f467</item> + <item>1f469,200d,1f469,200d,1f467,200d,1f466</item> + <item>1f469,200d,1f469,200d,1f467,200d,1f467</item> + <item>1f3fb</item> + <item>1f3fc</item> + <item>1f3fd</item> + <item>1f3fe</item> + <item>1f3ff</item> + <item>1f4aa</item> + <item>1f448</item> + <item>1f449</item> + <item>261d</item> + <item>1f446</item> + <item>1f595</item> + <item>1f447</item> + <item>270c</item> + <item>1f596</item> + <item>1f918</item> + <item>1f590</item> + <item>270a</item> + <item>270b</item> + <item>1f44a</item> + <item>1f44c</item> + <item>1f44d</item> + <item>1f44e</item> + <item>1f44b</item> + <item>1f44f</item> + <item>1f450</item> + <item>270d</item> + <item>1f485</item> + <item>1f442</item> + <item>1f443</item> + <item>1f463</item> + <item>1f440</item> + <item>1f441</item> + <item>1f445</item> + <item>1f444</item> + <item>1f48b</item> + <item>1f498</item> + <item>2764</item> + <item>1f493</item> + <item>1f494</item> + <item>1f495</item> + <item>1f496</item> + <item>1f497</item> + <item>1f499</item> + <item>1f49a</item> + <item>1f49b</item> + <item>1f49c</item> + <item>1f49d</item> + <item>1f49e</item> + <item>1f49f</item> + <item>2763</item> + <item>1f48c</item> + <item>1f4a4</item> + <item>1f4a2</item> + <item>1f4a3</item> + <item>1f4a5</item> + <item>1f4a6</item> + <item>1f4a8</item> + <item>1f4ab</item> + <item>1f4ac</item> + <item>1f5e8</item> + <item>1f5ef</item> + <item>1f4ad</item> + <item>1f441,200d,1f5e8</item> + <item>1f573</item> + <item>1f453</item> + <item>1f576</item> + <item>1f454</item> + <item>1f455</item> + <item>1f456</item> + <item>1f457</item> + <item>1f458</item> + <item>1f459</item> + <item>1f45a</item> + <item>1f45b</item> + <item>1f45c</item> + <item>1f45d</item> + <item>1f6cd</item> + <item>1f392</item> + <item>1f45e</item> + <item>1f45f</item> + <item>1f460</item> + <item>1f461</item> + <item>1f462</item> + <item>1f451</item> + <item>1f452</item> + <item>1f3a9</item> + <item>1f393</item> + <item>1f4ff</item> + <item>1f484</item> + <item>1f48d</item> + <item>1f48e</item> + </array> + <array + name="emoji_food_drink" + format="string"> + <item>1f347</item> + <item>1f348</item> + <item>1f349</item> + <item>1f34a</item> + <item>1f34b</item> + <item>1f34c</item> + <item>1f34d</item> + <item>1f34e</item> + <item>1f34f</item> + <item>1f350</item> + <item>1f351</item> + <item>1f352</item> + <item>1f353</item> + <item>1f345</item> + <item>1f346</item> + <item>1f33d</item> + <item>1f336</item> + <item>1f344</item> + <item>1f330</item> + <item>1f35e</item> + <item>1f9c0</item> + <item>1f356</item> + <item>1f357</item> + <item>1f354</item> + <item>1f35f</item> + <item>1f355</item> + <item>1f32d</item> + <item>1f32e</item> + <item>1f32f</item> + <item>1f37f</item> + <item>1f372</item> + <item>1f371</item> + <item>1f358</item> + <item>1f359</item> + <item>1f35a</item> + <item>1f35b</item> + <item>1f35c</item> + <item>1f35d</item> + <item>1f360</item> + <item>1f362</item> + <item>1f363</item> + <item>1f364</item> + <item>1f365</item> + <item>1f361</item> + <item>1f366</item> + <item>1f367</item> + <item>1f368</item> + <item>1f369</item> + <item>1f36a</item> + <item>1f382</item> + <item>1f370</item> + <item>1f36b</item> + <item>1f36c</item> + <item>1f36d</item> + <item>1f36e</item> + <item>1f36f</item> + <item>1f37c</item> + <item>2615</item> + <item>1f375</item> + <item>1f376</item> + <item>1f37e</item> + <item>1f377</item> + <item>1f378</item> + <item>1f379</item> + <item>1f37a</item> + <item>1f37b</item> + <item>1f37d</item> + <item>1f374</item> + <item>1f373</item> + <item>1f3fa</item> + </array> + <array + name="emoji_objects" + format="string"> + <item>1f507</item> + <item>1f508</item> + <item>1f509</item> + <item>1f50a</item> + <item>1f4e2</item> + <item>1f4e3</item> + <item>1f4ef</item> + <item>1f514</item> + <item>1f515</item> + <item>1f3bc</item> + <item>1f3b5</item> + <item>1f3b6</item> + <item>1f399</item> + <item>1f39a</item> + <item>1f39b</item> + <item>1f3a4</item> + <item>1f3a7</item> + <item>1f3b7</item> + <item>1f3b8</item> + <item>1f3b9</item> + <item>1f3ba</item> + <item>1f3bb</item> + <item>1f4fb</item> + <item>1f4f1</item> + <item>1f4f2</item> + <item>260e</item> + <item>1f4de</item> + <item>1f4df</item> + <item>1f4e0</item> + <item>1f50b</item> + <item>1f50c</item> + <item>1f4bb</item> + <item>1f5a5</item> + <item>1f5a8</item> + <item>2328</item> + <item>1f5b1</item> + <item>1f5b2</item> + <item>1f4bd</item> + <item>1f4be</item> + <item>1f4bf</item> + <item>1f4c0</item> + <item>1f3a5</item> + <item>1f3ac</item> + <item>1f4fd</item> + <item>1f4fa</item> + <item>1f4f7</item> + <item>1f4f8</item> + <item>1f4f9</item> + <item>1f4fc</item> + <item>1f50d</item> + <item>1f50e</item> + <item>1f52c</item> + <item>1f52d</item> + <item>1f4e1</item> + <item>1f56f</item> + <item>1f4a1</item> + <item>1f526</item> + <item>1f3ee</item> + <item>1f4d4</item> + <item>1f4d5</item> + <item>1f4d6</item> + <item>1f4d7</item> + <item>1f4d8</item> + <item>1f4d9</item> + <item>1f4da</item> + <item>1f4d3</item> + <item>1f4d2</item> + <item>1f4c3</item> + <item>1f4dc</item> + <item>1f4c4</item> + <item>1f4f0</item> + <item>1f5de</item> + <item>1f4d1</item> + <item>1f516</item> + <item>1f4b0</item> + <item>1f4b4</item> + <item>1f4b5</item> + <item>1f4b6</item> + <item>1f4b7</item> + <item>1f4b8</item> + <item>1f4b3</item> + <item>1f4b9</item> + <item>2709</item> + <item>1f4e7</item> + <item>1f4e8</item> + <item>1f4e9</item> + <item>1f4e4</item> + <item>1f4e5</item> + <item>1f4e6</item> + <item>1f4eb</item> + <item>1f4ea</item> + <item>1f4ec</item> + <item>1f4ed</item> + <item>1f4ee</item> + <item>1f5f3</item> + <item>270f</item> + <item>2712</item> + <item>1f58b</item> + <item>1f58a</item> + <item>1f58c</item> + <item>1f58d</item> + <item>1f4dd</item> + <item>1f4bc</item> + <item>1f4c1</item> + <item>1f4c2</item> + <item>1f5c2</item> + <item>1f4c5</item> + <item>1f4c6</item> + <item>1f5d2</item> + <item>1f5d3</item> + <item>1f4c7</item> + <item>1f4c8</item> + <item>1f4c9</item> + <item>1f4ca</item> + <item>1f4cb</item> + <item>1f4cc</item> + <item>1f4cd</item> + <item>1f4ce</item> + <item>1f587</item> + <item>1f4cf</item> + <item>1f4d0</item> + <item>2702</item> + <item>1f5c3</item> + <item>1f5c4</item> + <item>1f5d1</item> + <item>1f512</item> + <item>1f513</item> + <item>1f50f</item> + <item>1f510</item> + <item>1f511</item> + <item>1f5dd</item> + <item>1f528</item> + <item>26cf</item> + <item>2692</item> + <item>1f6e0</item> + <item>1f527</item> + <item>1f529</item> + <item>2699</item> + <item>1f5dc</item> + <item>2697</item> + <item>2696</item> + <item>1f517</item> + <item>26d3</item> + <item>1f489</item> + <item>1f48a</item> + <item>1f5e1</item> + <item>1f52a</item> + <item>2694</item> + <item>1f52b</item> + <item>1f6e1</item> + <item>1f3f9</item> + <item>1f6ac</item> + <item>26b0</item> + <item>26b1</item> + <item>1f5ff</item> + <item>1f6e2</item> + <item>1f52e</item> + </array> + <array + name="emoji_activity" + format="string"> + <item>1f383</item> + <item>1f384</item> + <item>1f386</item> + <item>1f387</item> + <item>2728</item> + <item>1f388</item> + <item>1f389</item> + <item>1f38a</item> + <item>1f38b</item> + <item>1f38c</item> + <item>1f38d</item> + <item>1f38e</item> + <item>1f38f</item> + <item>1f390</item> + <item>1f391</item> + <item>1f380</item> + <item>1f381</item> + <item>1f396</item> + <item>1f397</item> + <item>1f39e</item> + <item>1f39f</item> + <item>1f3ab</item> + <item>1f3f7</item> + <item>26bd</item> + <item>26be</item> + <item>1f3c0</item> + <item>1f3c8</item> + <item>1f3c9</item> + <item>1f3be</item> + <item>1f3b1</item> + <item>1f3b3</item> + <item>26f3</item> + <item>1f3cc</item> + <item>26f8</item> + <item>1f3a3</item> + <item>1f3bd</item> + <item>1f3bf</item> + <item>26f7</item> + <item>1f3c2</item> + <item>1f3c4</item> + <item>1f3c7</item> + <item>1f3ca</item> + <item>26f9</item> + <item>1f3cb</item> + <item>1f6b4</item> + <item>1f6b5</item> + <item>1f3ce</item> + <item>1f3cd</item> + <item>1f3c5</item> + <item>1f3c6</item> + <item>1f3cf</item> + <item>1f3d0</item> + <item>1f3d1</item> + <item>1f3d2</item> + <item>1f3d3</item> + <item>1f3f8</item> + <item>1f3af</item> + <item>1f3ae</item> + <item>1f579</item> + <item>1f3b2</item> + <item>2660</item> + <item>2665</item> + <item>2666</item> + <item>2663</item> + <item>1f0cf</item> + <item>1f004</item> + <item>1f3b4</item> + </array> + <array + name="emoji_travel_places" + format="string"> + <item>1f30d</item> + <item>1f30e</item> + <item>1f30f</item> + <item>1f310</item> + <item>1f5fa</item> + <item>1f3d4</item> + <item>26f0</item> + <item>1f30b</item> + <item>1f5fb</item> + <item>1f3d5</item> + <item>1f3d6</item> + <item>1f3dc</item> + <item>1f3dd</item> + <item>1f3de</item> + <item>1f3df</item> + <item>1f3db</item> + <item>1f3d7</item> + <item>1f3d8</item> + <item>1f3d9</item> + <item>1f3da</item> + <item>1f3e0</item> + <item>1f3e1</item> + <item>26ea</item> + <item>1f54b</item> + <item>1f54c</item> + <item>1f54d</item> + <item>26e9</item> + <item>1f3e2</item> + <item>1f3e3</item> + <item>1f3e4</item> + <item>1f3e5</item> + <item>1f3e6</item> + <item>1f3e8</item> + <item>1f3e9</item> + <item>1f3ea</item> + <item>1f3eb</item> + <item>1f3ec</item> + <item>1f3ed</item> + <item>1f3ef</item> + <item>1f3f0</item> + <item>1f492</item> + <item>1f5fc</item> + <item>1f5fd</item> + <item>1f5fe</item> + <item>26f2</item> + <item>26fa</item> + <item>1f301</item> + <item>1f303</item> + <item>1f304</item> + <item>1f305</item> + <item>1f306</item> + <item>1f307</item> + <item>1f309</item> + <item>2668</item> + <item>1f30c</item> + <item>1f3a0</item> + <item>1f3a1</item> + <item>1f3a2</item> + <item>1f488</item> + <item>1f3aa</item> + <item>1f3ad</item> + <item>1f5bc</item> + <item>1f3a8</item> + <item>1f3b0</item> + <item>1f682</item> + <item>1f683</item> + <item>1f684</item> + <item>1f685</item> + <item>1f686</item> + <item>1f687</item> + <item>1f688</item> + <item>1f689</item> + <item>1f68a</item> + <item>1f69d</item> + <item>1f69e</item> + <item>1f68b</item> + <item>1f68c</item> + <item>1f68d</item> + <item>1f68e</item> + <item>1f68f</item> + <item>1f690</item> + <item>1f691</item> + <item>1f692</item> + <item>1f693</item> + <item>1f694</item> + <item>1f695</item> + <item>1f696</item> + <item>1f697</item> + <item>1f698</item> + <item>1f699</item> + <item>1f69a</item> + <item>1f69b</item> + <item>1f69c</item> + <item>1f6b2</item> + <item>26fd</item> + <item>1f6e3</item> + <item>1f6e4</item> + <item>1f6a8</item> + <item>1f6a5</item> + <item>1f6a6</item> + <item>1f6a7</item> + <item>2693</item> + <item>26f5</item> + <item>1f6a3</item> + <item>1f6a4</item> + <item>1f6f3</item> + <item>26f4</item> + <item>1f6e5</item> + <item>1f6a2</item> + <item>2708</item> + <item>1f6e9</item> + <item>1f6eb</item> + <item>1f6ec</item> + <item>1f4ba</item> + <item>1f681</item> + <item>1f69f</item> + <item>1f6a0</item> + <item>1f6a1</item> + <item>1f680</item> + <item>1f6f0</item> + <item>1f6ce</item> + <item>1f6aa</item> + <item>1f6cc</item> + <item>1f6cf</item> + <item>1f6cb</item> + <item>1f6bd</item> + <item>1f6bf</item> + <item>1f6c0</item> + <item>1f6c1</item> + <item>231b</item> + <item>23f3</item> + <item>231a</item> + <item>23f0</item> + <item>23f1</item> + <item>23f2</item> + <item>1f570</item> + <item>1f55b</item> + <item>1f567</item> + <item>1f550</item> + <item>1f55c</item> + <item>1f551</item> + <item>1f55d</item> + <item>1f552</item> + <item>1f55e</item> + <item>1f553</item> + <item>1f55f</item> + <item>1f554</item> + <item>1f560</item> + <item>1f555</item> + <item>1f561</item> + <item>1f556</item> + <item>1f562</item> + <item>1f557</item> + <item>1f563</item> + <item>1f558</item> + <item>1f564</item> + <item>1f559</item> + <item>1f565</item> + <item>1f55a</item> + <item>1f566</item> + <item>1f311</item> + <item>1f312</item> + <item>1f313</item> + <item>1f314</item> + <item>1f315</item> + <item>1f316</item> + <item>1f317</item> + <item>1f318</item> + <item>1f319</item> + <item>1f31a</item> + <item>1f31b</item> + <item>1f31c</item> + <item>1f321</item> + <item>2600</item> + <item>1f31d</item> + <item>1f31e</item> + <item>2b50</item> + <item>1f31f</item> + <item>1f320</item> + <item>2601</item> + <item>26c5</item> + <item>26c8</item> + <item>1f324</item> + <item>1f325</item> + <item>1f326</item> + <item>1f327</item> + <item>1f328</item> + <item>1f329</item> + <item>1f32a</item> + <item>1f32b</item> + <item>1f32c</item> + <item>1f300</item> + <item>1f308</item> + <item>1f302</item> + <item>2602</item> + <item>2614</item> + <item>26f1</item> + <item>26a1</item> + <item>2744</item> + <item>2603</item> + <item>26c4</item> + <item>2604</item> + <item>1f525</item> + <item>1f4a7</item> + <item>1f30a</item> + </array> + <array + name="emoji_flags" + format="string"> + <item>1f1e6,1f1e8</item> + <item>1f1e6,1f1e9</item> + <item>1f1e6,1f1ea</item> + <item>1f1e6,1f1eb</item> + <item>1f1e6,1f1ec</item> + <item>1f1e6,1f1ee</item> + <item>1f1e6,1f1f1</item> + <item>1f1e6,1f1f2</item> + <item>1f1e6,1f1f4</item> + <item>1f1e6,1f1f6</item> + <item>1f1e6,1f1f7</item> + <item>1f1e6,1f1f8</item> + <item>1f1e6,1f1f9</item> + <item>1f1e6,1f1fa</item> + <item>1f1e6,1f1fc</item> + <item>1f1e6,1f1fd</item> + <item>1f1e6,1f1ff</item> + <item>1f1e7,1f1e6</item> + <item>1f1e7,1f1e7</item> + <item>1f1e7,1f1e9</item> + <item>1f1e7,1f1ea</item> + <item>1f1e7,1f1eb</item> + <item>1f1e7,1f1ec</item> + <item>1f1e7,1f1ed</item> + <item>1f1e7,1f1ee</item> + <item>1f1e7,1f1ef</item> + <item>1f1e7,1f1f1</item> + <item>1f1e7,1f1f2</item> + <item>1f1e7,1f1f3</item> + <item>1f1e7,1f1f4</item> + <item>1f1e7,1f1f6</item> + <item>1f1e7,1f1f7</item> + <item>1f1e7,1f1f8</item> + <item>1f1e7,1f1f9</item> + <item>1f1e7,1f1fb</item> + <item>1f1e7,1f1fc</item> + <item>1f1e7,1f1fe</item> + <item>1f1e7,1f1ff</item> + <item>1f1e8,1f1e6</item> + <item>1f1e8,1f1e8</item> + <item>1f1e8,1f1e9</item> + <item>1f1e8,1f1eb</item> + <item>1f1e8,1f1ec</item> + <item>1f1e8,1f1ed</item> + <item>1f1e8,1f1ee</item> + <item>1f1e8,1f1f0</item> + <item>1f1e8,1f1f1</item> + <item>1f1e8,1f1f2</item> + <item>1f1e8,1f1f3</item> + <item>1f1e8,1f1f4</item> + <item>1f1e8,1f1f5</item> + <item>1f1e8,1f1f7</item> + <item>1f1e8,1f1fa</item> + <item>1f1e8,1f1fb</item> + <item>1f1e8,1f1fc</item> + <item>1f1e8,1f1fd</item> + <item>1f1e8,1f1fe</item> + <item>1f1e8,1f1ff</item> + <item>1f1e9,1f1ea</item> + <item>1f1e9,1f1ec</item> + <item>1f1e9,1f1ef</item> + <item>1f1e9,1f1f0</item> + <item>1f1e9,1f1f2</item> + <item>1f1e9,1f1f4</item> + <item>1f1e9,1f1ff</item> + <item>1f1ea,1f1e6</item> + <item>1f1ea,1f1e8</item> + <item>1f1ea,1f1ea</item> + <item>1f1ea,1f1ec</item> + <item>1f1ea,1f1ed</item> + <item>1f1ea,1f1f7</item> + <item>1f1ea,1f1f8</item> + <item>1f1ea,1f1f9</item> + <item>1f1ea,1f1fa</item> + <item>1f1eb,1f1ee</item> + <item>1f1eb,1f1ef</item> + <item>1f1eb,1f1f0</item> + <item>1f1eb,1f1f2</item> + <item>1f1eb,1f1f4</item> + <item>1f1eb,1f1f7</item> + <item>1f1ec,1f1e6</item> + <item>1f1ec,1f1e7</item> + <item>1f1ec,1f1e9</item> + <item>1f1ec,1f1ea</item> + <item>1f1ec,1f1eb</item> + <item>1f1ec,1f1ec</item> + <item>1f1ec,1f1ed</item> + <item>1f1ec,1f1ee</item> + <item>1f1ec,1f1f1</item> + <item>1f1ec,1f1f2</item> + <item>1f1ec,1f1f3</item> + <item>1f1ec,1f1f5</item> + <item>1f1ec,1f1f6</item> + <item>1f1ec,1f1f7</item> + <item>1f1ec,1f1f8</item> + <item>1f1ec,1f1f9</item> + <item>1f1ec,1f1fa</item> + <item>1f1ec,1f1fc</item> + <item>1f1ec,1f1fe</item> + <item>1f1ed,1f1f0</item> + <item>1f1ed,1f1f2</item> + <item>1f1ed,1f1f3</item> + <item>1f1ed,1f1f7</item> + <item>1f1ed,1f1f9</item> + <item>1f1ed,1f1fa</item> + <item>1f1ee,1f1e8</item> + <item>1f1ee,1f1e9</item> + <item>1f1ee,1f1ea</item> + <item>1f1ee,1f1f1</item> + <item>1f1ee,1f1f2</item> + <item>1f1ee,1f1f3</item> + <item>1f1ee,1f1f4</item> + <item>1f1ee,1f1f6</item> + <item>1f1ee,1f1f7</item> + <item>1f1ee,1f1f8</item> + <item>1f1ee,1f1f9</item> + <item>1f1ef,1f1ea</item> + <item>1f1ef,1f1f2</item> + <item>1f1ef,1f1f4</item> + <item>1f1ef,1f1f5</item> + <item>1f1f0,1f1ea</item> + <item>1f1f0,1f1ec</item> + <item>1f1f0,1f1ed</item> + <item>1f1f0,1f1ee</item> + <item>1f1f0,1f1f2</item> + <item>1f1f0,1f1f3</item> + <item>1f1f0,1f1f5</item> + <item>1f1f0,1f1f7</item> + <item>1f1f0,1f1fc</item> + <item>1f1f0,1f1fe</item> + <item>1f1f0,1f1ff</item> + <item>1f1f1,1f1e6</item> + <item>1f1f1,1f1e7</item> + <item>1f1f1,1f1e8</item> + <item>1f1f1,1f1ee</item> + <item>1f1f1,1f1f0</item> + <item>1f1f1,1f1f7</item> + <item>1f1f1,1f1f8</item> + <item>1f1f1,1f1f9</item> + <item>1f1f1,1f1fa</item> + <item>1f1f1,1f1fb</item> + <item>1f1f1,1f1fe</item> + <item>1f1f2,1f1e6</item> + <item>1f1f2,1f1e8</item> + <item>1f1f2,1f1e9</item> + <item>1f1f2,1f1ea</item> + <item>1f1f2,1f1eb</item> + <item>1f1f2,1f1ec</item> + <item>1f1f2,1f1ed</item> + <item>1f1f2,1f1f0</item> + <item>1f1f2,1f1f1</item> + <item>1f1f2,1f1f2</item> + <item>1f1f2,1f1f3</item> + <item>1f1f2,1f1f4</item> + <item>1f1f2,1f1f5</item> + <item>1f1f2,1f1f6</item> + <item>1f1f2,1f1f7</item> + <item>1f1f2,1f1f8</item> + <item>1f1f2,1f1f9</item> + <item>1f1f2,1f1fa</item> + <item>1f1f2,1f1fb</item> + <item>1f1f2,1f1fc</item> + <item>1f1f2,1f1fd</item> + <item>1f1f2,1f1fe</item> + <item>1f1f2,1f1ff</item> + <item>1f1f3,1f1e6</item> + <item>1f1f3,1f1e8</item> + <item>1f1f3,1f1ea</item> + <item>1f1f3,1f1eb</item> + <item>1f1f3,1f1ec</item> + <item>1f1f3,1f1ee</item> + <item>1f1f3,1f1f1</item> + <item>1f1f3,1f1f4</item> + <item>1f1f3,1f1f5</item> + <item>1f1f3,1f1f7</item> + <item>1f1f3,1f1fa</item> + <item>1f1f3,1f1ff</item> + <item>1f1f4,1f1f2</item> + <item>1f1f5,1f1e6</item> + <item>1f1f5,1f1ea</item> + <item>1f1f5,1f1eb</item> + <item>1f1f5,1f1ec</item> + <item>1f1f5,1f1ed</item> + <item>1f1f5,1f1f0</item> + <item>1f1f5,1f1f1</item> + <item>1f1f5,1f1f2</item> + <item>1f1f5,1f1f3</item> + <item>1f1f5,1f1f7</item> + <item>1f1f5,1f1f8</item> + <item>1f1f5,1f1f9</item> + <item>1f1f5,1f1fc</item> + <item>1f1f5,1f1fe</item> + <item>1f1f6,1f1e6</item> + <item>1f1f7,1f1ea</item> + <item>1f1f7,1f1f4</item> + <item>1f1f7,1f1f8</item> + <item>1f1f7,1f1fa</item> + <item>1f1f7,1f1fc</item> + <item>1f1f8,1f1e6</item> + <item>1f1f8,1f1e7</item> + <item>1f1f8,1f1e8</item> + <item>1f1f8,1f1e9</item> + <item>1f1f8,1f1ea</item> + <item>1f1f8,1f1ec</item> + <item>1f1f8,1f1ed</item> + <item>1f1f8,1f1ee</item> + <item>1f1f8,1f1ef</item> + <item>1f1f8,1f1f0</item> + <item>1f1f8,1f1f1</item> + <item>1f1f8,1f1f2</item> + <item>1f1f8,1f1f3</item> + <item>1f1f8,1f1f4</item> + <item>1f1f8,1f1f7</item> + <item>1f1f8,1f1f8</item> + <item>1f1f8,1f1f9</item> + <item>1f1f8,1f1fb</item> + <item>1f1f8,1f1fd</item> + <item>1f1f8,1f1fe</item> + <item>1f1f8,1f1ff</item> + <item>1f1f9,1f1e6</item> + <item>1f1f9,1f1e8</item> + <item>1f1f9,1f1e9</item> + <item>1f1f9,1f1eb</item> + <item>1f1f9,1f1ec</item> + <item>1f1f9,1f1ed</item> + <item>1f1f9,1f1ef</item> + <item>1f1f9,1f1f0</item> + <item>1f1f9,1f1f1</item> + <item>1f1f9,1f1f2</item> + <item>1f1f9,1f1f3</item> + <item>1f1f9,1f1f4</item> + <item>1f1f9,1f1f7</item> + <item>1f1f9,1f1f9</item> + <item>1f1f9,1f1fb</item> + <item>1f1f9,1f1fc</item> + <item>1f1f9,1f1ff</item> + <item>1f1fa,1f1e6</item> + <item>1f1fa,1f1ec</item> + <item>1f1fa,1f1f2</item> + <item>1f1fa,1f1f8</item> + <item>1f1fa,1f1fe</item> + <item>1f1fa,1f1ff</item> + <item>1f1fb,1f1e6</item> + <item>1f1fb,1f1e8</item> + <item>1f1fb,1f1ea</item> + <item>1f1fb,1f1ec</item> + <item>1f1fb,1f1ee</item> + <item>1f1fb,1f1f3</item> + <item>1f1fb,1f1fa</item> + <item>1f1fc,1f1eb</item> + <item>1f1fc,1f1f8</item> + <item>1f1fd,1f1f0</item> + <item>1f1fe,1f1ea</item> + <item>1f1fe,1f1f9</item> + <item>1f1ff,1f1e6</item> + <item>1f1ff,1f1f2</item> + <item>1f1ff,1f1fc</item> + </array> +</resources> diff --git a/mailbox-android/src/main/res/values/strings.xml b/mailbox-android/src/main/res/values/strings.xml new file mode 100644 index 0000000000000000000000000000000000000000..46166922885bbf0793e498547a4022c88006d1ea --- /dev/null +++ b/mailbox-android/src/main/res/values/strings.xml @@ -0,0 +1,444 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <!-- Setup --> + <string name="setup_title">Welcome to Briar Mailbox</string> + <string name="setup_name_explanation">Your nickname will be shown next to any content you post. You can\'t change it after creating your account.</string> + <string name="setup_next">Next</string> + <string name="setup_password_intro">Choose a Password</string> + <string name="setup_password_explanation">Your Briar Mailbox account is stored encrypted on your device, not in the cloud. If you forget your password or uninstall Briar Mailbox, there\'s no way to recover your account.\n\nChoose a long password that\'s hard to guess, such as four random words, or ten random letters, numbers and symbols.</string> + <string name="setup_doze_title">Background Connections</string> + <string name="setup_doze_intro">To receive messages, Briar Mailbox needs to stay connected in the background.</string> + <string name="setup_doze_explanation">To receive messages, Briar Mailbox needs to stay connected in the background. Please disable battery optimizations so Briar Mailbox can stay connected.</string> + <string name="setup_doze_button">Allow Connections</string> + <string name="choose_nickname">Choose your nickname</string> + <string name="choose_password">Choose your password</string> + <string name="confirm_password">Confirm your password</string> + <string name="name_too_long">Name is too long</string> + <string name="password_too_weak">Password is too weak</string> + <string name="passwords_do_not_match">Passwords do not match</string> + <string name="create_account_button">Create Account</string> + <string name="more_info">More Information</string> + <string name="don_t_ask_again">Don\'t ask again</string> + + <string name="setup_huawei_text">Please tap the button below and make sure Briar Mailbox is protected in the \"Protected Apps\" screen.</string> + <string name="setup_huawei_button">Protect Briar Mailbox</string> + <string name="setup_huawei_help">If Briar Mailbox is not added to the protected apps list, it will be unable to run in the background.</string> + <string name="warning_dozed">%s was unable to run in the background</string> + + <!-- Login --> + <string name="enter_password">Password</string> + <string name="try_again">Wrong password, try again</string> + <string name="sign_in_button">Sign In</string> + <string name="forgotten_password">I have forgotten my password</string> + <string name="dialog_title_lost_password">Lost Password</string> + <string name="dialog_message_lost_password">Your Briar Mailbox account is stored encrypted on your device, not in the cloud, so we can\'t reset your password. Would you like to delete your account and start again?\n\nCaution: Your identities, contacts and messages will be permanently lost.</string> + <string name="startup_failed_notification_title">Briar Mailbox could not start</string> + <string name="startup_failed_notification_text">Tap for more information.</string> + <string name="startup_failed_activity_title">Briar Mailbox Startup Failure</string> + <string name="startup_failed_db_error">For some reason, your Briar Mailbox database is corrupted beyond repair. Your account, your data and all your contacts are lost. Unfortunately, you need to reinstall Briar Mailbox or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string> + <string name="startup_failed_data_too_old_error">Your account was created with an old version of this app and cannot be opened with this version. You must either reinstall the old version or set up a new account by choosing \'I have forgotten my password\' at the password prompt.</string> + <string name="startup_failed_data_too_new_error">This version of the app is too old. Please upgrade to the latest version and try again.</string> + <string name="startup_failed_service_error">Briar Mailbox was unable to start a required plugin. Reinstalling Briar Mailbox usually solves this problem. However, please note that you will then lose your account and all data associated with it since Briar Mailbox is not using central servers to store your data on.</string> + <plurals name="expiry_warning"> + <item quantity="one">This is a test version of Briar Mailbox. Your account will expire in %d day and cannot be renewed.</item> + <item quantity="other">This is a test version of Briar Mailbox. Your account will expire in %d days and cannot be renewed.</item> + </plurals> + <string name="expiry_update">The testing expiry date has been extended. Your account will now expire in %d days.</string> + <string name="expiry_date_reached">This software has expired.\nThank you for testing!</string> + <string name="download_briar">To continue using Briar Mailbox, please download version 1.0.</string> + <string name="create_new_account">You will need to create a new account, but you can use the same nickname.</string> + <string name="download_briar_button">Download Briar Mailbox 1.0</string> + <string name="startup_open_database">Decrypting Database…</string> + <string name="startup_migrate_database">Upgrading Database…</string> + + <!-- Navigation Drawer --> + <string name="nav_drawer_open_description">Open the navigation drawer</string> + <string name="nav_drawer_close_description">Close the navigation drawer</string> + <string name="contact_list_button">Contacts</string> + <string name="groups_button">Private Groups</string> + <string name="forums_button">Forums</string> + <string name="blogs_button">Blogs</string> + <string name="settings_button">Settings</string> + <string name="sign_out_button">Sign Out</string> + + <!-- Transports --> + <string name="transport_tor">Internet</string> + <string name="transport_bt">Bluetooth</string> + <string name="transport_lan">Wi-Fi</string> + + <!-- Notifications --> + <string name="reminder_notification_title">Signed out of Briar Mailbox</string> + <string name="reminder_notification_text">Tap to sign back in or swipe to dismiss.</string> + <string name="reminder_notification_channel_title">Briar Mailbox Sign-in Reminder</string> + <string name="ongoing_notification_title">Signed into Briar Mailbox</string> + <string name="ongoing_notification_text">Touch to open Briar Mailbox.</string> + <plurals name="private_message_notification_text"> + <item quantity="one">New private message.</item> + <item quantity="other">%d new private messages.</item> + </plurals> + <plurals name="group_message_notification_text"> + <item quantity="one">New group message.</item> + <item quantity="other">%d new group messages.</item> + </plurals> + <plurals name="forum_post_notification_text"> + <item quantity="one">New forum post.</item> + <item quantity="other">%d new forum posts.</item> + </plurals> + <plurals name="blog_post_notification_text"> + <item quantity="one">New blog post.</item> + <item quantity="other">%d new blog posts.</item> + </plurals> + + <!-- Misc --> + <string name="now">now</string> + <string name="show">Show</string> + <string name="hide">Hide</string> + <string name="ok">OK</string> + <string name="cancel">Cancel</string> + <string name="got_it">Got it</string> + <string name="delete">Delete</string> + <string name="accept">Accept</string> + <string name="decline">Decline</string> + <string name="options">Options</string> + <string name="online">Online</string> + <string name="offline">Offline</string> + <string name="send">Send</string> + <string name="allow">Allow</string> + <string name="open">Open</string> + <string name="no_data">No data</string> + <string name="ellipsis">…</string> + <string name="text_too_long">The entered text is too long</string> + <string name="show_onboarding">Show Help Dialog</string> + <string name="fix">Fix</string> + <string name="help">Help</string> + <string name="sorry">Sorry</string> + + <!-- Contacts and Private Conversations--> + <string name="no_contacts">No contacts to show\n\nTap the + icon to add a contact</string> + <string name="date_no_private_messages">No messages.</string> + <string name="no_private_messages">No messages to show</string> + <string name="message_hint">Type message</string> + <string name="delete_contact">Delete contact</string> + <string name="dialog_title_delete_contact">Confirm Contact Deletion</string> + <string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string> + <string name="contact_deleted_toast">Contact deleted</string> + + <!-- Adding Contacts --> + <string name="add_contact_title">Add a Contact</string> + <string name="face_to_face">You must meet up with the person you want to add as a contact.\n\nThis will prevent anyone from impersonating you or reading your messages in future.</string> + <string name="continue_button">Continue</string> + <string name="connection_failed">Connection failed</string> + <string name="try_again_button">Try Again</string> + <string name="waiting_for_contact_to_scan">Waiting for contact to scan and connect\u2026</string> + <string name="exchanging_contact_details">Exchanging contact details\u2026</string> + <string name="contact_added_toast">Contact added: %s</string> + <string name="contact_already_exists">Contact %s already exists</string> + <string name="contact_exchange_failed">Contact exchange failed</string> + <string name="qr_code_invalid">The QR code is invalid</string> + <string name="qr_code_unsupported">The QR code you are trying to scan belongs to an old version of %s which is no longer supported.\n\nPlease ensure that both of you are running the latest version and then try again.</string> + <string name="camera_error">Camera error</string> + <string name="connecting_to_device">Connecting to device\u2026</string> + <string name="authenticating_with_device">Authenticating with device\u2026</string> + <string name="connection_aborted_local">Connection aborted! This could mean that someone is trying to interfere with your connection</string> + <string name="connection_aborted_remote">Connection aborted by your contact! This could mean that someone is trying to interfere with your connection</string> + + <!-- Introductions --> + <string name="introduction_onboarding_title">Introduce your contacts</string> + <string name="introduction_onboarding_text">You can introduce your contacts to each other, so they don\'t need to meet in person to connect on Briar.</string> + <string name="introduction_menu_item">Make Introduction</string> + <string name="introduction_activity_title">Select Contact</string> + <string name="introduction_not_possible">You already have one introduction in progress with these contacts. Please allow for this to finish first. If you or your contacts are rarely online, this can take some time.</string> + <string name="introduction_message_title">Introduce Contacts</string> + <string name="introduction_message_hint">Add a message (optional)</string> + <string name="introduction_button">Make Introduction</string> + <string name="introduction_sent">Your introduction has been sent.</string> + <string name="introduction_error">There was an error making the introduction.</string> + <string name="introduction_response_error">Error when responding to introduction</string> + <string name="introduction_request_sent">You have asked to introduce %1$s to %2$s.</string> + <string name="introduction_request_received">%1$s has asked to introduce you to %2$s. Do you want to add %2$s to your contact list?</string> + <string name="introduction_request_exists_received">%1$s has asked to introduce you to %2$s, but %2$s is already in your contact list. Since %1$s might not know that, you can still respond:</string> + <string name="introduction_request_answered_received">%1$s has asked to introduce you to %2$s.</string> + <string name="introduction_response_accepted_sent">You accepted the introduction to %1$s.</string> + <string name="introduction_response_accepted_sent_info">Before %1$s gets added to your contacts, they need to accept the introduction as well. This might take some time.</string> + <string name="introduction_response_declined_sent">You declined the introduction to %1$s.</string> + <string name="introduction_response_accepted_received">%1$s accepted the introduction to %2$s.</string> + <string name="introduction_response_declined_received">%1$s declined the introduction to %2$s.</string> + <string name="introduction_response_declined_received_by_introducee">%1$s says that %2$s declined the introduction.</string> + <plurals name="introduction_notification_text"> + <item quantity="one">New contact added.</item> + <item quantity="other">%d new contacts added.</item> + </plurals> + + <!-- Private Groups --> + <string name="groups_list_empty">No groups to show\n\nTap the + icon to create a group, or ask your contacts to share groups with you</string> + <string name="groups_created_by">Created by %s</string> + <plurals name="messages"> + <item quantity="one">%d message</item> + <item quantity="other">%d messages</item> + </plurals> + <string name="groups_group_is_empty">This group is empty</string> + <string name="groups_group_is_dissolved">This group has been dissolved</string> + <string name="groups_remove">Remove</string> + <string name="groups_create_group_title">Create Private Group</string> + <string name="groups_create_group_button">Create Group</string> + <string name="groups_create_group_invitation_button">Send Invitation</string> + <string name="groups_create_group_hint">Choose a name for your private group</string> + <string name="groups_invitation_sent">Group invitation has been sent</string> + <string name="groups_message_sent">Message sent</string> + <string name="groups_member_list">Member List</string> + <string name="groups_invite_members">Invite Members</string> + <string name="groups_member_created_you">You created the group</string> + <string name="groups_member_created">%s created the group</string> + <string name="groups_member_joined_you">You joined the group</string> + <string name="groups_member_joined">%s joined the group</string> + <string name="groups_leave">Leave Group</string> + <string name="groups_leave_dialog_title">Confirm Leaving Group</string> + <string name="groups_leave_dialog_message">Are you sure that you want to leave this group?</string> + <string name="groups_dissolve">Dissolve Group</string> + <string name="groups_dissolve_dialog_title">Confirm Dissolving Group</string> + <string name="groups_dissolve_dialog_message">Are you sure that you want to dissolve this group?\n\nAll other members will not be able to continue their conversation and might not receive the latest messages.</string> + <string name="groups_dissolve_button">Dissolve</string> + <string name="groups_dissolved_dialog_title">Group Has Been Dissolved</string> + <string name="groups_dissolved_dialog_message">The creator of this group has dissolved it.\n\nYou can no longer write messages to the group and might not receive all posts that have been written.</string> + + <!-- Private Group Invitations --> + <string name="groups_invitations_title">Group Invitations</string> + <string name="groups_invitations_invitation_sent">You have invited %1$s to join the group \"%2$s\".</string> + <string name="groups_invitations_invitation_received">%1$s has invited you to join the group \"%2$s\".</string> + <string name="groups_invitations_joined">Joined group</string> + <string name="groups_invitations_declined">Group invitation declined</string> + <plurals name="groups_invitations_open"> + <item quantity="one">%d open group invitation</item> + <item quantity="other">%d open group invitations</item> + </plurals> + <string name="groups_invitations_response_accepted_sent">You accepted the group invitation from %s.</string> + <string name="groups_invitations_response_declined_sent">You declined the group invitation from %s.</string> + <string name="groups_invitations_response_accepted_received">%s accepted the group invitation.</string> + <string name="groups_invitations_response_declined_received">%s declined the group invitation.</string> + <string name="sharing_status_groups">Only the creator can invite new members to the group. Below are all current members of the group.</string> + + <!-- Private Groups Revealing Contacts --> + <string name="groups_reveal_contacts">Reveal Contacts</string> + <string name="groups_reveal_dialog_message">You can choose whether to reveal contacts to all current and future members of this group.\n\nRevealing contacts makes your connection to the group faster and more reliable, because you can communicate with revealed contacts even when the creator of the group is offline.</string> + <string name="groups_reveal_visible">Contact relationship is visible to the group</string> + <string name="groups_reveal_visible_revealed_by_us">Contact relationship is visible to the group (revealed by you)</string> + <string name="groups_reveal_visible_revealed_by_contact">Contact relationship is visible to the group (revealed by %s)</string> + <string name="groups_reveal_invisible">Contact relationship is not visible to the group</string> + + <!-- Forums --> + <string name="no_forums">No forums to show\n\nTap the + icon to create a forum, or ask your contacts to share forums with you</string> + <string name="create_forum_title">Create Forum</string> + <string name="choose_forum_hint">Choose a name for your forum</string> + <string name="create_forum_button">Create Forum</string> + <string name="forum_created_toast">Forum created</string> + <string name="no_forum_posts">No posts to show</string> + <string name="no_posts">No posts</string> + <plurals name="posts"> + <item quantity="one">%d post</item> + <item quantity="other">%d posts</item> + </plurals> + <string name="forum_new_entry_posted">Forum post published</string> + <string name="forum_new_message_hint">New Post</string> + <string name="forum_message_reply_hint">New Reply</string> + <string name="btn_reply">Reply</string> + <string name="forum_leave">Leave Forum</string> + <string name="dialog_title_leave_forum">Confirm Leaving Forum</string> + <string name="dialog_message_leave_forum">Are you sure that you want to leave this forum?\n\nAny contacts you\'ve shared this forum with might stop receiving updates.</string> + <string name="dialog_button_leave">Leave</string> + <string name="forum_left_toast">Left forum</string> + + <!-- Forum Sharing --> + <string name="forum_share_button">Share Forum</string> + <string name="contacts_selected">Contacts selected</string> + <string name="activity_share_toolbar_header">Choose Contacts</string> + <string name="no_contacts_selector">No contacts to show\n\nPlease come back here after adding a contact</string> + <string name="forum_shared_snackbar">Forum shared with chosen contacts</string> + <string name="forum_share_message">Add a message (optional)</string> + <string name="forum_share_error">There was an error sharing this forum.</string> + <string name="forum_invitation_received">%1$s has shared the forum \"%2$s\" with you.</string> + <string name="forum_invitation_sent">You have shared the forum \"%1$s\" with %2$s.</string> + <string name="forum_invitations_title">Forum Invitations</string> + <string name="forum_invitation_exists">You accepted an invitation to this forum already.\n\nAccepting more invitations will make your connection to the forum faster and more reliable.</string> + <string name="forum_joined_toast">Joined forum</string> + <string name="forum_declined_toast">Invitation declined</string> + <string name="shared_by_format">Shared by %s</string> + <string name="forum_invitation_already_sharing">Already sharing</string> + <string name="forum_invitation_response_accepted_sent">You accepted the forum invitation from %s.</string> + <string name="forum_invitation_response_declined_sent">You declined the forum invitation from %s.</string> + <string name="forum_invitation_response_accepted_received">%s accepted the forum invitation.</string> + <string name="forum_invitation_response_declined_received">%s declined the forum invitation.</string> + + <string name="sharing_status">Sharing Status</string> + <string name="sharing_status_forum">Any member of a forum can share it with their contacts. You are sharing this forum with the following contacts. There may also be other members who you can\'t see.</string> + <string name="shared_with">Shared with %1$d (%2$d online)</string> + <plurals name="forums_shared"> + <item quantity="one">%d forum shared by contacts</item> + <item quantity="other">%d forums shared by contacts</item> + </plurals> + <string name="nobody">Nobody</string> + + <!-- Blogs --> + <string name="blogs_other_blog_empty_state">No posts to show</string> + <string name="read_more">read more</string> + <string name="blogs_write_blog_post">Write Blog Post</string> + <string name="blogs_write_blog_post_body_hint">Type your blog post</string> + <string name="blogs_publish_blog_post">Publish</string> + <string name="blogs_blog_post_created">Blog Post Created</string> + <string name="blogs_blog_post_received">New Blog Post Received</string> + <string name="blogs_blog_post_scroll_to">Scroll To</string> + <string name="blogs_feed_empty_state">No posts to show\n\nPosts from your contacts and blogs you subscribe to will appear here\n\nTap the pen icon to write a post</string> + <string name="blogs_remove_blog">Remove Blog</string> + <string name="blogs_remove_blog_dialog_message">Are you sure that you want to remove this blog?\n\nPosts will be removed from your device but not from other people\'s devices.\n\nAny contacts you\'ve shared this blog with might stop receiving updates.</string> + <string name="blogs_remove_blog_ok">Remove</string> + <string name="blogs_blog_removed">Blog removed</string> + <string name="blogs_reblog_comment_hint">Add a comment (optional)</string> + <string name="blogs_reblog_button">Reblog</string> + + <!-- Blog Sharing --> + <string name="blogs_sharing_share">Share Blog</string> + <string name="blogs_sharing_error">There was an error sharing this blog.</string> + <string name="blogs_sharing_button">Share Blog</string> + <string name="blogs_sharing_snackbar">Blog shared with chosen contacts</string> + <string name="blogs_sharing_response_accepted_sent">You accepted the blog invitation from %s.</string> + <string name="blogs_sharing_response_declined_sent">You declined the blog invitation from %s.</string> + <string name="blogs_sharing_response_accepted_received">%s accepted the blog invitation.</string> + <string name="blogs_sharing_response_declined_received">%s declined the blog invitation.</string> + <string name="blogs_sharing_invitation_received">%1$s has shared the blog \"%2$s\" with you.</string> + <string name="blogs_sharing_invitation_sent">You have shared the blog \"%1$s\" with %2$s.</string> + <string name="blogs_sharing_invitations_title">Blog Invitations</string> + <string name="blogs_sharing_joined_toast">Subscribed to blog</string> + <string name="blogs_sharing_declined_toast">Invitation declined</string> + <string name="sharing_status_blog">Anyone who subscribes to a blog can share it with their contacts. You are sharing this blog with the following contacts. There may also be other subscribers who you can\'t see.</string> + + <!-- RSS Feeds --> + <string name="blogs_rss_feeds_import">Import RSS Feed</string> + <string name="blogs_rss_feeds_import_button">Import</string> + <string name="blogs_rss_feeds_import_hint">Enter the URL of the RSS feed</string> + <string name="blogs_rss_feeds_import_error">We are sorry! There was an error importing your feed.</string> + <string name="blogs_rss_feeds_manage">Manage RSS Feeds</string> + <string name="blogs_rss_feeds_manage_imported">Imported:</string> + <string name="blogs_rss_feeds_manage_author">Author:</string> + <string name="blogs_rss_feeds_manage_updated">Last Updated:</string> + <string name="blogs_rss_remove_feed">Remove Feed</string> + <string name="blogs_rss_remove_feed_dialog_message">Are you sure that you want to remove this feed?\n\nPosts will be removed from your device but not from other people\'s devices.\n\nAny contacts you\'ve shared this feed with might stop receiving updates.</string> + <string name="blogs_rss_remove_feed_ok">Remove</string> + <string name="blogs_rss_feeds_manage_delete_error">The feed could not be deleted!</string> + <string name="blogs_rss_feeds_manage_empty_state">No RSS feeds to show\n\nTap the + icon to import a feed</string> + <string name="blogs_rss_feeds_manage_error">There was a problem loading your feeds. Please try again later.</string> + + <!-- Settings Display --> + <string name="pref_language_title">Language & region</string> + <string name="pref_language_changed">This setting will take effect when you restart Briar Mailbox. Please sign out and restart Briar Mailbox.</string> + <string name="pref_language_default">System default</string> + <string name="display_settings_title">Display</string> + <string name="pref_theme_title">Theme</string> + <string name="pref_theme_light">Light</string> + <string name="pref_theme_dark">Dark</string> + <string name="pref_theme_auto">Automatic (Daytime)</string> + <string name="pref_theme_system">System Default</string> + + <!-- Settings Network --> + <string name="network_settings_title">Networks</string> + <string name="bluetooth_setting">Connect via Bluetooth</string> + <string name="bluetooth_setting_enabled">Whenever contacts are nearby</string> + <string name="bluetooth_setting_disabled">Only when adding contacts</string> + <string name="tor_network_setting">Connect via Tor</string> + <string name="tor_network_setting_never">Never</string> + <string name="tor_network_setting_wifi">Only when using Wi-Fi</string> + <string name="tor_network_setting_always">When using Wi-Fi or mobile data</string> + + <!-- Settings Security and Panic --> + <string name="security_settings_title">Security</string> + <string name="change_password">Change password</string> + <string name="current_password">Current password</string> + <string name="choose_new_password">New password</string> + <string name="confirm_new_password">Confirm new password</string> + <string name="password_changed">Password has been changed.</string> + <string name="panic_setting">Panic button setup</string> + <string name="panic_setting_title">Panic button</string> + <string name="panic_setting_hint">Configure how Briar Mailbox will react when you use a panic button app</string> + <string name="panic_app_setting_title">Panic Button App</string> + <string name="unknown_app">an unknown app</string> + <string name="panic_app_setting_summary">No app has been set</string> + <string name="panic_app_setting_none">None</string> + <string name="dialog_title_connect_panic_app">Confirm Panic App</string> + <string name="dialog_message_connect_panic_app">Are you sure that you want to allow %1$s to trigger destructive panic button actions?</string> + <string name="lock_setting_title">Sign Out</string> + <string name="lock_setting_summary">Sign out of Briar Mailbox if a panic button is pressed</string> + <string name="purge_setting_title">Delete Account</string> + <string name="purge_setting_summary">Delete your Briar Mailbox account if a panic button is pressed. Caution: This will permanently delete your identities, contacts and messages</string> + <string name="uninstall_setting_title">Uninstall Briar Mailbox</string> + <string name="uninstall_setting_summary">This requires manual confirmation in a panic event</string> + + <!-- Settings Notifications --> + <string name="notification_settings_title">Notifications</string> + <string name="notify_private_messages_setting_title">Private messages</string> + <string name="notify_private_messages_setting_summary">Show alerts for private messages</string> + <string name="notify_private_messages_setting_summary_26">Configure alerts for private messages</string> + <string name="notify_group_messages_setting_title">Group messages</string> + <string name="notify_group_messages_setting_summary">Show alerts for group messages</string> + <string name="notify_group_messages_setting_summary_26">Configure alerts for group messages</string> + <string name="notify_forum_posts_setting_title">Forum posts</string> + <string name="notify_forum_posts_setting_summary">Show alerts for forum posts</string> + <string name="notify_forum_posts_setting_summary_26">Configure alerts for forum posts</string> + <string name="notify_blog_posts_setting_title">Blog posts</string> + <string name="notify_blog_posts_setting_summary">Show alerts for blog posts</string> + <string name="notify_blog_posts_setting_summary_26">Configure alerts for blog posts</string> + <string name="notify_vibration_setting">Vibrate</string> + <string name="notify_lock_screen_setting_title">Lock Screen</string> + <string name="notify_lock_screen_setting_summary">Show notifications on the lock screen</string> + <string name="notify_sound_setting">Sound</string> + <string name="notify_sound_setting_default">Default ringtone</string> + <string name="notify_sound_setting_disabled">None</string> + <string name="choose_ringtone_title">Choose ringtone</string> + <string name="cannot_load_ringtone">Cannot load ringtone</string> + + <!-- Settings Feedback --> + <string name="feedback_settings_title">Feedback</string> + <string name="send_feedback">Send feedback</string> + + <!-- Link Warning --> + <string name="link_warning_title">Link Warning</string> + <string name="link_warning_intro">You are about to open the following link with an external app.</string> + <string name="link_warning_text">This can be used to identify you. Think about whether you trust the person that sent you this link and consider opening it with Orfox.</string> + <string name="link_warning_open_link">Open Link</string> + + <!-- Crash Reporter --> + <string name="crash_report_title">Briar Mailbox Crash Report</string> + <string name="briar_crashed">Sorry, Briar Mailbox has crashed.</string> + <string name="not_your_fault">This is not your fault.</string> + <string name="please_send_report">Please help us build a better Briar Mailbox by sending us a crash report.</string> + <string name="report_is_encrypted">We promise that the report is encrypted and sent securely.</string> + <string name="feedback_title">Feedback</string> + <string name="describe_crash">Describe what happened (optional)</string> + <string name="enter_feedback">Enter your feedback</string> + <string name="optional_contact_email">Your email address (optional)</string> + <string name="include_debug_report_crash">Include anonymous data about the crash</string> + <string name="include_debug_report_feedback">Include anonymous data about this device</string> + <string name="could_not_load_report_data">Could not load report data.</string> + <string name="send_report">Send report</string> + <string name="close">Close</string> + <string name="dev_report_saved">Report saved. It will be sent the next time you log into Briar Mailbox.</string> + + <!-- Sign Out --> + <string name="progress_title_logout">Signing out of Briar Mailbox…</string> + + <!-- Screen Filters & Tapjacking --> + <string name="screen_filter_title">Screen overlay detected</string> + <string name="screen_filter_body">Another app is drawing on top of Briar Mailbox. To protect your security, Briar Mailbox will not respond to touches when another app is drawing on top.\n\nThe following apps might be drawing on top:\n\n%1$s</string> + <string name="screen_filter_allow">Allow these apps to draw on top</string> + + <!-- Permission Requests --> + <string name="permission_camera_title">Camera permission</string> + <string name="permission_camera_request_body">To scan the QR code, Briar Mailbox needs access to the camera.</string> + <string name="permission_camera_denied_body">You have denied access to the camera, but adding contacts requires using the camera.\n\nPlease consider granting access.</string> + <string name="permission_camera_denied_toast">Camera permission was not granted</string> + <string name="qr_code">QR code</string> + <string name="show_qr_code_fullscreen">Show QR code fullscreen</string> + <string name="overview">Overview</string> + <string name="mailbox_pairing">Pair with Briar</string> + <string name="mailbox_paired">Succesfully paired</string> + <string name="mailbox_already_paired">Your Mailbox is already paired with this Briar account</string> + <string name="mailbox_unpaired">not paired</string> +</resources> diff --git a/mailbox-android/src/main/res/values/styles.xml b/mailbox-android/src/main/res/values/styles.xml new file mode 100644 index 0000000000000000000000000000000000000000..f679d808159f316ee5878b79279aa55720fc4011 --- /dev/null +++ b/mailbox-android/src/main/res/values/styles.xml @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:android="http://schemas.android.com/apk/res/android"> + + <style name="BriarToolbar" parent="Widget.AppCompat.Toolbar"> + <item name="android:background">?colorPrimary</item> + <item name="android:textColorPrimary">@color/briar_text_primary_inverse</item> + <item name="android:textSize">@dimen/text_size_medium</item> + <item name="colorPrimary">@color/briar_primary</item> + <item name="titleTextAppearance">@style/BriarToolbarTitleTextAppearance</item> + <item name="subtitleTextAppearance">@style/BriarToolbarSubTitleTextAppearance</item> + <item name="android:theme">@style/BriarToolbarTheme</item> + </style> + + <style name="BriarToolbarTheme"> + <item name="colorControlNormal">@color/briar_text_primary_inverse</item> + </style> + + <style name="BriarToolbarTitleTextAppearance" parent="TextAppearance.Widget.AppCompat.Toolbar.Title"> + <item name="android:textColor">@color/briar_text_primary_inverse</item> + </style> + + <style name="BriarToolbarSubTitleTextAppearance" parent="TextAppearance.Widget.AppCompat.Toolbar.Subtitle"> + <item name="android:textColor">@color/briar_text_secondary_inverse</item> + </style> + + <style name="BriarButton.Default"> + <item name="android:textAllCaps">true</item> + </style> + + <style name="BriarButton" parent="Widget.AppCompat.Button.Colored"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:textSize">@dimen/text_size_medium</item> + <item name="android:padding">@dimen/margin_large</item> + <item name="android:textColor">@color/button_text</item> + </style> + + <style name="BriarButtonFlat.Negative" parent="Widget.AppCompat.Button.Borderless"> + <item name="android:textColor">@color/briar_button_negative</item> + <item name="android:textSize">@dimen/text_size_medium</item> + </style> + + <style name="BriarButtonFlat.Positive" parent="Widget.AppCompat.Button.Borderless"> + <item name="android:textColor">@color/briar_button_positive</item> + <item name="android:textSize">@dimen/text_size_medium</item> + </style> + + <style name="BriarButtonFlat.Positive.Tiny" parent="BriarButtonFlat.Positive"> + <item name="android:textSize">@dimen/text_size_tiny</item> + <item name="android:padding">@dimen/margin_medium</item> + <item name="android:minWidth">@dimen/button_size</item> + </style> + + <style name="Divider"> + <item name="android:background">@color/divider</item> + </style> + + <style name="Divider.Horizontal" parent="Divider"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">1px</item> + </style> + + <style name="Divider.ContactList" parent="Divider"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">1dp</item> + <item name="android:layout_marginLeft">@dimen/margin_large</item> + </style> + + <style name="Divider.ThreadItem" parent="Divider"> + <item name="android:layout_width">match_parent</item> + <item name="android:layout_height">1dp</item> + </style> + + <style name="BriarAvatar"> + <item name="civ_border_width">@dimen/avatar_border_width</item> + <item name="civ_border_color">?android:attr/textColorSecondary</item> + </style> + + <style name="DiscussionLevelIndicator"> + <item name="android:layout_marginLeft">4dp</item> + <item name="android:background">@color/thread_indicator</item> + </style> + + <style name="BriarCard" parent="CardView"> + <item name="cardUseCompatPadding">true</item> + <item name="android:layout_margin">@dimen/margin_small</item> + </style> + +</resources> \ No newline at end of file diff --git a/mailbox-android/src/main/res/values/themes.xml b/mailbox-android/src/main/res/values/themes.xml new file mode 100644 index 0000000000000000000000000000000000000000..c4dc47a67fb400e59f19549f74313c92bfb9f22c --- /dev/null +++ b/mailbox-android/src/main/res/values/themes.xml @@ -0,0 +1,54 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + + <style name="BriarTheme" parent="Theme.AppCompat.DayNight.DarkActionBar"> + <item name="colorPrimary">@color/briar_primary</item> + <item name="colorPrimaryDark">@color/briar_primary_dark</item> + <item name="colorAccent">@color/briar_accent</item> + <item name="android:textColorLink">@color/briar_text_link</item> + <item name="android:windowAnimationStyle">@style/ActivityAnimation</item> + <item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item> + </style> + + <style name="BriarTheme.NoActionBar" parent="BriarTheme"> + <item name="windowActionBar">false</item> + <item name="windowNoTitle">true</item> + <item name="toolbarStyle">@style/BriarToolbar</item> + </style> + + <style name="ActivityAnimation" parent="@android:style/Animation.Activity"> + <item name="android:activityOpenEnterAnimation">@anim/screen_new_in</item> + <item name="android:activityOpenExitAnimation">@anim/screen_old_out</item> + <item name="android:activityCloseEnterAnimation">@anim/screen_old_in</item> + <item name="android:activityCloseExitAnimation">@anim/screen_new_out</item> + </style> + + <style name="BriarDialogTheme" parent="Theme.AppCompat.DayNight.Dialog"> + <item name="colorPrimary">@color/briar_primary</item> + <item name="colorPrimaryDark">@color/briar_primary_dark</item> + <item name="colorAccent">@color/briar_accent</item> + <item name="buttonBarPositiveButtonStyle">@style/BriarButtonFlat.Positive</item> + <item name="buttonBarNegativeButtonStyle">@style/BriarButtonFlat.Negative</item> + <item name="android:textColorLink">@color/briar_text_link</item> + <item name="android:windowAnimationStyle">@style/DialogAnimation</item> + <item name="android:filterTouchesWhenObscured">true</item> + </style> + + <!-- Use this with care. Only used for the screen filter warning dialog --> + <style name="BriarDialogThemeNoFilter" parent="BriarDialogTheme"> + <item name="android:filterTouchesWhenObscured">false</item> + </style> + + <style name="DialogAnimation" parent="@android:style/Animation.Dialog"> + <item name="android:windowEnterAnimation">@anim/fade_in</item> + <item name="android:windowExitAnimation">@anim/fade_out</item> + </style> + + <style name="OnboardingDialogTheme" parent="BriarDialogTheme"> + <item name="android:background">@color/briar_primary</item> + <item name="android:textColorPrimary">@color/briar_text_primary_inverse</item> + <item name="android:textColorSecondary">@color/briar_text_secondary_inverse</item> + <item name="buttonBarNeutralButtonStyle">@style/Widget.AppCompat.Button.Borderless</item> + </style> + +</resources> \ No newline at end of file diff --git a/mailbox-android/src/main/res/xml/settings.xml b/mailbox-android/src/main/res/xml/settings.xml new file mode 100644 index 0000000000000000000000000000000000000000..b14dd4ba36d4e277e703c2c4825318443fe65da2 --- /dev/null +++ b/mailbox-android/src/main/res/xml/settings.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="@string/display_settings_title"> + + <ListPreference + android:defaultValue="default" + android:entryValues="@array/pref_language_values" + android:key="pref_key_language" + android:summary="%s" + android:title="@string/pref_language_title" /> + + <ListPreference + android:defaultValue="@string/pref_theme_light_value" + android:entries="@array/pref_theme_entries" + android:entryValues="@array/pref_theme_values" + android:key="pref_key_theme" + android:summary="%s" + android:title="@string/pref_theme_title" /> + + </PreferenceCategory> + + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="@string/network_settings_title"> + + <ListPreference + android:defaultValue="false" + android:entries="@array/bt_setting_names" + android:entryValues="@array/boolean_array" + android:key="pref_key_bluetooth" + android:persistent="false" + android:summary="%s" + android:title="@string/bluetooth_setting" /> + + <ListPreference + android:defaultValue="2" + android:entries="@array/tor_network_setting_names" + android:entryValues="@array/tor_network_setting_values" + android:key="pref_key_tor_network" + android:persistent="false" + android:summary="%s" + android:title="@string/tor_network_setting" /> + + </PreferenceCategory> + + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="@string/security_settings_title"> + + <Preference + android:key="pref_key_change_password" + android:title="@string/change_password"> + + <intent + android:targetClass="org.briarproject.mailbox.login.ChangePasswordActivity" + android:targetPackage="@string/app_package" /> + </Preference> + + </PreferenceCategory> + + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="@string/feedback_settings_title" /> + + <Preference + android:key="pref_key_send_feedback" + android:title="@string/send_feedback" /> + + <PreferenceCategory + android:layout="@layout/preferences_category" + android:title="Testing"> + + <Preference + android:key="pref_key_explode" + android:title="Crash" /> + + </PreferenceCategory> +</PreferenceScreen> diff --git a/settings.gradle b/settings.gradle index 3f3670622d0acfba7ef41fb6a026608542f8379e..1f811046660c95484b2d9ed936a24f29fc259dce 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,3 +5,4 @@ include ':bramble-j2se' include ':briar-api' include ':briar-core' include ':briar-android' +include ':mailbox-android'