瀏覽代碼

[add] IO file operations add/list/read/write on raw data
[add] Software Keyboard Input
[tmp] settings activity

isundil 7 年之前
父節點
當前提交
3e1041c083
共有 39 個文件被更改,包括 1796 次插入47 次删除
  1. 6 0
      .idea/gradle.xml
  2. 26 1
      .idea/misc.xml
  3. 7 4
      app/build.gradle
  4. 25 1
      app/src/main/AndroidManifest.xml
  5. 38 0
      app/src/main/java/info/knacki/pass/generator/PasswordGenerator.java
  6. 163 0
      app/src/main/java/info/knacki/pass/generator/ui/PasswordGeneratorWizard.java
  7. 76 0
      app/src/main/java/info/knacki/pass/input/InputService.java
  8. 8 0
      app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java
  9. 9 0
      app/src/main/java/info/knacki/pass/io/IFileInterface.java
  10. 39 0
      app/src/main/java/info/knacki/pass/io/RawFileInterface.java
  11. 0 29
      app/src/main/java/info/knacki/pass/passListActivity.java
  12. 109 0
      app/src/main/java/info/knacki/pass/settings/ui/AppCompatPreferenceActivity.java
  13. 269 0
      app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java
  14. 121 0
      app/src/main/java/info/knacki/pass/ui/AlertText.java
  15. 81 0
      app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java
  16. 189 0
      app/src/main/java/info/knacki/pass/ui/MainActivity.java
  17. 7 0
      app/src/main/java/info/knacki/pass/ui/PasswordClickListener.java
  18. 116 0
      app/src/main/java/info/knacki/pass/ui/PasswordListView.java
  19. 43 0
      app/src/main/java/info/knacki/pass/ui/PasswordView.java
  20. 24 0
      app/src/main/res/drawable/ic_doc_folder.xml
  21. 24 0
      app/src/main/res/drawable/ic_doc_text.xml
  22. 37 0
      app/src/main/res/drawable/ic_fingerprint.xml
  23. 9 0
      app/src/main/res/drawable/ic_info_black_24dp.xml
  24. 9 0
      app/src/main/res/drawable/ic_notifications_black_24dp.xml
  25. 9 0
      app/src/main/res/drawable/ic_sync_black_24dp.xml
  26. 39 0
      app/src/main/res/layout/activity_pass_edit.xml
  27. 10 11
      app/src/main/res/layout/activity_pass_list.xml
  28. 56 0
      app/src/main/res/layout/input.xml
  29. 10 0
      app/src/main/res/menu/main_menu.xml
  30. 28 0
      app/src/main/res/values-fr/lang.xml
  31. 2 0
      app/src/main/res/values/colors.xml
  32. 28 0
      app/src/main/res/values/lang.xml
  33. 74 0
      app/src/main/res/values/strings.xml
  34. 3 0
      app/src/main/res/xml/method.xml
  35. 21 0
      app/src/main/res/xml/pref_data_sync.xml
  36. 33 0
      app/src/main/res/xml/pref_general.xml
  37. 20 0
      app/src/main/res/xml/pref_headers.xml
  38. 27 0
      app/src/main/res/xml/pref_notification.xml
  39. 1 1
      build.gradle

+ 6 - 0
.idea/gradle.xml

@@ -5,6 +5,12 @@
       <GradleProjectSettings>
         <option name="distributionType" value="DEFAULT_WRAPPED" />
         <option name="externalProjectPath" value="$PROJECT_DIR$" />
+        <option name="modules">
+          <set>
+            <option value="$PROJECT_DIR$" />
+            <option value="$PROJECT_DIR$/app" />
+          </set>
+        </option>
         <option name="resolveModulePerSourceSet" value="false" />
       </GradleProjectSettings>
     </option>

+ 26 - 1
.idea/misc.xml

@@ -1,6 +1,31 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
-  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7">
+  <component name="NullableNotNullManager">
+    <option name="myDefaultNullable" value="android.support.annotation.Nullable" />
+    <option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
+    <option name="myNullables">
+      <value>
+        <list size="5">
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
+          <item index="2" class="java.lang.String" itemvalue="javax.annotation.CheckForNull" />
+          <item index="3" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
+          <item index="4" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
+        </list>
+      </value>
+    </option>
+    <option name="myNotNulls">
+      <value>
+        <list size="4">
+          <item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
+          <item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
+          <item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
+          <item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
+        </list>
+      </value>
+    </option>
+  </component>
+  <component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" project-jdk-name="1.8" project-jdk-type="JavaSDK">
     <output url="file://$PROJECT_DIR$/build/classes" />
   </component>
   <component name="ProjectType">

+ 7 - 4
app/build.gradle

@@ -14,6 +14,7 @@ android {
                 cppFlags ""
             }
         }
+        vectorDrawables.useSupportLibrary = true
     }
     buildTypes {
         release {
@@ -30,9 +31,11 @@ android {
 
 dependencies {
     implementation fileTree(dir: 'libs', include: ['*.jar'])
-    implementation 'com.android.support:appcompat-v7:27.1.0'
-    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
+    implementation 'com.android.support:appcompat-v7:27.1.1'
+    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+    implementation 'com.android.support:support-v4:27.1.1'
+    implementation 'com.android.support:support-vector-drawable:27.1.1'
     testImplementation 'junit:junit:4.12'
-    androidTestImplementation 'com.android.support.test:runner:1.0.1'
-    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
+    androidTestImplementation 'com.android.support.test:runner:1.0.2'
+    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
 }

+ 25 - 1
app/src/main/AndroidManifest.xml

@@ -9,13 +9,37 @@
         android:roundIcon="@mipmap/ic_launcher_round"
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
-        <activity android:name=".passListActivity">
+        <activity android:name=".ui.MainActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
 
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity
+            android:name=".ui.EditPasswordActivity"
+            android:windowSoftInputMode="adjustResize" />
+
+        <service
+            android:name=".input.InputService"
+            android:permission="android.permission.BIND_INPUT_METHOD">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+
+            <meta-data
+                android:name="android.view.im"
+                android:resource="@xml/method" />
+        </service>
+
+        <activity
+            android:name=".settings.ui.SettingsActivity"
+            android:label="@string/title_activity_settings"
+            android:parentActivityName=".ui.MainActivity">
+            <meta-data
+                android:name="android.support.PARENT_ACTIVITY"
+                android:value="info.knacki.pass.ui.MainActivity" />
+        </activity>
     </application>
 
 </manifest>

+ 38 - 0
app/src/main/java/info/knacki/pass/generator/PasswordGenerator.java

@@ -0,0 +1,38 @@
+package info.knacki.pass.generator;
+
+public class PasswordGenerator {
+
+    public interface PasswordGeneratorParams {
+        boolean hasNumbers();
+        boolean hasLetters();
+        boolean hasCapitalize();
+        boolean hasSpecialChar();
+        int length();
+    }
+
+    private static String generateAlphabet(PasswordGeneratorParams wiz) {
+        StringBuilder abc = new StringBuilder();
+        if (wiz.hasSpecialChar())
+            abc.append("&~#'{\"([-|`_\\^@)]=}+%*$?,.;/:/");
+        if (wiz.hasLetters())
+            abc.append("abcdefghijklmnopqrstuvwxyz");
+        if (wiz.hasCapitalize())
+            abc.append("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+        if (wiz.hasNumbers() || abc.length() == 0)
+            abc.append("0123456789");
+        return abc.toString();
+    }
+
+    private static int rand(int max) {
+        return (int) (Math.random() * max);
+    }
+
+    public static String generate(PasswordGeneratorParams wiz) {
+        String alphabet = generateAlphabet(wiz);
+        StringBuilder result = new StringBuilder();
+
+        for (int i =0; i < wiz.length(); ++i)
+            result.append(alphabet.charAt(rand(alphabet.length())));
+        return result.toString();
+    }
+}

+ 163 - 0
app/src/main/java/info/knacki/pass/generator/ui/PasswordGeneratorWizard.java

@@ -0,0 +1,163 @@
+package info.knacki.pass.generator.ui;
+
+import android.content.Context;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.AppCompatSpinner;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.NumberPicker;
+import android.widget.TextView;
+
+import info.knacki.pass.R;
+import info.knacki.pass.generator.PasswordGenerator;
+
+public class PasswordGeneratorWizard extends LinearLayout implements PasswordGenerator.PasswordGeneratorParams {
+    public AppCompatSpinner fDifficulty;
+    public AppCompatCheckBox fAlpha;
+    public AppCompatCheckBox fCapitalize;
+    public AppCompatCheckBox fNum;
+    public AppCompatCheckBox fSpecial;
+    public NumberPicker fLength;
+
+    public static final int PRESET_NUM =0;
+    public static final int PRESET_WEAK =1;
+    public static final int PRESET_AVG =2;
+    public static final int PRESET_STRONG =3;
+
+    @Override
+    public boolean hasNumbers() {
+        return fNum.isChecked();
+    }
+
+    @Override
+    public boolean hasLetters() {
+        return fAlpha.isChecked();
+    }
+
+    @Override
+    public boolean hasCapitalize() {
+        return fCapitalize.isChecked();
+    }
+
+    @Override
+    public boolean hasSpecialChar() {
+        return fSpecial.isChecked();
+    }
+
+    @Override
+    public int length() {
+        return fLength.getValue();
+    }
+
+    private class ChangeListener implements CompoundButton.OnCheckedChangeListener, NumberPicker.OnValueChangeListener {
+        private boolean locked = true;
+
+        @Override
+        public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+            if (!locked)
+                fDifficulty.setSelection(4, true);
+        }
+
+        @Override
+        public void onValueChange(NumberPicker numberPicker, int i, int i1) {
+            if (!locked)
+                fDifficulty.setSelection(4, true);
+        }
+
+        void setLocked(boolean val) {
+            locked = val;
+        }
+    }
+    protected final ChangeListener fChangeListener = new ChangeListener();
+
+    public PasswordGeneratorWizard(Context c) {
+        super(c);
+
+
+        LinearLayout difficultyLayout = new LinearLayout(c);
+        ArrayAdapter fDifficultyAdapter = new ArrayAdapter<>(c, android.R.layout.simple_spinner_item, c.getResources().getStringArray(R.array.fDifficulty));
+        fDifficultyAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+        fDifficulty = new AppCompatSpinner(c);
+        fDifficulty.setAdapter(fDifficultyAdapter);
+        fDifficulty.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+            @Override
+            public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
+                SelectPreset(i);
+            }
+
+            @Override
+            public void onNothingSelected(AdapterView<?> adapterView) {}
+        });
+        TextView tv = new TextView(c);
+        tv.setText(R.string.generate_difficulty);
+        difficultyLayout.setOrientation(HORIZONTAL);
+        difficultyLayout.addView(tv);
+        difficultyLayout.addView(fDifficulty);
+        addView(difficultyLayout);
+        fNum = new AppCompatCheckBox(c);
+        fNum.setText(R.string.generate_number);
+        fNum.setOnCheckedChangeListener(fChangeListener);
+        addView(fNum);
+        fAlpha = new AppCompatCheckBox(c);
+        fAlpha.setText(R.string.generate_alpha);
+        fAlpha.setOnCheckedChangeListener(fChangeListener);
+        addView(fAlpha);
+        fCapitalize = new AppCompatCheckBox(c);
+        fCapitalize.setText(R.string.generate_capitalize);
+        fCapitalize.setOnCheckedChangeListener(fChangeListener);
+        addView(fCapitalize);
+        fSpecial = new AppCompatCheckBox(c);
+        fSpecial.setText(R.string.generate_special);
+        fSpecial.setOnCheckedChangeListener(fChangeListener);
+        addView(fSpecial);
+        fLength = new NumberPicker(c);
+        fLength.setMinValue(4);
+        fLength.setMaxValue(21);
+        fLength.setOnValueChangedListener(fChangeListener);
+        addView(fLength);
+        setOrientation(VERTICAL);
+        fDifficulty.setSelection(2, false);
+        fChangeListener.setLocked(false);
+    }
+
+    protected void SelectPreset(int pos) {
+        fChangeListener.setLocked(true);
+        switch (pos) {
+            case PRESET_NUM:
+                fAlpha.setChecked(false);
+                fCapitalize.setChecked(false);
+                fNum.setChecked(true);
+                fSpecial.setChecked(false);
+                fLength.setValue(4);
+                break;
+
+            case PRESET_WEAK:
+                fAlpha.setChecked(true);
+                fCapitalize.setChecked(false);
+                fNum.setChecked(true);
+                fSpecial.setChecked(false);
+                fLength.setValue(8);
+                break;
+
+            case PRESET_AVG:
+                fAlpha.setChecked(true);
+                fCapitalize.setChecked(true);
+                fNum.setChecked(true);
+                fSpecial.setChecked(true);
+                fLength.setValue(8);
+                break;
+
+            case PRESET_STRONG:
+                fAlpha.setChecked(true);
+                fCapitalize.setChecked(true);
+                fNum.setChecked(true);
+                fSpecial.setChecked(true);
+                fLength.setValue(12);
+                break;
+        }
+        fChangeListener.setLocked(false);
+    }
+}

+ 76 - 0
app/src/main/java/info/knacki/pass/input/InputService.java

@@ -0,0 +1,76 @@
+package info.knacki.pass.input;
+
+import android.content.Context;
+import android.content.Intent;
+import android.inputmethodservice.InputMethodService;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ScrollView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import info.knacki.pass.R;
+import info.knacki.pass.io.FileInterfaceFactory;
+import info.knacki.pass.ui.PasswordClickListener;
+import info.knacki.pass.ui.PasswordListView;
+import info.knacki.pass.ui.MainActivity;
+
+public class InputService extends InputMethodService implements PasswordClickListener {
+    protected PasswordListView fPasswordListView;
+    private static final Logger log = Logger.getLogger(InputService.class.getName());
+
+    @Override
+    public void onStartInputView(EditorInfo info, boolean restarting) {
+        super.onStartInputView(info, restarting);
+        if (fPasswordListView != null) {
+            fPasswordListView.reset();
+        }
+    }
+
+    @Override
+    public View onCreateInputView() {
+        View view = LayoutInflater.from(this).inflate(R.layout.input, null, false);
+        fPasswordListView = new PasswordListView(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
+        ((ScrollView)view.findViewById(R.id.passwordListContainer)).addView(fPasswordListView);
+        view.findViewById(R.id.prevButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                try {
+                    ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).showInputMethodPicker();
+                } catch (NullPointerException e) {
+                    Toast.makeText(InputService.this, "Android error", Toast.LENGTH_LONG).show();
+                    log.log(Level.SEVERE, e.getMessage(), e);
+                }
+            }
+        });
+        view.findViewById(R.id.openAppButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                startActivity(new Intent(InputService.this, MainActivity.class));
+            }
+        });
+        return view;
+    }
+
+    @Override
+    public void OnPasswordClicked(File f) {
+        try {
+            String passwordContent = FileInterfaceFactory.GetFileInterface(f).ReadFile();
+            for (char i: passwordContent.toCharArray()) {
+                sendKeyChar(i);
+            }
+        }
+        catch (IOException e) {
+            Toast.makeText(this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+            log.log(Level.SEVERE, e.getMessage(), e);
+            return;
+        }
+        onFinishInput();
+    }
+}

+ 8 - 0
app/src/main/java/info/knacki/pass/io/FileInterfaceFactory.java

@@ -0,0 +1,8 @@
+package info.knacki.pass.io;
+
+import java.io.File;
+
+public class FileInterfaceFactory {
+    public static IFileInterface GetFileInterface(File f)
+    { return new RawFileInterface(f); }
+}

+ 9 - 0
app/src/main/java/info/knacki/pass/io/IFileInterface.java

@@ -0,0 +1,9 @@
+package info.knacki.pass.io;
+
+import java.io.File;
+import java.io.IOException;
+
+public interface IFileInterface {
+    String ReadFile() throws IOException;
+    void WriteFile(String content) throws IOException;
+}

+ 39 - 0
app/src/main/java/info/knacki/pass/io/RawFileInterface.java

@@ -0,0 +1,39 @@
+package info.knacki.pass.io;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+
+import info.knacki.pass.io.IFileInterface;
+
+class RawFileInterface implements IFileInterface {
+    private static File fFile;
+
+    RawFileInterface(File f) {
+        fFile = f;
+    }
+
+    @Override
+    public String ReadFile() throws IOException {
+        BufferedReader buf = new BufferedReader(new FileReader(fFile));
+        StringBuilder result = new StringBuilder();
+        String tmp;
+
+        while ((tmp = buf.readLine()) != null) {
+            if (result.length() > 0)
+                result.append("\n");
+            result.append(tmp);
+        }
+        buf.close();
+        return result.toString();
+    }
+
+    @Override
+    public void WriteFile(String content) throws IOException {
+        FileWriter writer = new FileWriter(fFile);
+        writer.write(content);
+        writer.close();
+    }
+}

+ 0 - 29
app/src/main/java/info/knacki/pass/passListActivity.java

@@ -1,29 +0,0 @@
-package info.knacki.pass;
-
-import android.support.v7.app.AppCompatActivity;
-import android.os.Bundle;
-import android.widget.TextView;
-
-public class passListActivity extends AppCompatActivity {
-
-    // Used to load the 'native-lib' library on application startup.
-    static {
-        System.loadLibrary("native-lib");
-    }
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.activity_pass_list);
-
-        // Example of a call to a native method
-        TextView tv = (TextView) findViewById(R.id.sample_text);
-        tv.setText(stringFromJNI());
-    }
-
-    /**
-     * A native method that is implemented by the 'native-lib' native library,
-     * which is packaged with this application.
-     */
-    public native String stringFromJNI();
-}

+ 109 - 0
app/src/main/java/info/knacki/pass/settings/ui/AppCompatPreferenceActivity.java

@@ -0,0 +1,109 @@
+package info.knacki.pass.settings.ui;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.preference.PreferenceActivity;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.v7.app.ActionBar;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
+ * to be used with AppCompat.
+ */
+public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
+
+    private AppCompatDelegate mDelegate;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        getDelegate().installViewFactory();
+        getDelegate().onCreate(savedInstanceState);
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+        getDelegate().onPostCreate(savedInstanceState);
+    }
+
+    public ActionBar getSupportActionBar() {
+        return getDelegate().getSupportActionBar();
+    }
+
+    public void setSupportActionBar(@Nullable Toolbar toolbar) {
+        getDelegate().setSupportActionBar(toolbar);
+    }
+
+    @Override
+    public MenuInflater getMenuInflater() {
+        return getDelegate().getMenuInflater();
+    }
+
+    @Override
+    public void setContentView(@LayoutRes int layoutResID) {
+        getDelegate().setContentView(layoutResID);
+    }
+
+    @Override
+    public void setContentView(View view) {
+        getDelegate().setContentView(view);
+    }
+
+    @Override
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().setContentView(view, params);
+    }
+
+    @Override
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getDelegate().addContentView(view, params);
+    }
+
+    @Override
+    protected void onPostResume() {
+        super.onPostResume();
+        getDelegate().onPostResume();
+    }
+
+    @Override
+    protected 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
+    protected void onStop() {
+        super.onStop();
+        getDelegate().onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        getDelegate().onDestroy();
+    }
+
+    public void invalidateOptionsMenu() {
+        getDelegate().invalidateOptionsMenu();
+    }
+
+    private AppCompatDelegate getDelegate() {
+        if (mDelegate == null) {
+            mDelegate = AppCompatDelegate.create(this, null);
+        }
+        return mDelegate;
+    }
+}

+ 269 - 0
app/src/main/java/info/knacki/pass/settings/ui/SettingsActivity.java

@@ -0,0 +1,269 @@
+package info.knacki.pass.settings.ui;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.support.v7.app.ActionBar;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceManager;
+import android.preference.RingtonePreference;
+import android.text.TextUtils;
+import android.view.MenuItem;
+import android.support.v4.app.NavUtils;
+
+import info.knacki.pass.R;
+
+import java.util.List;
+
+/**
+ * A {@link PreferenceActivity} that presents a set of application settings. On
+ * handset devices, settings are presented as a single list. On tablets,
+ * settings are split by category, with category headers shown to the left of
+ * the list of settings.
+ * <p>
+ * See <a href="http://developer.android.com/design/patterns/settings.html">
+ * Android Design: Settings</a> for design guidelines and the <a
+ * href="http://developer.android.com/guide/topics/ui/settings.html">Settings
+ * API Guide</a> for more information on developing a Settings UI.
+ */
+public class SettingsActivity extends AppCompatPreferenceActivity {
+
+    /**
+     * A preference value change listener that updates the preference's summary
+     * to reflect its new value.
+     */
+    private static Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = new Preference.OnPreferenceChangeListener() {
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object value) {
+            String stringValue = value.toString();
+
+            if (preference instanceof ListPreference) {
+                // For list preferences, look up the correct display value in
+                // the preference's 'entries' list.
+                ListPreference listPreference = (ListPreference) preference;
+                int index = listPreference.findIndexOfValue(stringValue);
+
+                // Set the summary to reflect the new value.
+                preference.setSummary(
+                        index >= 0
+                                ? listPreference.getEntries()[index]
+                                : null);
+
+            } else if (preference instanceof RingtonePreference) {
+                // For ringtone preferences, look up the correct display value
+                // using RingtoneManager.
+                if (TextUtils.isEmpty(stringValue)) {
+                    // Empty values correspond to 'silent' (no ringtone).
+                    preference.setSummary(R.string.pref_ringtone_silent);
+
+                } else {
+                    Ringtone ringtone = RingtoneManager.getRingtone(
+                            preference.getContext(), Uri.parse(stringValue));
+
+                    if (ringtone == null) {
+                        // Clear the summary if there was a lookup error.
+                        preference.setSummary(null);
+                    } else {
+                        // Set the summary to reflect the new ringtone display
+                        // name.
+                        String name = ringtone.getTitle(preference.getContext());
+                        preference.setSummary(name);
+                    }
+                }
+
+            } else {
+                // For all other preferences, set the summary to the value's
+                // simple string representation.
+                preference.setSummary(stringValue);
+            }
+            return true;
+        }
+    };
+
+    /**
+     * Helper method to determine if the device has an extra-large screen. For
+     * example, 10" tablets are extra-large.
+     */
+    private static boolean isXLargeTablet(Context context) {
+        return (context.getResources().getConfiguration().screenLayout
+                & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_XLARGE;
+    }
+
+    /**
+     * Binds a preference's summary to its value. More specifically, when the
+     * preference's value is changed, its summary (line of text below the
+     * preference title) is updated to reflect the value. The summary is also
+     * immediately updated upon calling this method. The exact display format is
+     * dependent on the type of preference.
+     *
+     * @see #sBindPreferenceSummaryToValueListener
+     */
+    private static void bindPreferenceSummaryToValue(Preference preference) {
+        // Set the listener to watch for value changes.
+        preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener);
+
+        // Trigger the listener immediately with the preference's
+        // current value.
+        sBindPreferenceSummaryToValueListener.onPreferenceChange(preference,
+                PreferenceManager
+                        .getDefaultSharedPreferences(preference.getContext())
+                        .getString(preference.getKey(), ""));
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setupActionBar();
+    }
+
+    /**
+     * Set up the {@link android.app.ActionBar}, if the API is available.
+     */
+    private void setupActionBar() {
+        ActionBar actionBar = getSupportActionBar();
+        if (actionBar != null) {
+            // Show the Up button in the action bar.
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    public boolean onMenuItemSelected(int featureId, MenuItem item) {
+        int id = item.getItemId();
+        if (id == android.R.id.home) {
+            if (!super.onMenuItemSelected(featureId, item)) {
+                NavUtils.navigateUpFromSameTask(this);
+            }
+            return true;
+        }
+        return super.onMenuItemSelected(featureId, item);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean onIsMultiPane() {
+        return isXLargeTablet(this);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public void onBuildHeaders(List<Header> target) {
+        loadHeadersFromResource(R.xml.pref_headers, target);
+    }
+
+    /**
+     * This method stops fragment injection in malicious applications.
+     * Make sure to deny any unknown fragments here.
+     */
+    protected boolean isValidFragment(String fragmentName) {
+        return PreferenceFragment.class.getName().equals(fragmentName)
+                || GeneralPreferenceFragment.class.getName().equals(fragmentName)
+                || DataSyncPreferenceFragment.class.getName().equals(fragmentName)
+                || NotificationPreferenceFragment.class.getName().equals(fragmentName);
+    }
+
+    /**
+     * This fragment shows general preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class GeneralPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_general);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("example_text"));
+            bindPreferenceSummaryToValue(findPreference("example_list"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows notification preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class NotificationPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_notification);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("notifications_new_message_ringtone"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+
+    /**
+     * This fragment shows data and sync preferences only. It is used when the
+     * activity is showing a two-pane settings UI.
+     */
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+    public static class DataSyncPreferenceFragment extends PreferenceFragment {
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            addPreferencesFromResource(R.xml.pref_data_sync);
+            setHasOptionsMenu(true);
+
+            // Bind the summaries of EditText/List/Dialog/Ringtone preferences
+            // to their values. When their values change, their summaries are
+            // updated to reflect the new value, per the Android Design
+            // guidelines.
+            bindPreferenceSummaryToValue(findPreference("sync_frequency"));
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            int id = item.getItemId();
+            if (id == android.R.id.home) {
+                startActivity(new Intent(getActivity(), SettingsActivity.class));
+                return true;
+            }
+            return super.onOptionsItemSelected(item);
+        }
+    }
+}

+ 121 - 0
app/src/main/java/info/knacki/pass/ui/AlertText.java

@@ -0,0 +1,121 @@
+package info.knacki.pass.ui;
+
+import android.content.Context;
+import android.content.DialogInterface;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.AppCompatEditText;
+import android.view.View;
+import android.widget.LinearLayout;
+
+public class AlertText {
+    protected final AlertDialog.Builder fAlertBuilder;
+    protected View fView = null;
+
+    interface OnClickListener {
+        void onClick(DialogInterface dialogInterface, View view);
+    }
+
+    public static class SimpleTextEdit extends AppCompatEditText {
+        SimpleTextEdit(Context c) {
+            super(c);
+        }
+
+        public SimpleTextEdit setText(String str) {
+            super.setText(str);
+            super.setSelection(str.length());
+            super.requestFocus();
+            return this;
+        }
+
+        public String getStr() {
+            return super.getText().toString().trim();
+        }
+    }
+
+    public static class TextEditAndCheckbox extends LinearLayout {
+        private AppCompatEditText fTextEdit;
+        private AppCompatCheckBox fCheckbox;
+
+        TextEditAndCheckbox(Context c) {
+            super(c);
+            setOrientation(VERTICAL);
+
+            fTextEdit = new AppCompatEditText(c);
+            addView(fTextEdit);
+
+            fCheckbox = new AppCompatCheckBox(c);
+            addView(fCheckbox);
+        }
+
+        public TextEditAndCheckbox setText(String str) {
+            fTextEdit.setText(str);
+            fTextEdit.setSelection(str.length());
+            fTextEdit.requestFocus();
+            return this;
+        }
+
+        public TextEditAndCheckbox setChecked(boolean value) {
+            fCheckbox.setChecked(value);
+            return this;
+        }
+
+        public TextEditAndCheckbox setCheckboxCaption(int textId) {
+            fCheckbox.setText(textId);
+            return this;
+        }
+
+        public String getStr() {
+            return fTextEdit.getText().toString().trim();
+        }
+
+        public boolean isChecked() {
+            return fCheckbox.isChecked();
+        }
+    }
+
+    AlertText(Context c) {
+        fAlertBuilder = new AlertDialog.Builder(c);
+    }
+
+    public AlertText setView(View v) {
+        fView = v;
+        fAlertBuilder.setView(v);
+        return this;
+    }
+
+    public AlertText setCancelable(boolean value) {
+        fAlertBuilder.setCancelable(value);
+        return this;
+    }
+
+    public AlertText setPositiveButton(int textId, final OnClickListener listener) {
+        fAlertBuilder.setPositiveButton(textId, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialogInterface, int i) {
+                listener.onClick(dialogInterface, fView);
+            }
+        });
+        return this;
+    }
+
+    public AlertText setNegativeButton(int textId, final OnClickListener listener) {
+        fAlertBuilder.setNegativeButton(textId, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialogInterface, int i) {
+                listener.onClick(dialogInterface, fView);
+            }
+        });
+        return this;
+    }
+
+    public AlertText setTitle(int textId) {
+        fAlertBuilder.setTitle(textId);
+        return this;
+    }
+
+    public AlertText show() {
+        fAlertBuilder.show();
+        return this;
+    }
+}

+ 81 - 0
app/src/main/java/info/knacki/pass/ui/EditPasswordActivity.java

@@ -0,0 +1,81 @@
+package info.knacki.pass.ui;
+
+import android.os.Bundle;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.AppCompatCheckBox;
+import android.support.v7.widget.AppCompatEditText;
+import android.text.InputType;
+import android.view.View;
+import android.widget.CompoundButton;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import info.knacki.pass.R;
+import info.knacki.pass.io.FileInterfaceFactory;
+
+public class EditPasswordActivity extends AppCompatActivity {
+    private static final Logger log = Logger.getLogger(EditPasswordActivity.class.getName());
+    public static final String INTENT_PATH = "EditPasswordActivity_PATH";
+    public static final String INTENT_EDIT = "EditPasswordActivity_EDIT";
+    protected File fOutputFile;
+    protected AppCompatEditText fTextEdit;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_pass_edit);
+        String path = getIntent().getStringExtra(INTENT_PATH);
+        if (path == null) {
+            finish();
+            throw new RuntimeException("Error: unset PATH");
+        }
+        fOutputFile = new File(path);
+        fTextEdit = findViewById(R.id.password_edit);
+        ((AppCompatCheckBox) findViewById(R.id.showPasswordCheckbox)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+            @Override
+            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+                final int cursorPos = fTextEdit.getSelectionEnd();
+                fTextEdit.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE | (compoundButton.isChecked() ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_TEXT_VARIATION_PASSWORD));
+                fTextEdit.setSelection(cursorPos);
+            }
+        });
+        findViewById(R.id.saveButton).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                try {
+                    FileInterfaceFactory.GetFileInterface(fOutputFile).WriteFile(fTextEdit.getText().toString());
+                }
+                catch (IOException e) {
+                    Toast.makeText(EditPasswordActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+                    log.log(Level.WARNING, e.getMessage(), e);
+                    return;
+                }
+                EditPasswordActivity.this.finish();
+            }
+        });
+        boolean editMode = getIntent().getBooleanExtra(INTENT_EDIT, true);
+        populateContent(editMode);
+        if (!editMode) {
+            ((AppCompatCheckBox) findViewById(R.id.showPasswordCheckbox)).setChecked(true);
+        }
+    }
+
+    protected void populateContent(boolean requestFocus) {
+        try {
+            String pass = FileInterfaceFactory.GetFileInterface(fOutputFile).ReadFile();
+            fTextEdit.setText(pass);
+            if (requestFocus) {
+                fTextEdit.setSelection(pass.length());
+                fTextEdit.requestFocus();
+            }
+        }
+        catch (IOException e) {
+            Toast.makeText(EditPasswordActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+            log.log(Level.WARNING, e.getMessage(), e);
+        }
+    }
+}

+ 189 - 0
app/src/main/java/info/knacki/pass/ui/MainActivity.java

@@ -0,0 +1,189 @@
+package info.knacki.pass.ui;
+
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.support.v7.app.AppCompatActivity;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ScrollView;
+import android.widget.Toast;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import info.knacki.pass.R;
+import info.knacki.pass.generator.PasswordGenerator;
+import info.knacki.pass.generator.ui.PasswordGeneratorWizard;
+import info.knacki.pass.io.FileInterfaceFactory;
+import info.knacki.pass.io.IFileInterface;
+import info.knacki.pass.settings.ui.SettingsActivity;
+
+public class MainActivity extends AppCompatActivity implements PasswordClickListener {
+    private final Logger log = Logger.getLogger(MainActivity.class.getName());
+    private PasswordListView vPasswordListView;
+
+    protected AlertText.TextEditAndCheckbox lastFileName = null;
+
+    protected void CreateNewFolder() {
+        new AlertText(this)
+                .setCancelable(true)
+                .setView(new AlertText.SimpleTextEdit(this))
+                .setPositiveButton(R.string.add, new AlertText.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, View view) {
+                        final String filename = ((AlertText.SimpleTextEdit) view).getStr();
+
+                        if (filename.length() == 0) {
+                            Toast.makeText(MainActivity.this, "Error: Empty file name", Toast.LENGTH_LONG).show();
+                            return;
+                        }
+                        File f = new File(vPasswordListView.fCurrentDir +"/" +filename);
+                        if (!f.mkdir()) {
+                            Toast.makeText(MainActivity.this, "Error: cannot create folder", Toast.LENGTH_LONG).show();
+                        }
+                        vPasswordListView.refresh();
+                    }
+                })
+                .setNegativeButton(R.string.cancel, new AlertText.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, View s) {
+                        dialogInterface.cancel();
+                    }
+                })
+                .setTitle(R.string.add_title)
+                .show();
+    }
+
+    protected void CreateNewPassword() {
+        if (lastFileName == null) {
+            lastFileName = new AlertText.TextEditAndCheckbox(this);
+        }
+        new AlertText(this)
+                .setCancelable(true)
+                .setView(new AlertText.TextEditAndCheckbox(this)
+                    .setText(lastFileName.getStr())
+                    .setCheckboxCaption(R.string.add_generate)
+                    .setChecked(lastFileName.isChecked()))
+                .setPositiveButton(R.string.add, new AlertText.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, View view) {
+                        lastFileName = (AlertText.TextEditAndCheckbox) view;
+                        String filename = lastFileName.getStr();
+                        if (filename.length() == 0) {
+                            Toast.makeText(MainActivity.this, "Error: Empty file name", Toast.LENGTH_LONG).show();
+                            return;
+                        }
+                        File f = new File(vPasswordListView.fCurrentDir +"/" +filename);
+                        try {
+                            f.createNewFile();
+                            vPasswordListView.refresh();
+                        }
+                        catch (IOException e) {
+                            Toast.makeText(MainActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+                            log.log(Level.WARNING, e.getMessage(), e);
+                            return;
+                        }
+                        if (lastFileName.isChecked()) {
+                            GeneratePassword(f);
+                        } else {
+                            EditFile(f, true);
+                        }
+                        lastFileName = null;
+                    }
+                })
+                .setNegativeButton(R.string.cancel, new AlertText.OnClickListener() {
+                    @Override
+                    public void onClick(DialogInterface dialogInterface, View s) {
+                        dialogInterface.cancel();
+                    }
+                })
+                .setTitle(R.string.add_title)
+                .show();
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_pass_list);
+
+        setTitle(R.string.title);
+        vPasswordListView = new PasswordListView(this, getApplicationContext().getFilesDir().getAbsolutePath() +"/" +getResources().getString(R.string.data_dir_name));
+        ((ScrollView)findViewById(R.id.passwordListContainer)).addView(vPasswordListView);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case 0:
+                break;
+            case R.id.new_folder:
+                CreateNewFolder();
+                break;
+            case R.id.new_password:
+                CreateNewPassword();
+                break;
+            case R.id.settings:
+                ShowSettings();
+                break;
+            default:
+                log.log(Level.WARNING, "Error: Unknown MenuItem id: " +item.getItemId());
+                return false;
+        }
+        return true;
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        getMenuInflater().inflate(R.menu.main_menu, menu);
+        return super.onCreateOptionsMenu(menu);
+    }
+
+    protected void GeneratePassword(final File f) {
+        final PasswordGeneratorWizard wiz = new PasswordGeneratorWizard(this);
+
+        new AlertText(this)
+            .setCancelable(true)
+            .setView(wiz)
+            .setPositiveButton(R.string.add, new AlertText.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, View view) {
+                    IFileInterface writer = FileInterfaceFactory.GetFileInterface(f);
+                    try {
+                        writer.WriteFile(PasswordGenerator.generate(wiz));
+                    }
+                    catch (IOException e) {
+                        Toast.makeText(MainActivity.this, "Error: " +e.getMessage(), Toast.LENGTH_LONG).show();
+                        log.log(Level.WARNING, e.getMessage(), e);
+                    }
+                }
+            })
+            .setNegativeButton(R.string.cancel, new AlertText.OnClickListener() {
+                @Override
+                public void onClick(DialogInterface dialogInterface, View s) {
+                    dialogInterface.cancel();
+                }
+            })
+            .setTitle(R.string.generate_title)
+            .show();
+    }
+
+    protected void EditFile(final File f, boolean editMode) {
+        Intent i = new Intent(this, EditPasswordActivity.class);
+        i.putExtra(EditPasswordActivity.INTENT_PATH, f.getAbsolutePath());
+        i.putExtra(EditPasswordActivity.INTENT_EDIT, editMode);
+        startActivity(i);
+    }
+
+    protected void ShowSettings() {
+        startActivity(new Intent(this, SettingsActivity.class));
+    }
+
+    @Override
+    public void OnPasswordClicked(File f) {
+        EditFile(f, false);
+    }
+}

+ 7 - 0
app/src/main/java/info/knacki/pass/ui/PasswordClickListener.java

@@ -0,0 +1,7 @@
+package info.knacki.pass.ui;
+
+import java.io.File;
+
+public interface PasswordClickListener {
+    void OnPasswordClicked(File f);
+}

+ 116 - 0
app/src/main/java/info/knacki/pass/ui/PasswordListView.java

@@ -0,0 +1,116 @@
+package info.knacki.pass.ui;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.view.View;
+import android.widget.LinearLayout;
+
+import java.io.File;
+import java.util.ArrayDeque;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+public class PasswordListView<T extends Context & PasswordClickListener> extends LinearLayout {
+    public final String fRootPath;
+    public String fCurrentDir;
+    protected final PasswordClickListener fClickListener;
+
+    private class FileIdent implements Comparable<FileIdent> {
+        final boolean fIsDir;
+        final String fName;
+        private boolean fParent = false;
+
+        FileIdent(boolean isDir, String name) {
+            fIsDir = isDir;
+            fName = name;
+        }
+
+        public FileIdent setParent() {
+            fParent = true;
+            return this;
+        }
+
+        public boolean isParent() {
+            return fParent;
+        }
+
+        @Override
+        public int compareTo(@NonNull FileIdent fileIdent) {
+            if (fIsDir && !fileIdent.fIsDir)
+                return -1;
+            if (!fIsDir && fileIdent.fIsDir)
+                return 1;
+            return fName.compareTo(fileIdent.fName);
+        }
+    }
+
+    protected PasswordListView ClearView() {
+        removeAllViews();
+        return this;
+    }
+
+    protected PasswordView CreateView(final FileIdent file) {
+        PasswordView pv = new PasswordView(getContext(), file.fIsDir ? ((file.isParent() ? PasswordView.TYPE_PARENT : 0) | PasswordView.TYPE_DIR) : PasswordView.TYPE_PASSWORD, file.fName);
+        pv.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                File f = new File(fCurrentDir +"/" +file.fName);
+                if (file.isParent()) {
+                    f = new File(fCurrentDir).getParentFile();
+                    DisplayDir(f);
+                }
+                if (f.isDirectory())
+                    DisplayDir(f);
+                else
+                    fClickListener.OnPasswordClicked(f);
+            }
+        });
+        return pv;
+    }
+
+    public PasswordListView(final T ctx, String rootPath) {
+        super(ctx);
+        fClickListener = ctx;
+        File rootDir = new File(rootPath);
+        if (!rootDir.exists()) {
+            rootDir.mkdirs();
+        }
+        fRootPath = rootPath;
+        setOrientation(VERTICAL);
+        DisplayDir(rootDir);
+    }
+
+    protected Boolean IsAccessGranted(File f) {
+        if ((f.getName().charAt(0) == '.') && !f.getName().equals("..")) {
+            return false;
+        } else if (fRootPath.equals(f.getAbsolutePath())) {
+            return false;
+        }
+        return f.getAbsolutePath().startsWith(fRootPath);
+    }
+
+    public void refresh() {
+        DisplayDir(new File(fCurrentDir));
+    }
+
+    public void reset() {
+        DisplayDir(new File(fRootPath));
+    }
+
+    protected void DisplayDir(final File f) {
+        ClearView();
+        SortedSet<FileIdent> files = new TreeSet<>();
+
+        for (File i: f.listFiles()) {
+            if (IsAccessGranted(i)) {
+                files.add(new FileIdent(i.isDirectory(), i.getName()));
+            }
+        }
+        if (!f.getAbsolutePath().equals(fRootPath)) {
+            addView(CreateView(new FileIdent(true, "Parent").setParent()));
+        }
+        for (FileIdent i: files)
+            addView(CreateView(i));
+        fCurrentDir = f.getAbsolutePath();
+    }
+}

+ 43 - 0
app/src/main/java/info/knacki/pass/ui/PasswordView.java

@@ -0,0 +1,43 @@
+package info.knacki.pass.ui;
+
+import android.content.Context;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import info.knacki.pass.R;
+
+public class PasswordView extends LinearLayout {
+    public final static int ICON_SIZE = 128;
+    protected ImageView vIcon;
+    protected TextView vName;
+
+    public static int TYPE_DIR = 1;
+    public static int TYPE_PARENT = 2;
+    public static int TYPE_PASSWORD = 4;
+
+    public PasswordView(Context ctx, int type, String name) {
+        super(ctx);
+        vIcon = new ImageView(ctx);
+        if ((type & TYPE_PARENT) != 0)
+            vIcon.setImageResource(android.R.drawable.ic_media_previous);
+        else if ((type & TYPE_DIR) != 0)
+            vIcon.setImageResource(R.drawable.ic_doc_folder);
+        else
+            vIcon.setImageResource(R.drawable.ic_doc_text);
+        vIcon.setMinimumHeight(ICON_SIZE);
+        vIcon.setMinimumWidth(ICON_SIZE);
+        addView(vIcon);
+        vName = new TextView(ctx);
+        vName.setHeight(ICON_SIZE);
+        vName.setGravity(Gravity.CENTER_VERTICAL);
+        vName.setText(name);
+        addView(vName);
+        setClickable(true);
+    }
+}

+ 24 - 0
app/src/main/res/drawable/ic_doc_folder.xml

@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<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="#FF737373"
+        android:pathData="M10 4H4c-1.1 0,-1.99,0.9,-1.99 2L2 18c0 1.1,0.9 2 2 2h16c1.1 0 2,-0.9 2,-2V8c0,-1.1,-0.9,-2,-2,-2h-8l-2,-2z"/>
+</vector>

+ 24 - 0
app/src/main/res/drawable/ic_doc_text.xml

@@ -0,0 +1,24 @@
+<!--
+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.
+-->
+<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="#FF737373"
+        android:pathData="M14 2H6c-1.1 0,-1.99,0.9,-1.99 2L4 20c0 1.1,0.89 2 1.99 2H18c1.1 0 2,-0.9 2,-2V8l-6,-6zm2 16H8v-2h8v2zm0,-4H8v-2h8v2zm-3,-5V3.5L18.5 9H13z"/>
+</vector>

+ 37 - 0
app/src/main/res/drawable/ic_fingerprint.xml

@@ -0,0 +1,37 @@
+<!--
+  ~ 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
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24.0dp"
+        android:height="24.0dp"
+        android:tint="?attr/colorControlNormal"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M17.5,4.47c-0.08,0.0 -0.16,-0.02 -0.24,-0.06C15.45,3.42 13.88,3.0 12.01,3.0c-1.87,0.0 -3.64,0.47 -5.25,1.4C6.52,4.54 6.22,4.46 6.08,4.22C5.94,3.98 6.02,3.67 6.26,3.54C8.03,2.52 9.96,2.0 12.01,2.0c2.02,0.0 3.79,0.47 5.73,1.53c0.24,0.13 0.33,0.44 0.2,0.68C17.85,4.38 17.68,4.47 17.5,4.47z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M3.95,9.72c-0.1,0.0 -0.19,-0.03 -0.28,-0.08C3.44,9.48 3.38,9.17 3.54,8.94c0.94,-1.4 2.14,-2.5 3.56,-3.28c2.99,-1.63 6.82,-1.63 9.81,-0.01c1.42,0.77 2.61,1.87 3.56,3.26c0.15,0.23 0.09,0.54 -0.13,0.69c-0.23,0.16 -0.54,0.09 -0.69,-0.13c-0.85,-1.26 -1.93,-2.24 -3.2,-2.94c-2.7,-1.47 -6.16,-1.46 -8.86,0.01C6.3,7.24 5.22,8.23 4.37,9.5C4.27,9.64 4.11,9.72 3.95,9.72z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M9.86,21.79c-0.13,0.0 -0.27,-0.05 -0.36,-0.16c-0.82,-0.87 -1.26,-1.43 -1.9,-2.63c-0.65,-1.23 -1.0,-2.73 -1.0,-4.33c0.0,-2.97 2.42,-5.39 5.39,-5.39s5.39,2.42 5.39,5.39c0.0,0.28 -0.22,0.5 -0.5,0.5s-0.5,-0.22 -0.5,-0.5c0.0,-2.42 -1.97,-4.39 -4.39,-4.39S7.6,12.24 7.6,14.66c0.0,1.44 0.3,2.78 0.88,3.86c0.61,1.15 1.02,1.64 1.75,2.42c0.19,0.2 0.18,0.52 -0.02,0.71C10.11,21.74 9.98,21.79 9.86,21.79z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M16.7,19.94c-1.14,0.0 -2.13,-0.3 -2.96,-0.89c-1.41,-1.01 -2.25,-2.65 -2.25,-4.38c0.0,-0.28 0.22,-0.5 0.5,-0.5s0.5,0.22 0.5,0.5c0.0,1.41 0.69,2.75 1.83,3.57c0.66,0.48 1.44,0.71 2.38,0.71c0.23,0.0 0.6,-0.02 0.98,-0.1c0.27,-0.05 0.53,0.13 0.58,0.4c0.05,0.27 -0.13,0.53 -0.4,0.58C17.3,19.93 16.83,19.94 16.7,19.94z"/>
+    <path
+        android:fillColor="#ffffff"
+        android:pathData="M14.76,22.0c-0.05,0.0 -0.09,-0.01 -0.14,-0.02c-1.51,-0.44 -2.51,-1.03 -3.53,-2.11c-1.32,-1.39 -2.05,-3.24 -2.05,-5.21c0.0,-1.62 1.32,-2.94 2.94,-2.94c1.62,0.0 2.94,1.32 2.94,2.94c0.0,1.07 0.87,1.94 1.94,1.94s1.94,-0.87 1.94,-1.94c0.0,-3.77 -3.07,-6.83 -6.83,-6.83c-2.68,0.0 -5.12,1.58 -6.23,4.02c-0.37,0.81 -0.56,1.76 -0.56,2.81c0.0,0.78 0.07,2.01 0.63,3.61c0.09,0.26 -0.04,0.55 -0.3,0.64c-0.26,0.09 -0.55,-0.04 -0.64,-0.3c-0.46,-1.31 -0.69,-2.6 -0.69,-3.95c0.0,-1.2 0.22,-2.28 0.65,-3.23c1.27,-2.8 4.07,-4.61 7.14,-4.61c4.32,0.0 7.83,3.51 7.83,7.83c0.0,1.62 -1.32,2.94 -2.94,2.94c-1.62,0.0 -2.94,-1.32 -2.94,-2.94c0.0,-1.07 -0.87,-1.94 -1.94,-1.94s-1.94,0.87 -1.94,1.94c0.0,1.71 0.63,3.32 1.77,4.52c0.9,0.95 1.74,1.45 3.08,1.84c0.27,0.08 0.42,0.35 0.34,0.62C15.18,21.86 14.98,22.0 14.76,22.0z"/>
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_info_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="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm1,15h-2v-6h2v6zm0,-8h-2V7h2v2z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_notifications_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="M11.5,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2zm6.5,-6v-5.5c0,-3.07 -2.13,-5.64 -5,-6.32V3.5c0,-0.83 -0.67,-1.5 -1.5,-1.5S10,2.67 10,3.5v0.68c-2.87,0.68 -5,3.25 -5,6.32V16l-2,2v1h17v-1l-2,-2z" />
+</vector>

+ 9 - 0
app/src/main/res/drawable/ic_sync_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="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01,-0.25 1.97,-0.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0,-4.42,-3.58,-8,-8,-8zm0 14c-3.31 0,-6,-2.69,-6,-6 0,-1.01,0.25,-1.97,0.7,-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4,-4,-4,-4v3z" />
+</vector>

+ 39 - 0
app/src/main/res/layout/activity_pass_edit.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout
+    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:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+        <android.support.v7.widget.AppCompatEditText
+            android:id="@+id/password_edit"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ems="10"
+            android:inputType="textMultiLine|textPassword"
+            tools:layout_editor_absoluteX="0dp"
+            tools:layout_editor_absoluteY="0dp"/>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="1"
+            android:gravity="bottom">
+            <android.support.v7.widget.AppCompatCheckBox
+                android:id="@+id/showPasswordCheckbox"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:checked="false"
+                android:text="@string/edit_ShowPassword"/>
+
+            <android.support.v7.widget.AppCompatButton
+                android:id="@+id/saveButton"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/edit_Save" />
+        </LinearLayout>
+    </LinearLayout>
+</android.support.constraint.ConstraintLayout>

+ 10 - 11
app/src/main/res/layout/activity_pass_list.xml

@@ -1,19 +1,18 @@
 <?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=".passListActivity">
+    tools:context=".ui.MainActivity">
 
-    <TextView
-        android:id="@+id/sample_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="Hello World!"
-        app:layout_constraintBottom_toBottomOf="parent"
-        app:layout_constraintLeft_toLeftOf="parent"
-        app:layout_constraintRight_toRightOf="parent"
-        app:layout_constraintTop_toTopOf="parent" />
+<LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <ScrollView
+        android:id="@+id/passwordListContainer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>
 
 </android.support.constraint.ConstraintLayout>

+ 56 - 0
app/src/main/res/layout/input.xml

@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout 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:maxHeight="664px"
+    tools:context=".ui.MainActivity"
+    android:background="@color/inputBackground">
+
+<LinearLayout
+    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:padding="5dp">
+
+        <TextView
+            style="@android:style/Widget.DeviceDefault.Light.TextView"
+            android:id="@+id/prevButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:clickable="true"
+            android:focusable="true"
+            android:text="@string/prev"
+            android:layout_weight="0.5"
+            android:textStyle="bold" />
+        <View
+            android:layout_width="1dp"
+            android:layout_height="match_parent"
+            android:layout_weight="5" />
+        <TextView
+            style="@android:style/Widget.DeviceDefault.Light.TextView"
+            android:id="@+id/openAppButton"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:clickable="true"
+            android:focusable="true"
+            android:text="@string/title"
+            android:layout_weight="0.5"
+            android:layout_gravity="end"
+            android:textAlignment="textEnd"
+            android:textStyle="bold" />
+    </LinearLayout>
+    <View
+        android:layout_width="match_parent"
+        android:layout_height="1dp"
+        android:background="@color/colorSeparator"/>
+    <ScrollView
+        android:id="@+id/passwordListContainer"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+</LinearLayout>
+
+</android.support.constraint.ConstraintLayout>

+ 10 - 0
app/src/main/res/menu/main_menu.xml

@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:title="@string/New">
+        <menu>
+            <item android:id="@+id/new_folder" android:title="@string/newFolder"/>
+            <item android:id="@+id/new_password" android:title="@string/newPassword"/>
+        </menu>
+    </item>
+    <item android:id="@+id/settings" android:title="@string/settings"/>
+</menu>

+ 28 - 0
app/src/main/res/values-fr/lang.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="title">Password store</string>
+    <string name="add">Ajouter</string>
+    <string name="New">Nouveau</string>
+    <string name="newFolder">Dossier</string>
+    <string name="newPassword">Mot de passe</string>
+    <string name="add_title">Nom de fichier</string>
+    <string name="add_generate">Générer le mot de passe</string>
+    <string name="generate_title">Génération de mot de passe</string>
+    <string name="generate_number">Avec des chiffres</string>
+    <string name="generate_alpha">Avec des lettres</string>
+    <string name="generate_capitalize">Avec des majuscules</string>
+    <string name="generate_special">Avec des caractès spéciaux</string>
+    <string name="generate_difficulty">Force du mot de passe</string>
+    <string name="edit_ShowPassword">Montrer le mot de passe</string>
+    <string name="edit_Save">Sauvegarder</string>
+    <string name="cancel">Annuler</string>
+    <string name="prev">Précedent</string>
+    <string name="settings">Configuration</string>
+    <array name="fDifficulty">
+        <item>Code PIN</item>
+        <item>Faible</item>
+        <item>Médiocre</item>
+        <item>Fort</item>
+        <item>Personalisé</item>
+    </array>
+</resources>

+ 2 - 0
app/src/main/res/values/colors.xml

@@ -3,4 +3,6 @@
     <color name="colorPrimary">#3F51B5</color>
     <color name="colorPrimaryDark">#303F9F</color>
     <color name="colorAccent">#FF4081</color>
+    <color name="colorSeparator">#333333</color>
+    <color name="inputBackground">#dadae0</color>
 </resources>

+ 28 - 0
app/src/main/res/values/lang.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="title">Password store</string>
+    <string name="add">Add</string>
+    <string name="New">New</string>
+    <string name="newFolder">Folder</string>
+    <string name="newPassword">Password</string>
+    <string name="add_title">New filename</string>
+    <string name="add_generate">Generate password</string>
+    <string name="generate_title">Generate password</string>
+    <string name="generate_number">With numbers</string>
+    <string name="generate_alpha">With letters</string>
+    <string name="generate_capitalize">Capitalize letters</string>
+    <string name="generate_special">With specials chars</string>
+    <string name="generate_difficulty">Password strength</string>
+    <string name="edit_ShowPassword">Show password</string>
+    <string name="edit_Save">Save</string>
+    <string name="cancel">Cancel</string>
+    <string name="prev">Previous</string>
+    <string name="settings">Settings</string>
+    <array name="fDifficulty">
+        <item>Number PIN</item>
+        <item>Weak</item>
+        <item>Average</item>
+        <item>Strong</item>
+        <item>Custom</item>
+    </array>
+</resources>

+ 74 - 0
app/src/main/res/values/strings.xml

@@ -1,3 +1,77 @@
 <resources>
     <string name="app_name">pass</string>
+    <string name="data_dir_name">pass</string>
+    <string name="title_activity_settings">Settings</string>
+
+    <!-- Strings related to Settings -->
+
+    <!-- Example General settings -->
+    <string name="pref_header_general">General</string>
+
+    <string name="pref_title_social_recommendations">Enable social recommendations</string>
+    <string name="pref_description_social_recommendations">Recommendations for people to contact
+        based on your message history
+    </string>
+
+    <string name="pref_title_display_name">Display name</string>
+    <string name="pref_default_display_name">John Smith</string>
+
+    <string name="pref_title_add_friends_to_messages">Add friends to messages</string>
+    <string-array name="pref_example_list_titles">
+        <item>Always</item>
+        <item>When possible</item>
+        <item>Never</item>
+    </string-array>
+    <string-array name="pref_example_list_values">
+        <item>1</item>
+        <item>0</item>
+        <item>-1</item>
+    </string-array>
+
+    <!-- Example settings for Data & Sync -->
+    <string name="pref_header_data_sync">Data &amp; sync</string>
+
+    <string name="pref_title_sync_frequency">Sync frequency</string>
+    <string-array name="pref_sync_frequency_titles">
+        <item>15 minutes</item>
+        <item>30 minutes</item>
+        <item>1 hour</item>
+        <item>3 hours</item>
+        <item>6 hours</item>
+        <item>Never</item>
+    </string-array>
+    <string-array name="pref_sync_frequency_values">
+        <item>15</item>
+        <item>30</item>
+        <item>60</item>
+        <item>180</item>
+        <item>360</item>
+        <item>-1</item>
+    </string-array>
+
+    <string-array name="list_preference_entries">
+        <item>Entry 1</item>
+        <item>Entry 2</item>
+        <item>Entry 3</item>
+    </string-array>
+
+    <string-array name="list_preference_entry_values">
+        <item>1</item>
+        <item>2</item>
+        <item>3</item>
+    </string-array>
+
+    <string-array name="multi_select_list_preference_default_value" />
+
+    <string name="pref_title_system_sync_settings">System sync settings</string>
+
+    <!-- Example settings for Notifications -->
+    <string name="pref_header_notifications">Notifications</string>
+
+    <string name="pref_title_new_message_notifications">New message notifications</string>
+
+    <string name="pref_title_ringtone">Ringtone</string>
+    <string name="pref_ringtone_silent">Silent</string>
+
+    <string name="pref_title_vibrate">Vibrate</string>
 </resources>

+ 3 - 0
app/src/main/res/xml/method.xml

@@ -0,0 +1,3 @@
+<input-method xmlns:android="http://schemas.android.com/apk/res/android">
+    <subtype android:imeSubtypeMode="keyboard" />
+</input-method>

+ 21 - 0
app/src/main/res/xml/pref_data_sync.xml

@@ -0,0 +1,21 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
+         dismiss it. -->
+    <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
+    <ListPreference
+        android:defaultValue="180"
+        android:entries="@array/pref_sync_frequency_titles"
+        android:entryValues="@array/pref_sync_frequency_values"
+        android:key="sync_frequency"
+        android:negativeButtonText="@null"
+        android:positiveButtonText="@null"
+        android:title="@string/pref_title_sync_frequency" />
+
+    <!-- This preference simply launches an intent when selected. Use this UI sparingly, per
+         design guidelines. -->
+    <Preference android:title="@string/pref_title_system_sync_settings">
+        <intent android:action="android.settings.SYNC_SETTINGS" />
+    </Preference>
+
+</PreferenceScreen>

+ 33 - 0
app/src/main/res/xml/pref_general.xml

@@ -0,0 +1,33 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <SwitchPreference
+        android:defaultValue="true"
+        android:key="example_switch"
+        android:summary="@string/pref_description_social_recommendations"
+        android:title="@string/pref_title_social_recommendations" />
+
+    <!-- NOTE: EditTextPreference accepts EditText attributes. -->
+    <!-- NOTE: EditTextPreference's summary should be set to its value by the activity code. -->
+    <EditTextPreference
+        android:capitalize="words"
+        android:defaultValue="@string/pref_default_display_name"
+        android:inputType="textCapWords"
+        android:key="example_text"
+        android:maxLines="1"
+        android:selectAllOnFocus="true"
+        android:singleLine="true"
+        android:title="@string/pref_title_display_name" />
+
+    <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
+         dismiss it. -->
+    <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
+    <ListPreference
+        android:defaultValue="-1"
+        android:entries="@array/pref_example_list_titles"
+        android:entryValues="@array/pref_example_list_values"
+        android:key="example_list"
+        android:negativeButtonText="@null"
+        android:positiveButtonText="@null"
+        android:title="@string/pref_title_add_friends_to_messages" />
+
+</PreferenceScreen>

+ 20 - 0
app/src/main/res/xml/pref_headers.xml

@@ -0,0 +1,20 @@
+<preference-headers xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- These settings headers are only used on tablets. -->
+
+    <header
+        android:fragment="info.knacki.pass.settings.ui.SettingsActivity$GeneralPreferenceFragment"
+        android:icon="@drawable/ic_info_black_24dp"
+        android:title="@string/pref_header_general" />
+
+    <header
+        android:fragment="info.knacki.pass.settings.ui.SettingsActivity$NotificationPreferenceFragment"
+        android:icon="@drawable/ic_notifications_black_24dp"
+        android:title="@string/pref_header_notifications" />
+
+    <header
+        android:fragment="info.knacki.pass.settings.ui.SettingsActivity$DataSyncPreferenceFragment"
+        android:icon="@drawable/ic_sync_black_24dp"
+        android:title="@string/pref_header_data_sync" />
+
+</preference-headers>

+ 27 - 0
app/src/main/res/xml/pref_notification.xml

@@ -0,0 +1,27 @@
+<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <!-- A 'parent' preference, which enables/disables child preferences (below)
+         when checked/unchecked. -->
+    <SwitchPreference
+        android:defaultValue="true"
+        android:key="notifications_new_message"
+        android:title="@string/pref_title_new_message_notifications" />
+
+    <!-- Allows the user to choose a ringtone in the 'notification' category. -->
+    <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
+    <!-- NOTE: RingtonePreference's summary should be set to its value by the activity code. -->
+    <RingtonePreference
+        android:defaultValue="content://settings/system/notification_sound"
+        android:dependency="notifications_new_message"
+        android:key="notifications_new_message_ringtone"
+        android:ringtoneType="notification"
+        android:title="@string/pref_title_ringtone" />
+
+    <!-- NOTE: This preference will be enabled only when the checkbox above is checked. -->
+    <SwitchPreference
+        android:defaultValue="true"
+        android:dependency="notifications_new_message"
+        android:key="notifications_new_message_vibrate"
+        android:title="@string/pref_title_vibrate" />
+
+</PreferenceScreen>

+ 1 - 1
build.gradle

@@ -7,7 +7,7 @@ buildscript {
         jcenter()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.1.0'
+        classpath 'com.android.tools.build:gradle:3.1.4'
         
 
         // NOTE: Do not place your application dependencies here; they belong