diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java index bd61897c1..03e774f34 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/ViewKeyFragment.java @@ -40,6 +40,8 @@ import org.sufficientlysecure.keychain.ui.keyview.presenter.ViewKeyMvpView; import org.sufficientlysecure.keychain.ui.keyview.view.IdentitiesCardView; import org.sufficientlysecure.keychain.ui.keyview.view.KeyHealthView; import org.sufficientlysecure.keychain.ui.keyview.view.SystemContactCardView; +import org.sufficientlysecure.keychain.ui.keyview.view.KeyserverStatusCardView; +import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter; public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { @@ -51,6 +53,7 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { private static final int LOADER_IDENTITIES = 1; private static final int LOADER_ID_LINKED_CONTACT = 2; private static final int LOADER_ID_SUBKEY_STATUS = 3; + private static final int LOADER_ID_KEYSERVER_STATUS = 4; private IdentitiesCardView mIdentitiesCardView; private IdentitiesPresenter mIdentitiesPresenter; @@ -62,6 +65,9 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { KeyHealthPresenter mKeyHealthPresenter; + KeyserverStatusCardView mKeyserverStatusCard; + KeyserverStatusPresenter mKeyserverStatusPresenter; + /** * Creates new instance of this fragment */ @@ -85,6 +91,7 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { mSystemContactCard = (SystemContactCardView) view.findViewById(R.id.linked_system_contact_card); mKeyStatusHealth = (KeyHealthView) view.findViewById(R.id.key_status_health); + mKeyserverStatusCard = (KeyserverStatusCardView) view.findViewById(R.id.keyserver_status_card); return root; } @@ -107,6 +114,10 @@ public class ViewKeyFragment extends LoaderFragment implements ViewKeyMvpView { mKeyHealthPresenter = new KeyHealthPresenter( getContext(), mKeyStatusHealth, LOADER_ID_SUBKEY_STATUS, masterKeyId, mIsSecret); mKeyHealthPresenter.startLoader(getLoaderManager()); + + mKeyserverStatusPresenter = new KeyserverStatusPresenter( + getContext(), mKeyserverStatusCard, LOADER_ID_KEYSERVER_STATUS, masterKeyId, mIsSecret); + mKeyserverStatusPresenter.startLoader(getLoaderManager()); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java new file mode 100644 index 000000000..8cd59f934 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/loader/KeyserverStatusLoader.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.keyview.loader; + + +import java.util.Date; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.support.v4.content.AsyncTaskLoader; +import android.util.Log; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.provider.KeychainContract.UpdatedKeys; +import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusLoader.KeyserverStatus; + + +public class KeyserverStatusLoader extends AsyncTaskLoader { + public static final String[] PROJECTION = new String[] { + UpdatedKeys.LAST_UPDATED, + UpdatedKeys.SEEN_ON_KEYSERVERS + }; + private static final int INDEX_LAST_UPDATED = 0; + private static final int INDEX_SEEN_ON_KEYSERVERS = 1; + + + private final ContentResolver contentResolver; + private final long masterKeyId; + + private KeyserverStatus cachedResult; + + + public KeyserverStatusLoader(Context context, ContentResolver contentResolver, long masterKeyId) { + super(context); + + this.contentResolver = contentResolver; + this.masterKeyId = masterKeyId; + } + + @Override + public KeyserverStatus loadInBackground() { + Cursor cursor = contentResolver.query(UpdatedKeys.CONTENT_URI, PROJECTION, + UpdatedKeys.MASTER_KEY_ID + " = ?", new String[] { Long.toString(masterKeyId) }, null); + if (cursor == null) { + Log.e(Constants.TAG, "Error loading key items!"); + return null; + } + + try { + if (cursor.moveToFirst()) { + boolean isPublished = cursor.getInt(INDEX_SEEN_ON_KEYSERVERS) != 0; + Date lastUpdated = cursor.isNull(INDEX_LAST_UPDATED) ? + null : new Date(cursor.getLong(INDEX_LAST_UPDATED) * 1000); + + return new KeyserverStatus(masterKeyId, isPublished, lastUpdated); + } + + return new KeyserverStatus(masterKeyId); + } finally { + cursor.close(); + } + } + + @Override + public void deliverResult(KeyserverStatus keySubkeyStatus) { + cachedResult = keySubkeyStatus; + + if (isStarted()) { + super.deliverResult(keySubkeyStatus); + } + } + + @Override + protected void onStartLoading() { + if (cachedResult != null) { + deliverResult(cachedResult); + } + + if (takeContentChanged() || cachedResult == null) { + forceLoad(); + } + } + + public static class KeyserverStatus { + private final long masterKeyId; + private final boolean isPublished; + private final Date lastUpdated; + + KeyserverStatus(long masterKeyId, boolean isPublished, Date lastUpdated) { + this.masterKeyId = masterKeyId; + this.isPublished = isPublished; + this.lastUpdated = lastUpdated; + } + + KeyserverStatus(long masterKeyId) { + this.masterKeyId = masterKeyId; + this.isPublished = false; + this.lastUpdated = null; + } + + long getMasterKeyId() { + return masterKeyId; + } + + public boolean hasBeenUpdated() { + return lastUpdated != null; + } + + public boolean isPublished() { + if (lastUpdated == null) { + throw new IllegalStateException("Cannot get publication state if key has never been updated!"); + } + return isPublished; + } + + public Date getLastUpdated() { + return lastUpdated; + } + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java new file mode 100644 index 000000000..c3c248a22 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/presenter/KeyserverStatusPresenter.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.keyview.presenter; + + +import java.util.Date; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.LoaderManager; +import android.support.v4.app.LoaderManager.LoaderCallbacks; +import android.support.v4.content.Loader; + +import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusLoader; +import org.sufficientlysecure.keychain.ui.keyview.loader.KeyserverStatusLoader.KeyserverStatus; + + +public class KeyserverStatusPresenter implements LoaderCallbacks { + private final Context context; + private final KeyserverStatusMvpView view; + private final int loaderId; + + private final long masterKeyId; + private final boolean isSecret; + + + public KeyserverStatusPresenter(Context context, KeyserverStatusMvpView view, int loaderId, long masterKeyId, + boolean isSecret) { + this.context = context; + this.view = view; + this.loaderId = loaderId; + + this.masterKeyId = masterKeyId; + this.isSecret = isSecret; + + view.setOnKeyserverClickListener(new KeyserverStatusClickListener() { + @Override + public void onKeyRefreshClick() { + KeyserverStatusPresenter.this.onKeyRefreshClick(); + } + }); + } + + public void startLoader(LoaderManager loaderManager) { + loaderManager.restartLoader(loaderId, null, this); + } + + @Override + public Loader onCreateLoader(int id, Bundle args) { + return new KeyserverStatusLoader(context, context.getContentResolver(), masterKeyId); + } + + @Override + public void onLoadFinished(Loader loader, KeyserverStatus keyserverStatus) { + if (keyserverStatus.hasBeenUpdated()) { + if (keyserverStatus.isPublished()) { + view.setDisplayStatusPublished(); + } else { + view.setDisplayStatusNotPublished(); + } + view.setLastUpdated(keyserverStatus.getLastUpdated()); + } else { + view.setDisplayStatusUnknown(); + } + } + + @Override + public void onLoaderReset(Loader loader) { + + } + + private void onKeyRefreshClick() { + + } + + public interface KeyserverStatusMvpView { + void setOnKeyserverClickListener(KeyserverStatusClickListener keyserverStatusClickListener); + + void setDisplayStatusPublished(); + void setDisplayStatusNotPublished(); + void setLastUpdated(Date lastUpdated); + void setDisplayStatusUnknown(); + } + + public interface KeyserverStatusClickListener { + void onKeyRefreshClick(); + } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusCardView.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusCardView.java new file mode 100644 index 000000000..2dcb2155b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/keyview/view/KeyserverStatusCardView.java @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2017 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.ui.keyview.view; + + +import java.util.Date; + +import android.content.Context; +import android.support.annotation.ColorRes; +import android.support.annotation.DrawableRes; +import android.support.annotation.StringRes; +import android.support.v4.content.ContextCompat; +import android.support.v7.widget.CardView; +import android.text.format.DateFormat; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter.KeyserverStatusClickListener; +import org.sufficientlysecure.keychain.ui.keyview.presenter.KeyserverStatusPresenter.KeyserverStatusMvpView; + + +public class KeyserverStatusCardView extends CardView implements KeyserverStatusMvpView, OnClickListener { + private final View vLayout; + private final TextView vTitle; + private final TextView vSubtitle; + private final ImageView vIcon; + + private KeyserverStatusClickListener keyHealthClickListener; + + public KeyserverStatusCardView(Context context, AttributeSet attrs) { + super(context, attrs); + + View view = LayoutInflater.from(context).inflate(R.layout.key_keyserver_card_content, this, true); + + vLayout = view.findViewById(R.id.key_health_layout); + + vTitle = (TextView) view.findViewById(R.id.keyserver_status_title); + vSubtitle = (TextView) view.findViewById(R.id.keyserver_status_subtitle); + vIcon = (ImageView) view.findViewById(R.id.keyserver_icon); +// vExpander = (ImageView) view.findViewById(R.id.key_health_expander); + } + + private enum KeyserverDisplayStatus { + PUBLISHED (R.string.keyserver_title_published, R.drawable.ic_cloud_black_24dp, R.color.md_grey_900), + NOT_PUBLISHED (R.string.keyserver_title_not_published, R.drawable.ic_cloud_off_24dp, R.color.md_grey_900), + UNKNOWN (R.string.keyserver_title_unknown, R.drawable.ic_cloud_unknown_24dp, R.color.md_grey_900); + + @StringRes + private final int title; + @DrawableRes + private final int icon; + @ColorRes + private final int iconColor; + + KeyserverDisplayStatus(@StringRes int title, @DrawableRes int icon, @ColorRes int iconColor) { + this.title = title; + this.icon = icon; + this.iconColor = iconColor; + } + } + + @Override + public void onClick(View view) { + if (keyHealthClickListener != null) { + keyHealthClickListener.onKeyRefreshClick(); + } + } + + @Override + public void setOnKeyserverClickListener(KeyserverStatusClickListener keyHealthClickListener) { + this.keyHealthClickListener = keyHealthClickListener; + vLayout.setClickable(keyHealthClickListener != null); + } + + @Override + public void setDisplayStatusPublished() { + setDisplayStatus(KeyserverDisplayStatus.PUBLISHED); + } + + @Override + public void setDisplayStatusNotPublished() { + setDisplayStatus(KeyserverDisplayStatus.NOT_PUBLISHED); + } + + @Override + public void setDisplayStatusUnknown() { + setDisplayStatus(KeyserverDisplayStatus.UNKNOWN); + vSubtitle.setVisibility(View.GONE); + } + + @Override + public void setLastUpdated(Date lastUpdated) { + String lastUpdatedText = DateFormat.getMediumDateFormat(getContext()).format(lastUpdated); + vSubtitle.setText(getResources().getString(R.string.keyserver_last_updated, lastUpdatedText)); + } + + private void setDisplayStatus(KeyserverDisplayStatus displayStatus) { + vTitle.setText(displayStatus.title); + vIcon.setImageResource(displayStatus.icon); + vIcon.setColorFilter(ContextCompat.getColor(getContext(), displayStatus.iconColor)); + + setVisibility(View.VISIBLE); + } +} diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_cloud_off_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_cloud_off_24dp.png new file mode 100644 index 000000000..fabb499c7 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_cloud_off_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-hdpi/ic_cloud_unknown_24dp.png b/OpenKeychain/src/main/res/drawable-hdpi/ic_cloud_unknown_24dp.png new file mode 100644 index 000000000..a4c7be5df Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-hdpi/ic_cloud_unknown_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_cloud_off_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_cloud_off_24dp.png new file mode 100644 index 000000000..62dc80873 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_cloud_off_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-mdpi/ic_cloud_unknown_24dp.png b/OpenKeychain/src/main/res/drawable-mdpi/ic_cloud_unknown_24dp.png new file mode 100644 index 000000000..2e60f4c83 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-mdpi/ic_cloud_unknown_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_cloud_off_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_cloud_off_24dp.png new file mode 100644 index 000000000..fa6dd8da2 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_cloud_off_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xhdpi/ic_cloud_unknown_24dp.png b/OpenKeychain/src/main/res/drawable-xhdpi/ic_cloud_unknown_24dp.png new file mode 100644 index 000000000..19f43d423 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xhdpi/ic_cloud_unknown_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_cloud_off_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_cloud_off_24dp.png new file mode 100644 index 000000000..79d9717f8 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_cloud_off_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxhdpi/ic_cloud_unknown_24dp.png b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_cloud_unknown_24dp.png new file mode 100644 index 000000000..49a9935d4 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxhdpi/ic_cloud_unknown_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_cloud_off_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_cloud_off_24dp.png new file mode 100644 index 000000000..93003e9a0 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_cloud_off_24dp.png differ diff --git a/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_cloud_unknown_24dp.png b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_cloud_unknown_24dp.png new file mode 100644 index 000000000..f9196f6f2 Binary files /dev/null and b/OpenKeychain/src/main/res/drawable-xxxhdpi/ic_cloud_unknown_24dp.png differ diff --git a/OpenKeychain/src/main/res/layout/key_keyserver_card_content.xml b/OpenKeychain/src/main/res/layout/key_keyserver_card_content.xml new file mode 100644 index 000000000..bf0636d50 --- /dev/null +++ b/OpenKeychain/src/main/res/layout/key_keyserver_card_content.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 31a81e651..8b075b06e 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -1842,6 +1842,11 @@ "Healthy (Partially Stripped)" "Click for details" + "Published" + "Not Published" + "Unknown" + "Last updated: %s" + "This key uses the %1$s algorithm with a strength of %2$s bits. A secure key should have a strength of 2048 bits." "This key can\'t be upgraded. For secure communication, the owner must generate a new key." diff --git a/graphics/drawables/ic_cloud_off.svg b/graphics/drawables/ic_cloud_off.svg new file mode 100644 index 000000000..b2373ff16 --- /dev/null +++ b/graphics/drawables/ic_cloud_off.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/graphics/drawables/ic_cloud_unknown.svg b/graphics/drawables/ic_cloud_unknown.svg new file mode 100644 index 000000000..8fda8d19f --- /dev/null +++ b/graphics/drawables/ic_cloud_unknown.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/graphics/update-drawables.sh b/graphics/update-drawables.sh index 4407315b7..f4c998e4b 100755 --- a/graphics/update-drawables.sh +++ b/graphics/update-drawables.sh @@ -22,7 +22,7 @@ SRC_DIR=./drawables/ #inkscape -w 512 -h 512 -e "$PLAY_DIR/$NAME.png" $NAME.svg -for NAME in "broken_heart" "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link" +for NAME in "ic_cloud_unknown" "ic_cloud_off" "broken_heart" "ic_cloud_search" "ic_action_encrypt_file" "ic_action_encrypt_text" "ic_action_verified_cutout" "ic_action_encrypt_copy" "ic_action_encrypt_paste" "ic_action_encrypt_save" "ic_action_encrypt_share" "status_lock_closed" "status_lock_error" "status_lock_open" "status_signature_expired_cutout" "status_signature_invalid_cutout" "status_signature_revoked_cutout" "status_signature_unknown_cutout" "status_signature_unverified_cutout" "status_signature_verified_cutout" "key_flag_authenticate" "key_flag_certify" "key_flag_encrypt" "key_flag_sign" "yubi_icon" "ic_stat_notify" "status_signature_verified_inner" "link" "octo_link" do echo $NAME inkscape -w 24 -h 24 -e "$MDPI_DIR/${NAME}_24dp.png" "$SRC_DIR/$NAME.svg"