diff options
20 files changed, 486 insertions, 75 deletions
| diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 6a794b1b9..d05b0e020 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -38,12 +38,34 @@          <activity              android:name=".PublicKeyListActivity"              android:label="@string/title_managePublicKeys" -            android:configChanges="keyboardHidden|orientation|keyboard" /> +            android:configChanges="keyboardHidden|orientation|keyboard" +            android:launchMode="singleTop"> + +            <intent-filter> +                <action android:name="android.intent.action.SEARCH" /> +            </intent-filter> + +            <meta-data +                android:name="android.app.searchable" +                android:resource="@xml/searchable_public_keys"/> + +        </activity>          <activity              android:name=".SecretKeyListActivity"              android:label="@string/title_manageSecretKeys" -            android:configChanges="keyboardHidden|orientation|keyboard"/> +            android:configChanges="keyboardHidden|orientation|keyboard" +            android:launchMode="singleTop"> + +            <intent-filter> +                <action android:name="android.intent.action.SEARCH" /> +            </intent-filter> + +            <meta-data +                android:name="android.app.searchable" +                android:resource="@xml/searchable_secret_keys"/> + +        </activity>          <activity              android:name=".EditKeyActivity" @@ -53,7 +75,8 @@          <activity              android:name=".SelectPublicKeyListActivity"              android:label="@string/title_selectRecipients" -            android:configChanges="keyboardHidden|orientation|keyboard"> +            android:configChanges="keyboardHidden|orientation|keyboard" +            android:launchMode="singleTop">              <intent-filter>                  <action android:name="org.thialfihar.android.apg.intent.SELECT_PUBLIC_KEYS" /> @@ -61,12 +84,21 @@                  <data android:mimeType="text/*"/>              </intent-filter> +            <intent-filter> +                <action android:name="android.intent.action.SEARCH" /> +            </intent-filter> + +            <meta-data +                android:name="android.app.searchable" +                android:resource="@xml/searchable_public_keys"/> +          </activity>          <activity              android:name=".SelectSecretKeyListActivity"              android:label="@string/title_selectSignature" -            android:configChanges="keyboardHidden|orientation|keyboard"> +            android:configChanges="keyboardHidden|orientation|keyboard" +            android:launchMode="singleTop">              <intent-filter>                  <action android:name="org.thialfihar.android.apg.intent.SELECT_SECRET_KEY" /> @@ -74,6 +106,14 @@                  <data android:mimeType="text/*"/>              </intent-filter> +            <intent-filter> +                <action android:name="android.intent.action.SEARCH" /> +            </intent-filter> + +            <meta-data +                android:name="android.app.searchable" +                android:resource="@xml/searchable_secret_keys"/> +          </activity>          <activity diff --git a/res/layout/filter_info.xml b/res/layout/filter_info.xml new file mode 100644 index 000000000..8e07f2434 --- /dev/null +++ b/res/layout/filter_info.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="UTF-8"?>
 +<LinearLayout
 +    xmlns:android="http://schemas.android.com/apk/res/android"
 +    android:layout_width="fill_parent"
 +    android:layout_height="wrap_content"
 +    android:paddingBottom="5dip"
 +    android:orientation="horizontal">
 +
 +    <TextView
 +        android:id="@+id/filterInfo"
 +        android:layout_width="0dip"
 +        android:layout_height="wrap_content"
 +        android:layout_weight="1"
 +        android:textAppearance="?android:attr/textAppearanceMedium"
 +        android:ellipsize="end"/>
 +
 +    <Button
 +        android:id="@+id/btn_clear"
 +        android:text="@string/btn_clearFilter"
 +        android:layout_width="wrap_content"
 +        android:layout_height="wrap_content"/>
 +
 +</LinearLayout>
 diff --git a/res/layout/key_list.xml b/res/layout/key_list.xml index f08495368..ac07801ab 100644 --- a/res/layout/key_list.xml +++ b/res/layout/key_list.xml @@ -14,15 +14,18 @@       limitations under the License.  --> -<ScrollView +<LinearLayout      xmlns:android="http://schemas.android.com/apk/res/android"      android:layout_width="fill_parent"      android:layout_height="fill_parent" -    android:fillViewport="true"> +    android:orientation="vertical"> + +    <include android:id="@+id/layout_filter" layout="@layout/filter_info"/>      <ExpandableListView          android:id="@+id/list"          android:layout_width="fill_parent" -        android:layout_height="fill_parent"/> +        android:layout_height="fill_parent" +        android:isScrollContainer="true"/> -</ScrollView> +</LinearLayout>
\ No newline at end of file diff --git a/res/layout/select_public_key.xml b/res/layout/select_public_key.xml index 3c7c6534a..9ce35a7a8 100644 --- a/res/layout/select_public_key.xml +++ b/res/layout/select_public_key.xml @@ -21,6 +21,8 @@      android:layout_height="fill_parent"      android:fillViewport="true"> +    <include android:id="@+id/layout_filter" layout="@layout/filter_info"/> +      <ListView          android:id="@+id/list"          android:choiceMode="multipleChoice" diff --git a/res/layout/select_secret_key.xml b/res/layout/select_secret_key.xml index f252f56e5..feabc6160 100644 --- a/res/layout/select_secret_key.xml +++ b/res/layout/select_secret_key.xml @@ -21,6 +21,8 @@      android:layout_height="fill_parent"      android:fillViewport="true"> +    <include android:id="@+id/layout_filter" layout="@layout/filter_info"/> +      <ListView          android:id="@+id/list"          android:layout_width="fill_parent" diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml index 008981b0e..200388c7c 100644 --- a/res/values-de/strings.xml +++ b/res/values-de/strings.xml @@ -62,6 +62,7 @@      <string name="btn_doNotSave">Cancel</string>      <string name="btn_delete">Delete</string>      <string name="btn_noDate">None</string> +    <string name="btn_clearFilter">Clear Filter</string>      <!-- menu_lowerCase: capitalized words, no punctuation -->      <string name="menu_about">About</string> @@ -239,4 +240,14 @@      <string name="progress_decompressingData">decompressing data...</string>      <string name="progress_verifyingIntegrity">verifying integrity...</string> +    <!-- action strings --> +    <string name="action_encrypt">Encrypt</string> +    <string name="action_decrypt">Decrypt</string> +    <string name="action_importPublic">Import Public Keys</string> +    <string name="action_importSecret">Import Secret Keys</string> + +    <string name="hint_publicKeys">Search Public Keys</string> +    <string name="hint_secretKeys">Search Secret Keys</string> +    <string name="filterInfo">Filter: \"%s\"</string> +  </resources> diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml index 008981b0e..200388c7c 100644 --- a/res/values-ko/strings.xml +++ b/res/values-ko/strings.xml @@ -62,6 +62,7 @@      <string name="btn_doNotSave">Cancel</string>      <string name="btn_delete">Delete</string>      <string name="btn_noDate">None</string> +    <string name="btn_clearFilter">Clear Filter</string>      <!-- menu_lowerCase: capitalized words, no punctuation -->      <string name="menu_about">About</string> @@ -239,4 +240,14 @@      <string name="progress_decompressingData">decompressing data...</string>      <string name="progress_verifyingIntegrity">verifying integrity...</string> +    <!-- action strings --> +    <string name="action_encrypt">Encrypt</string> +    <string name="action_decrypt">Decrypt</string> +    <string name="action_importPublic">Import Public Keys</string> +    <string name="action_importSecret">Import Secret Keys</string> + +    <string name="hint_publicKeys">Search Public Keys</string> +    <string name="hint_secretKeys">Search Secret Keys</string> +    <string name="filterInfo">Filter: \"%s\"</string> +  </resources> diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml index 008981b0e..200388c7c 100644 --- a/res/values-ru/strings.xml +++ b/res/values-ru/strings.xml @@ -62,6 +62,7 @@      <string name="btn_doNotSave">Cancel</string>      <string name="btn_delete">Delete</string>      <string name="btn_noDate">None</string> +    <string name="btn_clearFilter">Clear Filter</string>      <!-- menu_lowerCase: capitalized words, no punctuation -->      <string name="menu_about">About</string> @@ -239,4 +240,14 @@      <string name="progress_decompressingData">decompressing data...</string>      <string name="progress_verifyingIntegrity">verifying integrity...</string> +    <!-- action strings --> +    <string name="action_encrypt">Encrypt</string> +    <string name="action_decrypt">Decrypt</string> +    <string name="action_importPublic">Import Public Keys</string> +    <string name="action_importSecret">Import Secret Keys</string> + +    <string name="hint_publicKeys">Search Public Keys</string> +    <string name="hint_secretKeys">Search Secret Keys</string> +    <string name="filterInfo">Filter: \"%s\"</string> +  </resources> diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml index 8bb198ad6..d838c6073 100644 --- a/res/values-sl/strings.xml +++ b/res/values-sl/strings.xml @@ -62,6 +62,7 @@      <string name="btn_doNotSave">Prekliči</string>      <string name="btn_delete">Izbriši</string>      <string name="btn_noDate">Brez</string> +    <string name="btn_clearFilter">Clear Filter</string>      <!-- menu_lowerCase: capitalized words, no punctuation -->      <string name="menu_about">O programu</string> @@ -239,4 +240,14 @@      <string name="progress_decompressingData">raztezam podatke...</string>      <string name="progress_verifyingIntegrity">overovljam integriteto...</string> +    <!-- action strings --> +    <string name="action_encrypt">Encrypt</string> +    <string name="action_decrypt">Decrypt</string> +    <string name="action_importPublic">Import Public Keys</string> +    <string name="action_importSecret">Import Secret Keys</string> + +    <string name="hint_publicKeys">Search Public Keys</string> +    <string name="hint_secretKeys">Search Secret Keys</string> +    <string name="filterInfo">Filter: \"%s\"</string> +  </resources> diff --git a/res/values/strings.xml b/res/values/strings.xml index e4f89baf5..1eac3292f 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -62,6 +62,7 @@      <string name="btn_doNotSave">Cancel</string>      <string name="btn_delete">Delete</string>      <string name="btn_noDate">None</string> +    <string name="btn_clearFilter">Clear Filter</string>      <!-- menu_lowerCase: capitalized words, no punctuation -->      <string name="menu_about">About</string> @@ -246,9 +247,12 @@      <!-- action strings -->      <string name="action_encrypt">Encrypt</string>      <string name="action_decrypt">Decrypt</string> -    <string name="action_import_public">Import Public Keys</string> -    <string name="action_import_secret">Import Secret Keys</string> +    <string name="action_importPublic">Import Public Keys</string> +    <string name="action_importSecret">Import Secret Keys</string> +    <string name="hint_publicKeys">Search Public Keys</string> +    <string name="hint_secretKeys">Search Secret Keys</string> +    <string name="filterInfo">Filter: \"%s\"</string>  </resources> diff --git a/res/xml/searchable_public_keys.xml b/res/xml/searchable_public_keys.xml new file mode 100644 index 000000000..f8963e997 --- /dev/null +++ b/res/xml/searchable_public_keys.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<searchable +    xmlns:android="http://schemas.android.com/apk/res/android" +    android:label="@string/app_name" +    android:hint="@string/hint_publicKeys"> +</searchable> diff --git a/res/xml/searchable_secret_keys.xml b/res/xml/searchable_secret_keys.xml new file mode 100644 index 000000000..43328df82 --- /dev/null +++ b/res/xml/searchable_secret_keys.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<searchable +    xmlns:android="http://schemas.android.com/apk/res/android" +    android:label="@string/app_name" +    android:hint="@string/hint_secretKeys"> +</searchable> diff --git a/src/org/thialfihar/android/apg/Apg.java b/src/org/thialfihar/android/apg/Apg.java index 3c35064a4..744bb736c 100644 --- a/src/org/thialfihar/android/apg/Apg.java +++ b/src/org/thialfihar/android/apg/Apg.java @@ -79,6 +79,7 @@ import org.bouncycastle2.openpgp.PGPSignatureList;  import org.bouncycastle2.openpgp.PGPSignatureSubpacketGenerator;
  import org.bouncycastle2.openpgp.PGPSignatureSubpacketVector;
  import org.bouncycastle2.openpgp.PGPUtil;
 +import org.thialfihar.android.apg.provider.DataProvider;
  import org.thialfihar.android.apg.provider.Database;
  import org.thialfihar.android.apg.provider.KeyRings;
  import org.thialfihar.android.apg.provider.Keys;
 @@ -92,6 +93,7 @@ import android.app.Activity;  import android.content.Context;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
 +import android.net.Uri;
  import android.os.Bundle;
  import android.os.Environment;
  import android.view.ViewGroup;
 @@ -130,6 +132,18 @@ public class Apg {      public static final String EXTRA_MAX = "max";
      public static final String EXTRA_ACCOUNT = "account";
 +    public static final String AUTHORITY = DataProvider.AUTHORITY;
 +
 +    public static final Uri CONTENT_URI_SECRET_KEY_RINGS =
 +            Uri.parse("content://" + AUTHORITY + "/key_rings/secret/");
 +    public static final Uri CONTENT_URI_SECRET_KEY_RING_BY_KEY_ID =
 +            Uri.parse("content://" + AUTHORITY + "/key_rings/secret/key_id/");
 +
 +    public static final Uri CONTENT_URI_PUBLIC_KEY_RINGS =
 +            Uri.parse("content://" + AUTHORITY + "/key_rings/public/");
 +    public static final Uri CONTENT_URI_PUBLIC_KEY_RING_BY_KEY_ID =
 +            Uri.parse("content://" + AUTHORITY + "/key_rings/public/key_id/");
 +
      public static String VERSION = "1.0.1";
      public static String FULL_VERSION = "APG v" + VERSION;
 diff --git a/src/org/thialfihar/android/apg/GeneralActivity.java b/src/org/thialfihar/android/apg/GeneralActivity.java index 1a80368aa..af919bb48 100644 --- a/src/org/thialfihar/android/apg/GeneralActivity.java +++ b/src/org/thialfihar/android/apg/GeneralActivity.java @@ -66,8 +66,8 @@ public class GeneralActivity extends BaseActivity {          Vector<Choice> choices = new Vector<Choice>();
          if (containsKeys) {
 -            choices.add(new Choice(Id.choice.action.import_public, getString(R.string.action_import_public)));
 -            choices.add(new Choice(Id.choice.action.import_secret, getString(R.string.action_import_secret)));
 +            choices.add(new Choice(Id.choice.action.import_public, getString(R.string.action_importPublic)));
 +            choices.add(new Choice(Id.choice.action.import_secret, getString(R.string.action_importSecret)));
          }
          if (isEncrypted) {
 diff --git a/src/org/thialfihar/android/apg/KeyListActivity.java b/src/org/thialfihar/android/apg/KeyListActivity.java index 7e86504b3..f09a74384 100644 --- a/src/org/thialfihar/android/apg/KeyListActivity.java +++ b/src/org/thialfihar/android/apg/KeyListActivity.java @@ -29,11 +29,13 @@ import org.thialfihar.android.apg.provider.UserIds;  import android.app.AlertDialog;
  import android.app.Dialog;
 +import android.app.SearchManager;
  import android.content.Context;
  import android.content.DialogInterface;
  import android.content.Intent;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
 +import android.database.sqlite.SQLiteQueryBuilder;
  import android.net.Uri;
  import android.os.Bundle;
  import android.os.Message;
 @@ -41,7 +43,9 @@ import android.view.LayoutInflater;  import android.view.MenuItem;
  import android.view.View;
  import android.view.ViewGroup;
 +import android.view.View.OnClickListener;
  import android.widget.BaseExpandableListAdapter;
 +import android.widget.Button;
  import android.widget.ExpandableListView;
  import android.widget.ImageView;
  import android.widget.TextView;
 @@ -51,6 +55,9 @@ import android.widget.ExpandableListView.ExpandableListContextMenuInfo;  public class KeyListActivity extends BaseActivity {
      protected ExpandableListView mList;
      protected KeyListAdapter mListAdapter;
 +    protected View mFilterLayout;
 +    protected Button mClearFilterButton;
 +    protected TextView mFilterInfo;
      protected int mSelectedItem = -1;
      protected int mTask = 0;
 @@ -66,9 +73,49 @@ public class KeyListActivity extends BaseActivity {          setContentView(R.layout.key_list);
          mList = (ExpandableListView) findViewById(R.id.list);
 -        mListAdapter = new KeyListAdapter(this);
 -        mList.setAdapter(mListAdapter);
          registerForContextMenu(mList);
 +
 +        mFilterLayout = (View) findViewById(R.id.layout_filter);
 +        mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo);
 +        mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear);
 +
 +        mClearFilterButton.setOnClickListener(new OnClickListener() {
 +            @Override
 +            public void onClick(View v) {
 +                handleIntent(new Intent());
 +            }
 +        });
 +
 +        handleIntent(getIntent());
 +    }
 +
 +    @Override
 +    protected void onNewIntent(Intent intent) {
 +        super.onNewIntent(intent);
 +        handleIntent(intent);
 +    }
 +
 +    private void handleIntent(Intent intent) {
 +        String searchString = null;
 +        if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
 +            searchString = intent.getStringExtra(SearchManager.QUERY);
 +            if (searchString != null && searchString.trim().length() == 0) {
 +                searchString = null;
 +            }
 +        }
 +
 +        if (searchString == null) {
 +            mFilterLayout.setVisibility(View.GONE);
 +        } else {
 +            mFilterLayout.setVisibility(View.VISIBLE);
 +            mFilterInfo.setText(getString(R.string.filterInfo, searchString));
 +        }
 +
 +        if (mListAdapter != null) {
 +            mListAdapter.cleanup();
 +        }
 +        mListAdapter = new KeyListAdapter(this, searchString);
 +        mList.setAdapter(mListAdapter);
      }
      @Override
 @@ -371,6 +418,7 @@ public class KeyListActivity extends BaseActivity {          private Vector<Vector<KeyChild>> mChildren;
          private SQLiteDatabase mDatabase;
          private Cursor mCursor;
 +        private String mSearchString;
          private class KeyChild {
              public static final int KEY = 0;
 @@ -401,11 +449,13 @@ public class KeyListActivity extends BaseActivity {              }
          }
 -        public KeyListAdapter(Context context) {
 +        public KeyListAdapter(Context context, String searchString) {
 +            mSearchString = searchString;
 +
              mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
              mDatabase = Apg.getDatabase().db();
 -            mCursor = mDatabase.query(
 -                    KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
 +            SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 +            qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
                                            "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
                                            Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
                                            Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
 @@ -413,7 +463,32 @@ public class KeyListActivity extends BaseActivity {                                            " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
                                            "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
                                            UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
 -                                          UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ",
 +                                          UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0')");
 +
 +            if (searchString != null && searchString.trim().length() > 0) {
 +                String[] chunks = searchString.trim().split(" +");
 +                qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " +
 +                                        UserIds.TABLE_NAME + " AS tmp WHERE " +
 +                                        "tmp." + UserIds.KEY_ID + " = " +
 +                                        Keys.TABLE_NAME + "." + Keys._ID);
 +                for (int i = 0; i < chunks.length; ++i) {
 +                    qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
 +                    qb.appendWhereEscapeString("%" + chunks[i] + "%");
 +                }
 +                qb.appendWhere(")");
 +            }
 +
 +            String query = qb.buildQuery(new String[] {
 +                        KeyRings.TABLE_NAME + "." + KeyRings._ID,           // 0
 +                        KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
 +                        UserIds.TABLE_NAME + "." + UserIds.USER_ID,         // 2
 +                    },
 +                    KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = ?",
 +                    new String[] { "" + (mKeyType == Id.type.public_key ?
 +                                             Id.database.type_public : Id.database.type_secret) },
 +                    null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC", null);
 +
 +            mCursor = qb.query(mDatabase,
                      new String[] {
                          KeyRings.TABLE_NAME + "." + KeyRings._ID,           // 0
                          KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
 @@ -424,10 +499,33 @@ public class KeyListActivity extends BaseActivity {                                               Id.database.type_public : Id.database.type_secret) },
                      null, null, UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC");
 +            // content provider way for reference, might have to go back to it sometime:
 +            /*Uri contentUri = null;
 +            if (mKeyType == Id.type.secret_key) {
 +                contentUri = Apg.CONTENT_URI_SECRET_KEY_RINGS;
 +            } else {
 +                contentUri = Apg.CONTENT_URI_PUBLIC_KEY_RINGS;
 +            }
 +            mCursor = getContentResolver().query(
 +                    contentUri,
 +                    new String[] {
 +                        DataProvider._ID,           // 0
 +                        DataProvider.MASTER_KEY_ID, // 1
 +                        DataProvider.USER_ID,       // 2
 +                    },
 +                    null, null, null);*/
 +
              startManagingCursor(mCursor);
              rebuild(false);
          }
 +        public void cleanup() {
 +            if (mCursor != null) {
 +                stopManagingCursor(mCursor);
 +                mCursor.close();
 +            }
 +        }
 +
          public void rebuild(boolean requery) {
              if (requery) {
                  mCursor.requery();
 diff --git a/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java b/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java index aeb6d59a3..d9d9864c4 100644 --- a/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SelectPublicKeyListActivity.java @@ -18,50 +18,32 @@ package org.thialfihar.android.apg;  import java.util.Vector; +import android.app.SearchManager;  import android.content.Intent;  import android.os.Bundle;  import android.view.View;  import android.view.View.OnClickListener;  import android.widget.Button;  import android.widget.ListView; +import android.widget.TextView;  public class SelectPublicKeyListActivity extends BaseActivity { -    protected Intent mIntent;      protected ListView mList; +    protected SelectPublicKeyListAdapter mListAdapter; +    protected View mFilterLayout; +    protected Button mClearFilterButton; +    protected TextView mFilterInfo;      @Override      public void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.select_public_key); -        // fill things -        mIntent = getIntent(); -        long selectedKeyIds[] = null; -        if (mIntent.getExtras() != null) { -            selectedKeyIds = mIntent.getExtras().getLongArray(Apg.EXTRA_SELECTION); -        } -          mList = (ListView) findViewById(R.id.list);          // needed in Android 1.5, where the XML attribute gets ignored          mList.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); -        SelectPublicKeyListAdapter adapter = new SelectPublicKeyListAdapter(this, mList); -        mList.setAdapter(adapter); - -        if (selectedKeyIds != null) { -            for (int i = 0; i < adapter.getCount(); ++i) { -                long keyId = adapter.getItemId(i); -                for (int j = 0; j < selectedKeyIds.length; ++j) { -                    if (keyId == selectedKeyIds[j]) { -                        mList.setItemChecked(i, true); -                        break; -                    } -                } -            } -        } -          Button okButton = (Button) findViewById(R.id.btn_ok); -          okButton.setOnClickListener(new OnClickListener() {              @Override              public void onClick(View v) { @@ -70,13 +52,85 @@ public class SelectPublicKeyListActivity extends BaseActivity {          });          Button cancelButton = (Button) findViewById(R.id.btn_cancel); -          cancelButton.setOnClickListener(new OnClickListener() {              @Override              public void onClick(View v) {                  cancelClicked();              }          }); + +        mFilterLayout = (View) findViewById(R.id.layout_filter); +        mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); +        mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + +        mClearFilterButton.setOnClickListener(new OnClickListener() { +            @Override +            public void onClick(View v) { +                handleIntent(new Intent()); +            } +        }); + +        handleIntent(getIntent()); +    } + +    @Override +    protected void onNewIntent(Intent intent) { +        super.onNewIntent(intent); +        handleIntent(intent); +    } + +    private void handleIntent(Intent intent) { +        String searchString = null; +        if (Intent.ACTION_SEARCH.equals(intent.getAction())) { +            searchString = intent.getStringExtra(SearchManager.QUERY); +            if (searchString != null && searchString.trim().length() == 0) { +                searchString = null; +            } +        } + +        long selectedKeyIds[] = null; +        if (getIntent().getExtras() != null) { +            selectedKeyIds = getIntent().getExtras().getLongArray(Apg.EXTRA_SELECTION); +        } + +        if (selectedKeyIds == null) { +            Vector<Long> vector = new Vector<Long>(); +            for (int i = 0; i < mList.getCount(); ++i) { +                if (mList.isItemChecked(i)) { +                    vector.add(mList.getItemIdAtPosition(i)); +                } +            } +            selectedKeyIds = new long[vector.size()]; +            for (int i = 0; i < vector.size(); ++i) { +                selectedKeyIds[i] = vector.get(i); +            } +        } + +        if (searchString == null) { +            mFilterLayout.setVisibility(View.GONE); +        } else { +            mFilterLayout.setVisibility(View.VISIBLE); +            mFilterInfo.setText(getString(R.string.filterInfo, searchString)); +        } + +        if (mListAdapter != null) { +            mListAdapter.cleanup(); +        } + +        mListAdapter = new SelectPublicKeyListAdapter(this, mList, searchString, selectedKeyIds); +        mList.setAdapter(mListAdapter); + +        if (selectedKeyIds != null) { +            for (int i = 0; i < mListAdapter.getCount(); ++i) { +                long keyId = mListAdapter.getItemId(i); +                for (int j = 0; j < selectedKeyIds.length; ++j) { +                    if (keyId == selectedKeyIds[j]) { +                        mList.setItemChecked(i, true); +                        break; +                    } +                } +            } +        }      }      private void cancelClicked() { diff --git a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java index ffc344ead..1b5a20d31 100644 --- a/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectPublicKeyListAdapter.java @@ -26,6 +26,7 @@ import android.app.Activity;  import android.content.Context;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
 +import android.database.sqlite.SQLiteQueryBuilder;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
 @@ -39,14 +40,20 @@ public class SelectPublicKeyListAdapter extends BaseAdapter {      protected ListView mParent;
      protected SQLiteDatabase mDatabase;
      protected Cursor mCursor;
 +    protected String mSearchString;
 +    protected Activity mActivity;
 -    public SelectPublicKeyListAdapter(Activity activity, ListView parent) {
 +    public SelectPublicKeyListAdapter(Activity activity, ListView parent,
 +                                      String searchString, long selectedKeyIds[]) {
 +        mSearchString = searchString;
 +
 +        mActivity = activity;
          mParent = parent;
          mDatabase =  Apg.getDatabase().db();
          mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          long now = new Date().getTime() / 1000;
 -        mCursor = mDatabase.query(
 -              KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
 +        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 +        qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
                                      "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
                                      Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
                                      Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
 @@ -54,7 +61,36 @@ public class SelectPublicKeyListAdapter extends BaseAdapter {                                      " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
                                      "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
                                      UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
 -                                    UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ",
 +                                    UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
 +
 +        if (searchString != null && searchString.trim().length() > 0) {
 +            String[] chunks = searchString.trim().split(" +");
 +            qb.appendWhere("(EXISTS (SELECT tmp." + UserIds._ID + " FROM " +
 +                                    UserIds.TABLE_NAME + " AS tmp WHERE " +
 +                                    "tmp." + UserIds.KEY_ID + " = " +
 +                                    Keys.TABLE_NAME + "." + Keys._ID);
 +            for (int i = 0; i < chunks.length; ++i) {
 +                qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
 +                qb.appendWhereEscapeString("%" + chunks[i] + "%");
 +            }
 +            qb.appendWhere("))");
 +
 +            if (selectedKeyIds != null && selectedKeyIds.length > 0) {
 +                qb.appendWhere(" OR ");
 +
 +                qb.appendWhere("(" + KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID +
 +                               " IN (");
 +                for (int i = 0; i < selectedKeyIds.length; ++i) {
 +                    if (i != 0) {
 +                        qb.appendWhere(", ");
 +                    }
 +                    qb.appendWhereEscapeString("" + selectedKeyIds[i]);
 +                }
 +                qb.appendWhere("))");
 +            }
 +        }
 +
 +        mCursor = qb.query(mDatabase,
                new String[] {
                    KeyRings.TABLE_NAME + "." + KeyRings._ID,           // 0
                    KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
 @@ -80,6 +116,13 @@ public class SelectPublicKeyListAdapter extends BaseAdapter {          activity.startManagingCursor(mCursor);
      }
 +    public void cleanup() {
 +        if (mCursor != null) {
 +            mActivity.stopManagingCursor(mCursor);
 +            mCursor.close();
 +        }
 +    }
 +
      @Override
      public boolean isEnabled(int position) {
          mCursor.moveToPosition(position);
 diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java b/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java index cd87a94b6..be00e18f4 100644 --- a/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListActivity.java @@ -16,16 +16,23 @@  package org.thialfihar.android.apg; +import android.app.SearchManager;  import android.content.Intent;  import android.os.Bundle;  import android.view.View; +import android.view.View.OnClickListener;  import android.widget.AdapterView; +import android.widget.Button;  import android.widget.ListView; +import android.widget.TextView;  import android.widget.AdapterView.OnItemClickListener;  public class SelectSecretKeyListActivity extends BaseActivity {      protected ListView mList;      protected SelectSecretKeyListAdapter mListAdapter; +    protected View mFilterLayout; +    protected Button mClearFilterButton; +    protected TextView mFilterInfo;      protected long mSelectedKeyId = 0; @@ -36,8 +43,6 @@ public class SelectSecretKeyListActivity extends BaseActivity {          setContentView(R.layout.select_secret_key);          mList = (ListView) findViewById(R.id.list); -        mListAdapter = new SelectSecretKeyListAdapter(this, mList); -        mList.setAdapter(mListAdapter);          mList.setOnItemClickListener(new OnItemClickListener() {              @Override @@ -48,5 +53,48 @@ public class SelectSecretKeyListActivity extends BaseActivity {                  finish();              }          }); + +        mFilterLayout = (View) findViewById(R.id.layout_filter); +        mFilterInfo = (TextView) mFilterLayout.findViewById(R.id.filterInfo); +        mClearFilterButton = (Button) mFilterLayout.findViewById(R.id.btn_clear); + +        mClearFilterButton.setOnClickListener(new OnClickListener() { +            @Override +            public void onClick(View v) { +                handleIntent(new Intent()); +            } +        }); + +        handleIntent(getIntent()); +    } + +    @Override +    protected void onNewIntent(Intent intent) { +        super.onNewIntent(intent); +        handleIntent(intent); +    } + +    private void handleIntent(Intent intent) { +        String searchString = null; +        if (Intent.ACTION_SEARCH.equals(intent.getAction())) { +            searchString = intent.getStringExtra(SearchManager.QUERY); +            if (searchString != null && searchString.trim().length() == 0) { +                searchString = null; +            } +        } + +        if (searchString == null) { +            mFilterLayout.setVisibility(View.GONE); +        } else { +            mFilterLayout.setVisibility(View.VISIBLE); +            mFilterInfo.setText(getString(R.string.filterInfo, searchString)); +        } + +        if (mListAdapter != null) { +            mListAdapter.cleanup(); +        } + +        mListAdapter = new SelectSecretKeyListAdapter(this, mList, searchString); +        mList.setAdapter(mListAdapter);      }  } diff --git a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java index 33cd15b40..d83c2aca6 100644 --- a/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java +++ b/src/org/thialfihar/android/apg/SelectSecretKeyListAdapter.java @@ -10,6 +10,7 @@ import android.app.Activity;  import android.content.Context;
  import android.database.Cursor;
  import android.database.sqlite.SQLiteDatabase;
 +import android.database.sqlite.SQLiteQueryBuilder;
  import android.view.LayoutInflater;
  import android.view.View;
  import android.view.ViewGroup;
 @@ -22,14 +23,19 @@ public class SelectSecretKeyListAdapter extends BaseAdapter {      protected ListView mParent;
      protected SQLiteDatabase mDatabase;
      protected Cursor mCursor;
 +    protected String mSearchString;
 +    protected Activity mActivity;
 -    public SelectSecretKeyListAdapter(Activity activity, ListView parent) {
 +    public SelectSecretKeyListAdapter(Activity activity, ListView parent, String searchString) {
 +        mSearchString = searchString;
 +
 +        mActivity = activity;
          mParent = parent;
          mDatabase =  Apg.getDatabase().db();
          mInflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
          long now = new Date().getTime() / 1000;
 -        mCursor = mDatabase.query(
 -              KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
 +        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
 +        qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +
                                      "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +
                                      Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " +
                                      Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" +
 @@ -37,7 +43,22 @@ public class SelectSecretKeyListAdapter extends BaseAdapter {                                      " INNER JOIN " + UserIds.TABLE_NAME + " ON " +
                                      "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " +
                                      UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +
 -                                    UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ",
 +                                    UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') ");
 +
 +        if (searchString != null && searchString.trim().length() > 0) {
 +            String[] chunks = searchString.trim().split(" +");
 +            qb.appendWhere("EXISTS (SELECT tmp." + UserIds._ID + " FROM " +
 +                                    UserIds.TABLE_NAME + " AS tmp WHERE " +
 +                                    "tmp." + UserIds.KEY_ID + " = " +
 +                                    Keys.TABLE_NAME + "." + Keys._ID);
 +            for (int i = 0; i < chunks.length; ++i) {
 +                qb.appendWhere(" AND tmp." + UserIds.USER_ID + " LIKE ");
 +                qb.appendWhereEscapeString("%" + chunks[i] + "%");
 +            }
 +            qb.appendWhere(")");
 +        }
 +
 +        mCursor = qb.query(mDatabase,
                new String[] {
                    KeyRings.TABLE_NAME + "." + KeyRings._ID,           // 0
                    KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID, // 1
 @@ -63,6 +84,13 @@ public class SelectSecretKeyListAdapter extends BaseAdapter {          activity.startManagingCursor(mCursor);
      }
 +    public void cleanup() {
 +        if (mCursor != null) {
 +            mActivity.stopManagingCursor(mCursor);
 +            mCursor.close();
 +        }
 +    }
 +
      @Override
      public boolean isEnabled(int position) {
          mCursor.moveToPosition(position);
 diff --git a/src/org/thialfihar/android/apg/provider/DataProvider.java b/src/org/thialfihar/android/apg/provider/DataProvider.java index 8a3fefdff..0667d82dd 100644 --- a/src/org/thialfihar/android/apg/provider/DataProvider.java +++ b/src/org/thialfihar/android/apg/provider/DataProvider.java @@ -72,6 +72,7 @@ public class DataProvider extends ContentProvider {      private static final String USER_ID_CONTENT_ITEM_TYPE =              "vnd.android.cursor.item/vnd.thialfihar.apg.user_id"; +    public static final String _ID = "_id";      public static final String MASTER_KEY_ID = "master_key_id";      public static final String KEY_ID = "key_id";      public static final String USER_ID = "user_id"; @@ -117,6 +118,7 @@ public class DataProvider extends ContentProvider {          // TODO: implement the others, then use them for the lists          SQLiteQueryBuilder qb = new SQLiteQueryBuilder();          HashMap<String, String> projectionMap = new HashMap<String, String>(); +          int match = mUriMatcher.match(uri);          int type;          switch (match) { @@ -148,28 +150,17 @@ public class DataProvider extends ContentProvider {          qb.appendWhere(KeyRings.TABLE_NAME + "." + KeyRings.TYPE + " = " + type);          switch (match) { -            case PUBLIC_KEY_RINGS: -            case SECRET_KEY_RINGS: { -                qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " + -                             "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " + -                             Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + -                             Keys.TABLE_NAME + "." + Keys.IS_MASTER_KEY + " = '1'" + -                             ") " + -                             " INNER JOIN " + UserIds.TABLE_NAME + " ON " + -                             "(" + Keys.TABLE_NAME + "." + Keys._ID + " = " + -                             UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " + -                             UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); - -                projectionMap.put(MASTER_KEY_ID, -                                  KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID); -                projectionMap.put(USER_ID, -                                  UserIds.TABLE_NAME + "." + UserIds.USER_ID); +            case PUBLIC_KEY_RING_ID: +            case SECRET_KEY_RING_ID: { +                qb.appendWhere(" AND " + +                               KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = "); +                qb.appendWhereEscapeString(uri.getPathSegments().get(2)); -                break; +                // break omitted intentionally              } -            case PUBLIC_KEY_RING_ID: -            case SECRET_KEY_RING_ID: { +            case PUBLIC_KEY_RINGS: +            case SECRET_KEY_RINGS: {                  qb.setTables(KeyRings.TABLE_NAME + " INNER JOIN " + Keys.TABLE_NAME + " ON " +                               "(" + KeyRings.TABLE_NAME + "." + KeyRings._ID + " = " +                               Keys.TABLE_NAME + "." + Keys.KEY_RING_ID + " AND " + @@ -180,14 +171,17 @@ public class DataProvider extends ContentProvider {                               UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +                               UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); +                projectionMap.put(_ID, +                                  KeyRings.TABLE_NAME + "." + KeyRings._ID);                  projectionMap.put(MASTER_KEY_ID,                                    KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);                  projectionMap.put(USER_ID,                                    UserIds.TABLE_NAME + "." + UserIds.USER_ID); -                qb.appendWhere(" AND " + -                               KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID + " = "); -                qb.appendWhereEscapeString(uri.getPathSegments().get(2)); +                if (TextUtils.isEmpty(sortOrder)) { +                    sortOrder = UserIds.TABLE_NAME + "." + UserIds.USER_ID + " ASC"; +                } +                  break;              } @@ -207,6 +201,8 @@ public class DataProvider extends ContentProvider {                               UserIds.TABLE_NAME + "." + UserIds.KEY_ID + " AND " +                               UserIds.TABLE_NAME + "." + UserIds.RANK + " = '0') "); +                projectionMap.put(_ID, +                                  KeyRings.TABLE_NAME + "." + KeyRings._ID);                  projectionMap.put(MASTER_KEY_ID,                                    KeyRings.TABLE_NAME + "." + KeyRings.MASTER_KEY_ID);                  projectionMap.put(USER_ID, | 
