diff options
Diffstat (limited to 'OpenPGP-Keychain/src')
4 files changed, 274 insertions, 57 deletions
| diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java index 095887433..e582ec559 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/provider/KeychainProvider.java @@ -87,7 +87,8 @@ public class KeychainProvider extends ContentProvider {      private static final int CERTS = 401;      private static final int CERTS_BY_KEY_ID = 402;      private static final int CERTS_BY_ROW_ID = 403; -    private static final int CERTS_BY_CERTIFIER_ID = 404; +    private static final int CERTS_BY_KEY_ROW_ID = 404; +    private static final int CERTS_BY_CERTIFIER_ID = 405;      // private static final int DATA_STREAM = 401; @@ -254,6 +255,8 @@ public class KeychainProvider extends ContentProvider {          matcher.addURI(authority, KeychainContract.BASE_CERTS, CERTS);          matcher.addURI(authority, KeychainContract.BASE_CERTS + "/#", CERTS_BY_ROW_ID);          matcher.addURI(authority, KeychainContract.BASE_CERTS + "/" +                + KeychainContract.PATH_BY_KEY_ROW_ID + "/#", CERTS_BY_KEY_ROW_ID); +        matcher.addURI(authority, KeychainContract.BASE_CERTS + "/"                  + KeychainContract.PATH_BY_KEY_ID + "/#", CERTS_BY_KEY_ID);          matcher.addURI(authority, KeychainContract.BASE_CERTS + "/"                  + KeychainContract.PATH_BY_CERTIFIER_ID + "/#", CERTS_BY_CERTIFIER_ID); @@ -713,6 +716,44 @@ public class KeychainProvider extends ContentProvider {                  break; +            case CERTS_BY_KEY_ROW_ID: +                qb.setTables(Tables.CERTS +                    + " JOIN " + Tables.USER_IDS + " ON (" +                            + Tables.CERTS + "." + Certs.KEY_RING_ROW_ID + " = " +                            + Tables.USER_IDS + "." + UserIds.KEY_RING_ROW_ID +                        + " AND " +                            + Tables.CERTS + "." + Certs.RANK + " = " +                            + Tables.USER_IDS + "." + UserIds.RANK +                    // noooooooot sure about this~ database design +                    + ") LEFT JOIN " + Tables.KEYS + " ON (" +                        + Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER + " = " +                        + Tables.KEYS + "." + Keys.KEY_ID +                    + ") LEFT JOIN " + Tables.USER_IDS + " AS signer ON (" +                            + Tables.KEYS + "." + Keys.KEY_RING_ROW_ID + " = " +                            + "signer." + UserIds.KEY_RING_ROW_ID +                        + " AND " +                            + Tables.KEYS + "." + Keys.RANK + " = " +                            + "signer." + UserIds.RANK +                    + ")"); + +                // groupBy = Tables.USER_IDS + "." + UserIds.RANK; + +                HashMap<String, String> pmap2 = new HashMap<String, String>(); +                pmap2.put(Certs._ID, Tables.CERTS + "." + Certs._ID); +                pmap2.put(Certs.RANK, Tables.CERTS + "." + Certs.RANK); +                pmap2.put(Certs.KEY_ID_CERTIFIER, Tables.CERTS + "." + Certs.KEY_ID_CERTIFIER); +                pmap2.put(Certs.VERIFIED, Tables.CERTS + "." + Certs.VERIFIED); +                // verified key data +                pmap2.put(UserIds.USER_ID, Tables.USER_IDS + "." + UserIds.USER_ID); +                // verifying key data +                pmap2.put("signer_uid", "signer." + UserIds.USER_ID + " AS signer_uid"); +                qb.setProjectionMap(pmap2); + +                qb.appendWhere(Tables.CERTS + "." + Certs.KEY_RING_ROW_ID + " = "); +                qb.appendWhereEscapeString(uri.getPathSegments().get(2)); + +                break; +              case API_APPS:                  qb.setTables(Tables.API_APPS); @@ -825,7 +866,7 @@ public class KeychainProvider extends ContentProvider {                      break;                  case CERTS_BY_ROW_ID:                      rowId = db.insertOrThrow(Tables.CERTS, null, values); -                    // kinda useless :S +                    // kinda useless.. should this be buildCertsByKeyRowIdUri?                      rowUri = Certs.buildCertsUri(Long.toString(rowId));                      break; diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java index 8f177dc6b..055203c0f 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyActivity.java @@ -84,13 +84,11 @@ public class ViewKeyActivity extends ActionBarActivity {              selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);          } -        { -            // normalize mDataUri to a "by row id" query, to ensure it works with any -            // given valid /public/ query -            long rowId = ProviderHelper.getRowId(this, getIntent().getData()); -            // TODO: handle (rowId == 0) with something else than a crash -            mDataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)) ; -        } +        // normalize mDataUri to a "by row id" query, to ensure it works with any +        // given valid /public/ query +        long rowId = ProviderHelper.getRowId(this, getIntent().getData()); +        // TODO: handle (rowId == 0) with something else than a crash +        mDataUri = KeychainContract.KeyRings.buildPublicKeyRingsUri(Long.toString(rowId)) ;          Bundle mainBundle = new Bundle();          mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri); @@ -98,7 +96,7 @@ public class ViewKeyActivity extends ActionBarActivity {                  ViewKeyMainFragment.class, mainBundle, (selectedTab == 0 ? true : false));          Bundle certBundle = new Bundle(); -        certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri); +        certBundle.putLong(ViewKeyCertsFragment.ARG_KEYRING_ROW_ID, rowId);          mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)),                  ViewKeyCertsFragment.class, certBundle, (selectedTab == 1 ? true : false));      } diff --git a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java index 36d3e6ace..abed097c1 100644 --- a/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java +++ b/OpenPGP-Keychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewKeyCertsFragment.java @@ -17,26 +17,66 @@  package org.sufficientlysecure.keychain.ui; +import android.annotation.SuppressLint; +import android.content.Context;  import android.content.Intent; +import android.database.Cursor; +import android.graphics.Color;  import android.net.Uri;  import android.os.Bundle;  import android.support.v4.app.Fragment; +import android.support.v4.app.LoaderManager; +import android.support.v4.content.CursorLoader; +import android.support.v4.content.Loader; +import android.support.v4.widget.CursorAdapter;  import android.view.LayoutInflater;  import android.view.View;  import android.view.ViewGroup; +import android.widget.Button; +import android.widget.TextView;  import com.beardedhen.androidbootstrap.BootstrapButton;  import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.pgp.PgpKeyHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.KeychainDatabase;  import org.sufficientlysecure.keychain.util.Log; +import java.nio.ByteBuffer; +import java.util.HashMap; -public class ViewKeyCertsFragment extends Fragment { +import se.emilsjolander.stickylistheaders.ApiLevelTooLowException; +import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter; +import se.emilsjolander.stickylistheaders.StickyListHeadersListView; -    public static final String ARG_DATA_URI = "uri"; -    private BootstrapButton mActionCertify; +public class ViewKeyCertsFragment extends Fragment +        implements LoaderManager.LoaderCallbacks<Cursor> { + +    // These are the rows that we will retrieve. +    static final String[] PROJECTION = new String[]{ +        KeychainContract.Certs._ID, +        KeychainContract.Certs.VERIFIED, +        KeychainContract.Certs.RANK, +        KeychainContract.Certs.KEY_ID_CERTIFIER, +        KeychainContract.UserIds.USER_ID, +        "signer_uid" +    }; + +    // sort by our user id, +    static final String SORT_ORDER = +              KeychainDatabase.Tables.USER_IDS + "." + KeychainContract.UserIds.USER_ID + " ASC, " +            + KeychainDatabase.Tables.CERTS + "." + KeychainContract.Certs.VERIFIED + " DESC, " +            + "signer_uid ASC"; + +    public static final String ARG_KEYRING_ROW_ID = "row_id"; + +    private StickyListHeadersListView mStickyList; + +    private CertListAdapter mAdapter;      private Uri mDataUri; @@ -44,8 +84,6 @@ public class ViewKeyCertsFragment extends Fragment {      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {          View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false); -        mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify); -          return view;      } @@ -53,40 +91,197 @@ public class ViewKeyCertsFragment extends Fragment {      public void onActivityCreated(Bundle savedInstanceState) {          super.onActivityCreated(savedInstanceState); -        Uri dataUri = getArguments().getParcelable(ARG_DATA_URI); -        if (dataUri == null) { +        mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list); + +        if (!getArguments().containsKey(ARG_KEYRING_ROW_ID)) {              Log.e(Constants.TAG, "Data missing. Should be Uri of key!");              getActivity().finish();              return;          } -        loadData(dataUri); +        long rowId = getArguments().getLong(ARG_KEYRING_ROW_ID); +        mDataUri = KeychainContract.Certs.buildCertsByKeyRowIdUri(Long.toString(rowId)); + +        mStickyList.setAreHeadersSticky(true); +        mStickyList.setDrawingListUnderStickyHeader(false); +        mStickyList.setFastScrollEnabled(true); + +        try { +            mStickyList.setFastScrollAlwaysVisible(true); +        } catch (ApiLevelTooLowException e) { +        } + +        // TODO this view is made visible if no data is available +        // mStickyList.setEmptyView(getActivity().findViewById(R.id.empty)); + + +        // Create an empty adapter we will use to display the loaded data. +        mAdapter = new CertListAdapter(getActivity(), null); +        mStickyList.setAdapter(mAdapter); + +        // Prepare the loader. Either re-connect with an existing one, +        // or start a new one. +        getLoaderManager().initLoader(0, null, this); +      } -    private void loadData(Uri dataUri) { -        if (dataUri.equals(mDataUri)) { -            Log.d(Constants.TAG, "Same URI, no need to load the data again!"); -            return; +    @Override +    public Loader<Cursor> onCreateLoader(int id, Bundle args) { +        // Now create and return a CursorLoader that will take care of +        // creating a Cursor for the data being displayed. +        return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, SORT_ORDER); +    } + +    @Override +    public void onLoadFinished(Loader<Cursor> loader, Cursor data) { +        // Swap the new cursor in. (The framework will take care of closing the +        // old cursor once we return.) +        mAdapter.swapCursor(data); + +        mStickyList.setAdapter(mAdapter); +    } + +    @Override +    public void onLoaderReset(Loader<Cursor> loader) { +        // This is called when the last Cursor provided to onLoadFinished() +        // above is about to be closed. We need to make sure we are no +        // longer using it. +        mAdapter.swapCursor(null); +    } + +    /** +     * Implements StickyListHeadersAdapter from library +     */ +    private class CertListAdapter extends CursorAdapter implements StickyListHeadersAdapter { +        private LayoutInflater mInflater; +        private int mIndexUserId, mIndexRank; +        private int mIndexSignerKeyId, mIndexSignerUserId; +        private int mIndexVerified; + +        public CertListAdapter(Context context, Cursor c) { +            super(context, c, 0); + +            mInflater = LayoutInflater.from(context); +            initIndex(c);          } -        mDataUri = dataUri; +        @Override +        public Cursor swapCursor(Cursor newCursor) { +            initIndex(newCursor); -        Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); +            return super.swapCursor(newCursor); +        } -        mActionCertify.setOnClickListener(new View.OnClickListener() { +        /** +         * Get column indexes for performance reasons just once in constructor and swapCursor. For a +         * performance comparison see http://stackoverflow.com/a/17999582 +         * +         * @param cursor +         */ +        private void initIndex(Cursor cursor) { +            if (cursor != null) { -            @Override -            public void onClick(View v) { -                certifyKey(mDataUri); +                mIndexUserId = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.USER_ID); +                mIndexRank = cursor.getColumnIndexOrThrow(KeychainContract.UserIds.RANK); +                mIndexVerified = cursor.getColumnIndexOrThrow(KeychainContract.Certs.VERIFIED); +                mIndexSignerKeyId = cursor.getColumnIndexOrThrow(KeychainContract.Certs.KEY_ID_CERTIFIER); +                mIndexSignerUserId = cursor.getColumnIndexOrThrow("signer_uid");              } -        }); +        } -    } +        /** +         * Bind cursor data to the item list view +         * <p/> +         * NOTE: CursorAdapter already implements the ViewHolder pattern in its getView() method. +         * Thus no ViewHolder is required here. +         */ +        @Override +        public void bindView(View view, Context context, Cursor cursor) { + +            // set name and stuff, common to both key types +            TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId); +            TextView wSignerUserId = (TextView) view.findViewById(R.id.signerUserId); +            TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus); + +            String signerKeyId = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexSignerKeyId)); +            String signerUserId = cursor.getString(mIndexSignerUserId); +            String signStatus = cursor.getInt(mIndexVerified) > 0 ? "ok" : "unknown"; + +            wSignerUserId.setText(signerUserId); +            wSignerKeyId.setText(signerKeyId); +            wSignStatus.setText(signStatus); + +        } + +        @Override +        public View newView(Context context, Cursor cursor, ViewGroup parent) { +            return mInflater.inflate(R.layout.view_key_certs_item, parent, false); +        } + +        /** +         * Creates a new header view and binds the section headers to it. It uses the ViewHolder +         * pattern. Most functionality is similar to getView() from Android's CursorAdapter. +         * <p/> +         * NOTE: The variables mDataValid and mCursor are available due to the super class +         * CursorAdapter. +         */ +        @Override +        public View getHeaderView(int position, View convertView, ViewGroup parent) { +            HeaderViewHolder holder; +            if (convertView == null) { +                holder = new HeaderViewHolder(); +                convertView = mInflater.inflate(R.layout.view_key_certs_header, parent, false); +                holder.text = (TextView) convertView.findViewById(R.id.stickylist_header_text); +                holder.count = (TextView) convertView.findViewById(R.id.certs_num); +                convertView.setTag(holder); +            } else { +                holder = (HeaderViewHolder) convertView.getTag(); +            } + +            if (!mDataValid) { +                // no data available at this point +                Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); +                return convertView; +            } + +            if (!mCursor.moveToPosition(position)) { +                throw new IllegalStateException("couldn't move cursor to position " + position); +            } + +            // set header text as first char in user id +            String userId = mCursor.getString(mIndexUserId); +            holder.text.setText(userId); +            holder.count.setVisibility(View.GONE); +            return convertView; +        } + +        /** +         * Header IDs should be static, position=1 should always return the same Id that is. +         */ +        @Override +        public long getHeaderId(int position) { +            if (!mDataValid) { +                // no data available at this point +                Log.d(Constants.TAG, "getHeaderView: No data available at this point!"); +                return -1; +            } + +            if (!mCursor.moveToPosition(position)) { +                throw new IllegalStateException("couldn't move cursor to position " + position); +            } + +            // otherwise, return the first character of the name as ID +            return mCursor.getInt(mIndexRank); + +            // sort by the first four characters (should be enough I guess?) +            // return ByteBuffer.wrap(userId.getBytes()).asLongBuffer().get(0); +        } + +        class HeaderViewHolder { +            TextView text; +            TextView count; +        } -    private void certifyKey(Uri dataUri) { -        Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class); -        signIntent.setData(dataUri); -        startActivity(signIntent);      }  }
\ No newline at end of file diff --git a/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml index 299471c66..faa26b525 100644 --- a/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml +++ b/OpenPGP-Keychain/src/main/res/layout/view_key_certs_fragment.xml @@ -1,7 +1,8 @@  <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"      xmlns:bootstrapbutton="http://schemas.android.com/apk/res-auto"      android:layout_width="match_parent" -    android:layout_height="match_parent"> +    android:layout_height="match_parent" +    android:fillViewport="true">      <LinearLayout          android:layout_width="match_parent" @@ -10,29 +11,11 @@          android:paddingLeft="16dp"          android:paddingRight="16dp"> -        <TextView -            android:layout_width="wrap_content" -            android:layout_height="wrap_content" -            android:layout_marginBottom="4dp" -            android:layout_marginTop="14dp" -            android:text="Display of existing certifications is a planned feature for a later release of OpenPGP Keychain. Stay tuned for updates!" /> - -        <TextView -            style="@style/SectionHeader" -            android:layout_width="wrap_content" -            android:layout_height="wrap_content" -            android:layout_marginBottom="4dp" -            android:layout_marginTop="14dp" -            android:text="@string/section_actions" /> - -        <com.beardedhen.androidbootstrap.BootstrapButton -            android:id="@+id/action_certify" +        <view              android:layout_width="match_parent" -            android:layout_height="60dp" -            android:padding="4dp" -            android:text="@string/key_view_action_certify" -            bootstrapbutton:bb_icon_left="fa-pencil" -            bootstrapbutton:bb_type="info" /> +            android:layout_height="match_parent" +            class="se.emilsjolander.stickylistheaders.StickyListHeadersListView" +            android:id="@+id/list" />      </LinearLayout>  </ScrollView>
\ No newline at end of file | 
