merged dialog

This commit is contained in:
Adithya Abraham Philip
2015-02-26 22:26:40 +05:30
835 changed files with 14491 additions and 5628 deletions

View File

@@ -86,7 +86,7 @@ public class KeychainApplication extends Application {
}
brandGlowEffect(getApplicationContext(),
getApplicationContext().getResources().getColor(R.color.emphasis));
getApplicationContext().getResources().getColor(R.color.primary));
setupAccountAsNeeded(this);

View File

@@ -555,6 +555,7 @@ public abstract class OperationResult implements Parcelable {
MSG_DC_CLEAR_META_FILE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_file),
MSG_DC_CLEAR_META_MIME (LogLevel.DEBUG, R.string.msg_dc_clear_meta_mime),
MSG_DC_CLEAR_META_SIZE (LogLevel.DEBUG, R.string.msg_dc_clear_meta_size),
MSG_DC_CLEAR_META_SIZE_UNKNOWN (LogLevel.DEBUG, R.string.msg_dc_clear_meta_size_unknown),
MSG_DC_CLEAR_META_TIME (LogLevel.DEBUG, R.string.msg_dc_clear_meta_time),
MSG_DC_CLEAR (LogLevel.DEBUG, R.string.msg_dc_clear),
MSG_DC_CLEAR_SIGNATURE_BAD (LogLevel.WARN, R.string.msg_dc_clear_signature_bad),

View File

@@ -160,9 +160,6 @@ public class PgpDecryptVerify extends BaseOperation {
/**
* If detachedSignature != null, it will be used exclusively to verify the signature
*
* @param detachedSignature
* @return
*/
public Builder setDetachedSignature(byte[] detachedSignature) {
mDetachedSignature = detachedSignature;
@@ -540,12 +537,8 @@ public class PgpDecryptVerify extends BaseOperation {
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
// TODO: how to get the real original size?
// this is the encrypted size so if we enable compression this value is wrong!
long originalSize = mData.getSize() - mData.getStreamPosition();
if (originalSize < 0) {
originalSize = 0;
}
// reported size may be null if partial packets are involved (highly unlikely though)
Long originalSize = literalData.getDataLengthIfAvailable();
String originalFilename = literalData.getFileName();
String mimeType = null;
@@ -573,18 +566,20 @@ public class PgpDecryptVerify extends BaseOperation {
originalFilename,
mimeType,
literalData.getModificationTime().getTime(),
originalSize);
originalSize == null ? 0 : originalSize);
if (!originalFilename.equals("")) {
if (!"".equals(originalFilename)) {
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
}
log.add(LogType.MSG_DC_CLEAR_META_MIME, indent + 1,
mimeType);
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
new Date(literalData.getModificationTime().getTime()).toString());
if (originalSize != 0) {
if (originalSize != null) {
log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
Long.toString(originalSize));
} else {
log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
}
// return here if we want to decrypt the metadata only
@@ -633,9 +628,8 @@ public class PgpDecryptVerify extends BaseOperation {
progress = 100;
}
progressScaler.setProgress((int) progress, 100);
} else {
// TODO: slow annealing to fake a progress?
}
// TODO: slow annealing to fake a progress?
}
if (signature != null) {
@@ -851,9 +845,8 @@ public class PgpDecryptVerify extends BaseOperation {
progress = 100;
}
progressScaler.setProgress((int) progress, 100);
} else {
// TODO: slow annealing to fake a progress?
}
// TODO: slow annealing to fake a progress?
}
updateProgress(R.string.progress_verifying_signature, 90, 100);

View File

@@ -40,7 +40,10 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.remote.AppSettings;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.BaseActivity;
import org.sufficientlysecure.keychain.ui.dialog.AddSubkeyDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.security.MessageDigest;
@@ -130,16 +133,40 @@ public class AppSettingsActivity extends BaseActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_api_settings_revoke:
revokeAccess();
return true;
case R.id.menu_api_save:
case R.id.menu_api_save: {
save();
return true;
}
case R.id.menu_api_settings_revoke: {
revokeAccess();
return true;
}
case R.id.menu_api_settings_advanced: {
showAdvancedInfo();
return true;
}
}
return super.onOptionsItemSelected(item);
}
private void showAdvancedInfo() {
String signature = null;
// advanced info: package signature SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(mAppSettings.getPackageSignature());
byte[] digest = md.digest();
signature = new String(Hex.encode(digest));
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, "Should not happen!", e);
}
AdvancedAppSettingsDialogFragment dialogFragment =
AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), signature);
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
}
private void startApp() {
Intent i;
PackageManager manager = getPackageManager();
@@ -175,21 +202,6 @@ public class AppSettingsActivity extends BaseActivity {
mAppNameView.setText(appName);
mAppIconView.setImageDrawable(appIcon);
// advanced info: package name
mPackageName.setText(mAppSettings.getPackageName());
// advanced info: package signature SHA-256
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
md.update(mAppSettings.getPackageSignature());
byte[] digest = md.digest();
String signature = new String(Hex.encode(digest));
mPackageSignature.setText(signature);
} catch (NoSuchAlgorithmException e) {
Log.e(Constants.TAG, "Should not happen!", e);
}
Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build();
Log.d(Constants.TAG, "accountsUri: " + accountsUri);
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2013-2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
@@ -48,82 +48,83 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
import org.sufficientlysecure.keychain.util.Log;
public class AppsListFragment extends ListFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
// This is the Adapter being used to display the list's data.
RegisteredAppsAdapter mAdapter;
AppsAdapter mAdapter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
getListView().setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String selectedPackageName = mAdapter.getItemPackageName(position);
boolean installed = mAdapter.getItemIsInstalled(position);
boolean registered = mAdapter.getItemIsRegistered(position);
getListView().setOnItemClickListener(this);
if (installed) {
if (registered) {
// edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
startActivity(intent);
} else {
Intent i;
PackageManager manager = getActivity().getPackageManager();
try {
i = manager.getLaunchIntentForPackage(selectedPackageName);
if (i == null)
throw new PackageManager.NameNotFoundException();
// start like the Android launcher would do
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
i.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(i);
} catch (PackageManager.NameNotFoundException e) {
Log.e(Constants.TAG, "startApp", e);
}
}
} else {
try {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + selectedPackageName)));
} catch (ActivityNotFoundException anfe) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("http://play.google.com/store/apps/details?id=" + selectedPackageName)));
}
}
}
});
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText(getString(R.string.api_no_apps));
// NOTE: No setEmptyText(), we always have the default entries
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new RegisteredAppsAdapter(getActivity(), null, 0);
mAdapter = new AppsAdapter(getActivity(), null, 0);
setListAdapter(mAdapter);
// Loader is started in onResume!
// NOTE: Loader is started in onResume!
}
@Override
public void onResume() {
super.onResume();
// after coming back from Google Play -> reload
// Start out with a progress indicator.
setListShown(false);
// After coming back from Google Play -> reload
getLoaderManager().restartLoader(0, null, this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String selectedPackageName = mAdapter.getItemPackageName(position);
boolean installed = mAdapter.getItemIsInstalled(position);
boolean registered = mAdapter.getItemIsRegistered(position);
if (installed) {
if (registered) {
// Edit app settings
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
startActivity(intent);
} else {
Intent i;
PackageManager manager = getActivity().getPackageManager();
try {
i = manager.getLaunchIntentForPackage(selectedPackageName);
if (i == null) {
throw new PackageManager.NameNotFoundException();
}
// Start like the Android launcher would do
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
i.addCategory(Intent.CATEGORY_LAUNCHER);
startActivity(i);
} catch (PackageManager.NameNotFoundException e) {
Log.e(Constants.TAG, "startApp", e);
}
}
} else {
try {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + selectedPackageName)));
} catch (ActivityNotFoundException anfe) {
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName)));
}
}
}
private static final String TEMP_COLUMN_NAME = "NAME";
private static final String TEMP_COLUMN_INSTALLED = "INSTALLED";
private static final String TEMP_COLUMN_REGISTERED = "REGISTERED";
private static final String TEMP_COLUMN_ICON_RES_ID = "ICON_RES_ID";
// These are the Contacts rows that we will retrieve.
static final String[] PROJECTION = new String[]{
ApiApps._ID, // 0
ApiApps.PACKAGE_NAME, // 1
@@ -149,106 +150,17 @@ public class AppsListFragment extends ListFragment implements
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), baseUri, PROJECTION, null, null,
return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null,
ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{
ApiApps._ID,
ApiApps.PACKAGE_NAME,
TEMP_COLUMN_NAME,
TEMP_COLUMN_INSTALLED,
TEMP_COLUMN_REGISTERED,
TEMP_COLUMN_ICON_RES_ID
});
// NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner!
// Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png
availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9});
availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store});
availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations});
MatrixCursor mergedCursor = new MatrixCursor(new String[]{
ApiApps._ID,
ApiApps.PACKAGE_NAME,
TEMP_COLUMN_NAME,
TEMP_COLUMN_INSTALLED,
TEMP_COLUMN_REGISTERED,
TEMP_COLUMN_ICON_RES_ID
});
CursorJoiner joiner = new CursorJoiner(
availableAppsCursor,
new String[]{ApiApps.PACKAGE_NAME},
data,
new String[]{ApiApps.PACKAGE_NAME});
for (CursorJoiner.Result joinerResult : joiner) {
switch (joinerResult) {
case LEFT: {
// handle case where a row in availableAppsCursor is unique
String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME);
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
availableAppsCursor.getString(INDEX_NAME),
isInstalled(packageName),
0,
availableAppsCursor.getInt(INDEX_ICON_RES_ID)
});
break;
}
case RIGHT: {
// handle case where a row in data is unique
String packageName = data.getString(INDEX_PACKAGE_NAME);
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
null,
isInstalled(packageName),
1, // registered!
R.drawable.ic_launcher // icon is retrieved later
});
break;
}
case BOTH: {
// handle case where a row with the same key is in both cursors
String packageName = data.getString(INDEX_PACKAGE_NAME);
String name;
if (isInstalled(packageName) == 1) {
name = data.getString(INDEX_NAME);
} else {
// if not installed take name from available apps list
name = availableAppsCursor.getString(INDEX_NAME);
}
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
name,
isInstalled(packageName),
1, // registered!
R.drawable.ic_launcher // icon is retrieved later
});
break;
}
}
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(mergedCursor);
}
mAdapter.swapCursor(data);
private int isInstalled(String packageName) {
try {
getActivity().getPackageManager().getApplicationInfo(packageName, 0);
return 1;
} catch (final PackageManager.NameNotFoundException e) {
return 0;
}
// The list should now be shown.
setListShown(true);
}
public void onLoaderReset(Loader<Cursor> loader) {
@@ -258,12 +170,127 @@ public class AppsListFragment extends ListFragment implements
mAdapter.swapCursor(null);
}
private class RegisteredAppsAdapter extends CursorAdapter {
/**
* Besides the queried cursor with all registered apps, this loader also returns non-installed
* proposed apps using a MatrixCursor.
*/
private static class AppsLoader extends CursorLoader {
public AppsLoader(Context context) {
super(context);
}
public AppsLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
super(context, uri, projection, selection, selectionArgs, sortOrder);
}
@Override
public Cursor loadInBackground() {
// Load registered apps from content provider
Cursor data = super.loadInBackground();
MatrixCursor availableAppsCursor = new MatrixCursor(new String[]{
ApiApps._ID,
ApiApps.PACKAGE_NAME,
TEMP_COLUMN_NAME,
TEMP_COLUMN_INSTALLED,
TEMP_COLUMN_REGISTERED,
TEMP_COLUMN_ICON_RES_ID
});
// NOTE: SORT ascending by package name, this is REQUIRED for CursorJoiner!
// Drawables taken from projects res/drawables-xxhdpi/ic_launcher.png
availableAppsCursor.addRow(new Object[]{1, "com.fsck.k9", "K-9 Mail", 0, 0, R.drawable.apps_k9});
availableAppsCursor.addRow(new Object[]{1, "com.zeapo.pwdstore", "Password Store", 0, 0, R.drawable.apps_password_store});
availableAppsCursor.addRow(new Object[]{1, "eu.siacs.conversations", "Conversations (Instant Messaging)", 0, 0, R.drawable.apps_conversations});
MatrixCursor mergedCursor = new MatrixCursor(new String[]{
ApiApps._ID,
ApiApps.PACKAGE_NAME,
TEMP_COLUMN_NAME,
TEMP_COLUMN_INSTALLED,
TEMP_COLUMN_REGISTERED,
TEMP_COLUMN_ICON_RES_ID
});
CursorJoiner joiner = new CursorJoiner(
availableAppsCursor,
new String[]{ApiApps.PACKAGE_NAME},
data,
new String[]{ApiApps.PACKAGE_NAME});
for (CursorJoiner.Result joinerResult : joiner) {
switch (joinerResult) {
case LEFT: {
// handle case where a row in availableAppsCursor is unique
String packageName = availableAppsCursor.getString(INDEX_PACKAGE_NAME);
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
availableAppsCursor.getString(INDEX_NAME),
isInstalled(packageName),
0,
availableAppsCursor.getInt(INDEX_ICON_RES_ID)
});
break;
}
case RIGHT: {
// handle case where a row in data is unique
String packageName = data.getString(INDEX_PACKAGE_NAME);
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
null,
isInstalled(packageName),
1, // registered!
R.drawable.ic_launcher // icon is retrieved later
});
break;
}
case BOTH: {
// handle case where a row with the same key is in both cursors
String packageName = data.getString(INDEX_PACKAGE_NAME);
String name;
if (isInstalled(packageName) == 1) {
name = data.getString(INDEX_NAME);
} else {
// if not installed take name from available apps list
name = availableAppsCursor.getString(INDEX_NAME);
}
mergedCursor.addRow(new Object[]{
1, // no need for unique _ID
packageName,
name,
isInstalled(packageName),
1, // registered!
R.drawable.ic_launcher // icon is retrieved later
});
break;
}
}
}
return mergedCursor;
}
private int isInstalled(String packageName) {
try {
getContext().getPackageManager().getApplicationInfo(packageName, 0);
return 1;
} catch (final PackageManager.NameNotFoundException e) {
return 0;
}
}
}
private class AppsAdapter extends CursorAdapter {
private LayoutInflater mInflater;
private PackageManager mPM;
public RegisteredAppsAdapter(Context context, Cursor c, int flags) {
public AppsAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
@@ -273,44 +300,23 @@ public class AppsListFragment extends ListFragment implements
/**
* Similar to CursorAdapter.getItemId().
* Required to build Uris for api apps, which are not based on row ids
*
* @param position
* @return
*/
public String getItemPackageName(int position) {
if (mDataValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return mCursor.getString(INDEX_PACKAGE_NAME);
} else {
return null;
}
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
return mCursor.getString(INDEX_PACKAGE_NAME);
} else {
return null;
}
}
public boolean getItemIsInstalled(int position) {
if (mDataValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return (mCursor.getInt(INDEX_INSTALLED) == 1);
} else {
return false;
}
} else {
return false;
}
return mDataValid && mCursor != null
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1);
}
public boolean getItemIsRegistered(int position) {
if (mDataValid && mCursor != null) {
if (mCursor.moveToPosition(position)) {
return (mCursor.getInt(INDEX_REGISTERED) == 1);
} else {
return false;
}
} else {
return false;
}
return mDataValid && mCursor != null
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1);
}
@Override

View File

@@ -35,6 +35,7 @@ import org.sufficientlysecure.keychain.R;
*/
public abstract class BaseActivity extends ActionBarActivity {
protected Toolbar mToolbar;
protected View mStatusBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -51,6 +52,7 @@ public abstract class BaseActivity extends ActionBarActivity {
setSupportActionBar(mToolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
mStatusBar = findViewById(R.id.status_bar);
}
protected void setActionBarIcon(int iconRes) {

View File

@@ -1,6 +1,5 @@
/*
* Copyright (C) 2013-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2013 Bahtiar 'kalkin' Gadimov
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
@@ -18,54 +17,46 @@
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.util.Log;
public class ViewKeyAdvancedActivity extends BaseActivity {
public class CertifyFingerprintActivity extends BaseActivity {
ExportHelper mExportHelper;
ProviderHelper mProviderHelper;
protected Uri mDataUri;
@Override
protected void onCreate(Bundle savedInstanceState) {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mExportHelper = new ExportHelper(this);
mProviderHelper = new ProviderHelper(this);
// Inflate a "Done" custom action bar
setFullScreenDialogClose(
new View.OnClickListener() {
@Override
public void onClick(View v) {
// "Done"
finish();
}
}
);
Uri dataUri = getIntent().getData();
if (dataUri == null) {
mDataUri = getIntent().getData();
if (mDataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be uri of key!");
finish();
return;
}
Log.i(Constants.TAG, "mDataUri: " + dataUri.toString());
setFullScreenDialogClose(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
startFragment(savedInstanceState, dataUri);
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
startFragment(savedInstanceState, mDataUri);
}
@Override
protected void initLayout() {
setContentView(R.layout.view_key_advanced_activity);
setContentView(R.layout.certify_fingerprint_activity);
}
private void startFragment(Bundle savedInstanceState, Uri dataUri) {
@@ -77,15 +68,25 @@ public class ViewKeyAdvancedActivity extends BaseActivity {
}
// Create an instance of the fragment
ViewKeyAdvancedFragment frag = ViewKeyAdvancedFragment.newInstance(dataUri);
CertifyFingerprintFragment frag = CertifyFingerprintFragment.newInstance(dataUri);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.view_key_advanced_fragment, frag)
.replace(R.id.certify_fingerprint_fragment, frag)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// if a result has been returned, display a notify
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(this).show();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@@ -0,0 +1,184 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
public class CertifyFingerprintFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private TextView mFingerprint;
private static final int LOADER_ID_UNIFIED = 0;
private Uri mDataUri;
private View mActionNo;
private View mActionYes;
/**
* Creates new instance of this fragment
*/
public static CertifyFingerprintFragment newInstance(Uri dataUri) {
CertifyFingerprintFragment frag = new CertifyFingerprintFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.certify_fingerprint_fragment, getContainer());
mActionNo = view.findViewById(R.id.certify_fingerprint_button_no);
mActionYes = view.findViewById(R.id.certify_fingerprint_button_yes);
mFingerprint = (TextView) view.findViewById(R.id.certify_fingerprint_fingerprint);
mActionNo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getActivity().finish();
}
});
mActionYes.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
certify(mDataUri);
}
});
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
loadData(dataUri);
}
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
}
static final String[] UNIFIED_PROJECTION = new String[]{
KeyRings._ID, KeyRings.FINGERPRINT,
};
static final int INDEX_UNIFIED_FINGERPRINT = 1;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
}
default:
return null;
}
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(fingerprintBlob);
mFingerprint.setText(KeyFormattingUtils.colorizeFingerprint(fingerprint));
break;
}
}
}
setContentShown(true);
}
/**
* 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.
*/
public void onLoaderReset(Loader<Cursor> loader) {
}
private void certify(Uri dataUri) {
long keyId = 0;
try {
keyId = new ProviderHelper(getActivity())
.getCachedPublicKeyRing(dataUri)
.extractOrGetMasterKeyId();
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
}
Intent certifyIntent = new Intent(getActivity(), CertifyKeyActivity.class);
certifyIntent.putExtra(CertifyKeyActivity.EXTRA_KEY_IDS, new long[]{keyId});
startActivityForResult(certifyIntent, 0);
}
}

View File

@@ -21,6 +21,7 @@ import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.v4.app.Fragment;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -110,11 +111,9 @@ public class ImportKeysCloudFragment extends Fragment {
mConfigButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent i = new Intent(mImportActivity, SettingsActivity.class);
// GRR, for some reason I cant set the Action or I get an incomprehensible
// exception about “modern two-pane layouts”
// i.setAction(PreferencesActivity.ACTION_PREFS_CLOUD);
startActivity(i);
Intent intent = new Intent(mImportActivity, SettingsActivity.class);
intent.putExtra(PreferenceActivity.EXTRA_SHOW_FRAGMENT, SettingsActivity.CloudSearchPrefsFragment.class.getName());
startActivity(intent);
}
});

View File

@@ -56,6 +56,8 @@ import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.getbase.floatingactionbutton.FloatingActionButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ConsolidateResult;
@@ -105,6 +107,10 @@ public class KeyListFragment extends LoaderFragment
private String mQuery;
private SearchView mSearchView;
private FloatingActionButton mFabQrCode;
private FloatingActionButton mFabCloud;
private FloatingActionButton mFabFile;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -123,6 +129,29 @@ public class KeyListFragment extends LoaderFragment
mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list);
mStickyList.setOnItemClickListener(this);
mFabQrCode = (FloatingActionButton) view.findViewById(R.id.fab_add_qr_code);
mFabCloud = (FloatingActionButton) view.findViewById(R.id.fab_add_cloud);
mFabFile = (FloatingActionButton) view.findViewById(R.id.fab_add_file);
mFabQrCode.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
scanQrCode();
}
});
mFabCloud.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
searchCloud();
}
});
mFabFile.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
importFile();
}
});
mSwipeRefreshLayout = (ListAwareSwipeRefreshLayout) view.findViewById(R.id.key_list_swipe_container);
mSwipeRefreshLayout.setOnRefreshListener(new NoScrollableSwipeRefreshLayout.OnRefreshListener() {
@Override
@@ -198,6 +227,9 @@ public class KeyListFragment extends LoaderFragment
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// show app name instead of "keys" from nav drawer
getActivity().setTitle(R.string.app_name);
mStickyList.setOnItemClickListener(this);
mStickyList.setAreHeadersSticky(true);
mStickyList.setDrawingListUnderStickyHeader(false);
@@ -466,9 +498,6 @@ public class KeyListFragment extends LoaderFragment
// Execute this when searching
mSearchView.setOnQueryTextListener(this);
View searchPlate = mSearchView.findViewById(android.support.v7.appcompat.R.id.search_plate);
searchPlate.setBackgroundResource(R.drawable.keychaintheme_searchview_holo_light);
// Erase search result without focus
MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
@Override
@@ -496,26 +525,11 @@ public class KeyListFragment extends LoaderFragment
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_key_list_add:
Intent scanQrCode = new Intent(getActivity(), QrCodeScanActivity.class);
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
startActivityForResult(scanQrCode, 0);
return true;
case R.id.menu_key_list_search_cloud:
searchCloud();
return true;
case R.id.menu_key_list_create:
createKey();
return true;
case R.id.menu_key_list_import_existing_key:
Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class);
intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
startActivityForResult(intentImportExisting, 0);
return true;
case R.id.menu_key_list_export:
mExportHelper.showExportKeysDialog(null, Constants.Path.APP_DIR_FILE, true);
return true;
@@ -587,6 +601,18 @@ public class KeyListFragment extends LoaderFragment
startActivity(importIntent);
}
private void scanQrCode() {
Intent scanQrCode = new Intent(getActivity(), QrCodeScanActivity.class);
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
startActivityForResult(scanQrCode, 0);
}
private void importFile() {
Intent intentImportExisting = new Intent(getActivity(), ImportKeysActivity.class);
intentImportExisting.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN);
startActivityForResult(intentImportExisting, 0);
}
private void createKey() {
Intent intent = new Intent(getActivity(), CreateKeyActivity.class);
startActivityForResult(intent, 0);
@@ -749,13 +775,13 @@ public class KeyListFragment extends LoaderFragment
// Note: order is important!
if (isRevoked) {
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, true);
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
h.mStatus.setVisibility(View.VISIBLE);
h.mSlinger.setVisibility(View.GONE);
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));
h.mMainUserIdRest.setTextColor(context.getResources().getColor(R.color.bg_gray));
} else if (isExpired) {
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, true);
KeyFormattingUtils.setStatusImage(getActivity(), h.mStatus, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray);
h.mStatus.setVisibility(View.VISIBLE);
h.mSlinger.setVisibility(View.GONE);
h.mMainUserId.setTextColor(context.getResources().getColor(R.color.bg_gray));

View File

@@ -255,11 +255,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
switch (subEntry.mType.mLevel) {
case DEBUG: ih.mSecondImg.setBackgroundColor(Color.GRAY); break;
case INFO: ih.mSecondImg.setBackgroundColor(Color.BLACK); break;
case WARN: ih.mSecondImg.setBackgroundColor(Color.YELLOW); break;
case ERROR: ih.mSecondImg.setBackgroundColor(Color.RED); break;
case START: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break;
case OK: ih.mSecondImg.setBackgroundColor(Color.GREEN); break;
case CANCELLED: ih.mSecondImg.setBackgroundColor(Color.RED); break;
case WARN: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break;
case ERROR: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
case START: ih.mSecondImg.setBackgroundColor(Color.BLACK); break;
case OK: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break;
case CANCELLED: ih.mSecondImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
}
} else {
ih.mSecond.setVisibility(View.GONE);
@@ -286,11 +286,11 @@ public class LogDisplayFragment extends ListFragment implements OnItemClickListe
switch (entry.mType.mLevel) {
case DEBUG: ih.mImg.setBackgroundColor(Color.GRAY); break;
case INFO: ih.mImg.setBackgroundColor(Color.BLACK); break;
case WARN: ih.mImg.setBackgroundColor(Color.YELLOW); break;
case ERROR: ih.mImg.setBackgroundColor(Color.RED); break;
case START: ih.mImg.setBackgroundColor(getResources().getColor(R.color.emphasis)); break;
case OK: ih.mImg.setBackgroundColor(Color.GREEN); break;
case CANCELLED: ih.mImg.setBackgroundColor(Color.RED); break;
case WARN: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_orange_light)); break;
case ERROR: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
case START: ih.mImg.setBackgroundColor(Color.BLACK); break;
case OK: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_green_light)); break;
case CANCELLED: ih.mImg.setBackgroundColor(getResources().getColor(R.color.android_red_light)); break;
}
return convertView;

View File

@@ -31,8 +31,6 @@ public class MainActivity extends NavDrawerActivity {
public void init(Bundle savedInstanceState) {
super.init(savedInstanceState);
setTitle(R.string.nav_keys);
// if this is the first time show first time activity
Preferences prefs = Preferences.getPreferences(this);
if (prefs.isFirstTime()) {

View File

@@ -39,7 +39,7 @@ public abstract class NavDrawerActivity extends MaterialNavigationDrawer {
setDrawerHeaderImage(R.drawable.drawer_header);
// create sections
addSection(newSection(getString(R.string.title_keys), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment()));
addSection(newSection(getString(R.string.nav_keys), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment()));
addSection(newSection(getString(R.string.nav_encrypt_decrypt), R.drawable.ic_lock_black_24dp, new EncryptDecryptOverviewFragment()));
addSection(newSection(getString(R.string.title_api_registered_apps), R.drawable.ic_apps_black_24dp, new AppsListFragment()));

View File

@@ -41,8 +41,6 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.haibison.android.lockpattern.LockPatternActivity;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
@@ -108,20 +106,20 @@ public class PassphraseDialogActivity extends FragmentActivity {
case RESULT_CANCELED:
// The user cancelled the task
break;
case LockPatternActivity.RESULT_FAILED:
// The user failed to enter the pattern
break;
case LockPatternActivity.RESULT_FORGOT_PATTERN:
// The user forgot the pattern and invoked your recovery Activity.
break;
// case LockPatternActivity.RESULT_FAILED:
// // The user failed to enter the pattern
// break;
// case LockPatternActivity.RESULT_FORGOT_PATTERN:
// // The user forgot the pattern and invoked your recovery Activity.
// break;
}
/*
* In any case, there's always a key EXTRA_RETRY_COUNT, which holds
* the number of tries that the user did.
*/
int retryCount = data.getIntExtra(
LockPatternActivity.EXTRA_RETRY_COUNT, 0);
// int retryCount = data.getIntExtra(
// LockPatternActivity.EXTRA_RETRY_COUNT, 0);
break;
}
@@ -253,9 +251,9 @@ public class PassphraseDialogActivity extends FragmentActivity {
if (keyType == CanonicalizedSecretKey.SecretKeyType.PATTERN) {
// start pattern dialog and show progress circle here...
Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
// Intent patternActivity = new Intent(getActivity(), LockPatternActivity.class);
// patternActivity.putExtra(LockPatternActivity.EXTRA_PATTERN, "123");
// startActivityForResult(patternActivity, REQUEST_CODE_ENTER_PATTERN);
mInput.setVisibility(View.GONE);
mProgress.setVisibility(View.VISIBLE);
} else {

View File

@@ -43,10 +43,6 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.haibison.android.lockpattern.LockPatternFragment;
import com.haibison.android.lockpattern.LockPatternFragmentOld;
import com.haibison.android.lockpattern.widget.LockPatternView;
import org.sufficientlysecure.keychain.R;
import java.io.IOException;
@@ -56,7 +52,8 @@ import java.util.Arrays;
import java.util.List;
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
public class PassphraseWizardActivity extends FragmentActivity {
//public class PassphraseWizardActivity extends FragmentActivity implements LockPatternView.OnPatternListener {
//create or authenticate
public String selectedAction;
//for lockpattern
@@ -117,10 +114,10 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
getActionBar().setTitle(R.string.draw_lockpattern);
}
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
// LockPatternFragment lpf = LockPatternFragment.newInstance("asd");
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
}
public void cancel(View view) {
@@ -205,9 +202,9 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
writeNFC = false; //just write once
Toast.makeText(this, R.string.nfc_write_succesful, Toast.LENGTH_SHORT).show();
//advance to lockpattern
LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
}
} catch (IOException | FormatException e) {
e.printStackTrace();
@@ -224,9 +221,9 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
//passwort matches, go to next view
Toast.makeText(this, R.string.passphrases_match + "!", Toast.LENGTH_SHORT).show();
LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
// LockPatternFragmentOld lpf = LockPatternFragmentOld.newInstance(selectedAction);
// FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// transaction.replace(R.id.fragmentContainer, lpf).addToBackStack(null).commit();
readNFC = false; //just once
} else {
//passwort doesnt match
@@ -352,26 +349,6 @@ public class PassphraseWizardActivity extends FragmentActivity implements LockPa
adapter.disableForegroundDispatch(this);
}
@Override
public void onPatternStart() {
}
@Override
public void onPatternCleared() {
}
@Override
public void onPatternCellAdded(List<LockPatternView.Cell> pattern) {
}
@Override
public void onPatternDetected(List<LockPatternView.Cell> pattern) {
}
public static class SelectMethods extends Fragment {
// private OnFragmentInteractionListener mListener;

View File

@@ -20,6 +20,8 @@ package org.sufficientlysecure.keychain.ui;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.CardView;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
@@ -36,7 +38,8 @@ import org.sufficientlysecure.keychain.util.Log;
public class QrCodeViewActivity extends BaseActivity {
private ImageView mFingerprintQrCode;
private ImageView mQrCode;
private CardView mQrCodeLayout;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -48,7 +51,7 @@ public class QrCodeViewActivity extends BaseActivity {
@Override
public void onClick(View v) {
// "Done"
finish();
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
}
}
);
@@ -56,16 +59,17 @@ public class QrCodeViewActivity extends BaseActivity {
Uri dataUri = getIntent().getData();
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
finish();
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
return;
}
mFingerprintQrCode = (ImageView) findViewById(R.id.qr_code_image);
mQrCode = (ImageView) findViewById(R.id.qr_code_image);
mQrCodeLayout = (CardView) findViewById(R.id.qr_code_image_layout);
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
}
});
@@ -77,7 +81,7 @@ public class QrCodeViewActivity extends BaseActivity {
if (blob == null) {
Log.e(Constants.TAG, "key not found!");
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
finish();
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
}
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
@@ -86,20 +90,20 @@ public class QrCodeViewActivity extends BaseActivity {
// create a minimal size qr code, we can keep this in ram no problem
final Bitmap qrCode = QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0);
mFingerprintQrCode.getViewTreeObserver().addOnGlobalLayoutListener(
mQrCode.getViewTreeObserver().addOnGlobalLayoutListener(
new OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
// create actual bitmap in display dimensions
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
mFingerprintQrCode.getWidth(), mFingerprintQrCode.getWidth(), false);
mFingerprintQrCode.setImageBitmap(scaled);
}
});
@Override
public void onGlobalLayout() {
// create actual bitmap in display dimensions
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
mQrCode.getWidth(), mQrCode.getWidth(), false);
mQrCode.setImageBitmap(scaled);
}
});
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
Notify.showNotify(this, R.string.error_key_not_found, Style.ERROR);
finish();
ActivityCompat.finishAfterTransition(QrCodeViewActivity.this);
}
}
@@ -108,20 +112,4 @@ public class QrCodeViewActivity extends BaseActivity {
setContentView(R.layout.qr_code_activity);
}
@Override
protected void onResume() {
super.onResume();
// custom activity transition to get zoom in effect
this.overridePendingTransition(R.anim.qr_code_zoom_enter, android.R.anim.fade_out);
}
@Override
protected void onPause() {
super.onPause();
// custom activity transition to get zoom out effect
this.overridePendingTransition(0, R.anim.qr_code_zoom_exit);
}
}

View File

@@ -183,7 +183,6 @@ public class SettingsActivity extends PreferenceActivity {
}
}
/* Called only on Honeycomb and later */
@Override
public void onBuildHeaders(List<Header> target) {
super.onBuildHeaders(target);

View File

@@ -19,8 +19,10 @@
package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.ActivityOptions;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
@@ -32,31 +34,37 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.CardView;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.animation.AlphaAnimation;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import com.astuetz.PagerSlidingTabStrip;
import com.getbase.floatingactionbutton.FloatingActionButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.util.QrCodeUtils;
import org.sufficientlysecure.keychain.ui.widget.AspectRatioImageView;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.Log;
@@ -72,19 +80,18 @@ public class ViewKeyActivity extends BaseActivity implements
protected Uri mDataUri;
public static final String EXTRA_SELECTED_TAB = "selected_tab";
public static final int TAB_MAIN = 0;
public static final int TAB_SHARE = 1;
// view
private ViewPager mViewPager;
private PagerSlidingTabStrip mSlidingTabLayout;
private PagerTabStripAdapter mTabsAdapter;
private LinearLayout mStatusLayout;
private TextView mName;
private TextView mStatusText;
private ImageView mStatusImage;
private View mStatusDivider;
private RelativeLayout mBigToolbar;
private ImageButton mActionEncryptFile;
private ImageButton mActionEncryptText;
private ImageButton mActionNfc;
private FloatingActionButton mFab;
private AspectRatioImageView mPhoto;
private ImageView mQrCode;
private CardView mQrCodeLayout;
// NFC
private NfcAdapter mNfcAdapter;
@@ -95,6 +102,10 @@ public class ViewKeyActivity extends BaseActivity implements
private static final int LOADER_ID_UNIFIED = 0;
private boolean mIsSecret = false;
private boolean mHasEncrypt = false;
private boolean mIsVerified = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -102,25 +113,20 @@ public class ViewKeyActivity extends BaseActivity implements
mExportHelper = new ExportHelper(this);
mProviderHelper = new ProviderHelper(this);
// let the actionbar look like Android's contact app
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setIcon(android.R.color.transparent);
actionBar.setHomeButtonEnabled(true);
setTitle(null);
mStatusLayout = (LinearLayout) findViewById(R.id.view_key_status_layout);
mStatusText = (TextView) findViewById(R.id.view_key_status_text);
mName = (TextView) findViewById(R.id.view_key_name);
mStatusText = (TextView) findViewById(R.id.view_key_status);
mStatusImage = (ImageView) findViewById(R.id.view_key_status_image);
mStatusDivider = findViewById(R.id.view_key_status_divider);
mBigToolbar = (RelativeLayout) findViewById(R.id.toolbar_big);
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.view_key_sliding_tab_layout);
int switchToTab = TAB_MAIN;
Intent intent = getIntent();
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
}
mActionEncryptFile = (ImageButton) findViewById(R.id.view_key_action_encrypt_files);
mActionEncryptText = (ImageButton) findViewById(R.id.view_key_action_encrypt_text);
mActionNfc = (ImageButton) findViewById(R.id.view_key_action_nfc);
mFab = (FloatingActionButton) findViewById(R.id.fab);
mPhoto = (AspectRatioImageView) findViewById(R.id.view_key_photo);
mQrCode = (ImageView) findViewById(R.id.view_key_qr_code);
mQrCodeLayout = (CardView) findViewById(R.id.view_key_qr_code_layout);
mDataUri = getIntent().getData();
if (mDataUri == null) {
@@ -140,16 +146,52 @@ public class ViewKeyActivity extends BaseActivity implements
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
mActionEncryptFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encrypt(mDataUri, false);
}
});
mActionEncryptText.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encrypt(mDataUri, true);
}
});
mFab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mIsSecret) {
startSafeSlinger(mDataUri);
} else {
scanQrCode();
}
}
});
mQrCodeLayout.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showQrCodeDialog();
}
});
mActionNfc.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
invokeNfcBeam();
}
});
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
initNfc(mDataUri);
initTabs(mDataUri);
// switch to tab selected by extra
mViewPager.setCurrentItem(switchToTab);
startFragment(savedInstanceState, mDataUri);
}
@Override
@@ -157,22 +199,24 @@ public class ViewKeyActivity extends BaseActivity implements
setContentView(R.layout.view_key_activity);
}
private void initTabs(Uri dataUri) {
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
private void startFragment(Bundle savedInstanceState, Uri dataUri) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
if (savedInstanceState != null) {
return;
}
Bundle mainBundle = new Bundle();
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyMainFragment.class,
mainBundle, getString(R.string.key_view_tab_main));
// Create an instance of the fragment
ViewKeyFragment frag = ViewKeyFragment.newInstance(dataUri);
Bundle shareBundle = new Bundle();
shareBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyShareFragment.class,
shareBundle, getString(R.string.key_view_tab_share));
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
getSupportFragmentManager().beginTransaction()
.replace(R.id.view_key_fragment, frag)
.commitAllowingStateLoss();
// do it immediately!
getSupportFragmentManager().executePendingTransactions();
}
@Override
@@ -202,9 +246,26 @@ public class ViewKeyActivity extends BaseActivity implements
return true;
}
case R.id.menu_key_view_advanced: {
Intent advancedIntent = new Intent(this, ViewKeyAdvancedActivity.class);
Intent advancedIntent = new Intent(this, ViewKeyAdvActivity.class);
advancedIntent.setData(mDataUri);
startActivity(advancedIntent);
return true;
}
case R.id.menu_key_view_refresh: {
try {
updateFromKeyserver(mDataUri, mProviderHelper);
} catch (ProviderHelper.NotFoundException e) {
Notify.showNotify(this, R.string.error_key_not_found, Notify.Style.ERROR);
}
return true;
}
case R.id.menu_key_view_edit: {
editKey(mDataUri);
return true;
}
case R.id.menu_key_view_certify_fingerprint: {
certifyFingeprint(mDataUri);
return true;
}
}
} catch (ProviderHelper.NotFoundException e) {
@@ -214,6 +275,75 @@ public class ViewKeyActivity extends BaseActivity implements
return super.onOptionsItemSelected(item);
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
MenuItem editKey = menu.findItem(R.id.menu_key_view_edit);
editKey.setVisible(mIsSecret);
MenuItem certifyFingerprint = menu.findItem(R.id.menu_key_view_certify_fingerprint);
certifyFingerprint.setVisible(!mIsSecret && !mIsVerified);
return true;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void invokeNfcBeam() {
// Check for available NFC Adapter
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null || !mNfcAdapter.isEnabled()) {
Notify.createNotify(this, R.string.error_nfc_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() {
@Override
public void onAction() {
Intent intentSettings = new Intent(Settings.ACTION_NFC_SETTINGS);
startActivity(intentSettings);
}
}, R.string.menu_nfc_preferences).show();
return;
}
if (!mNfcAdapter.isNdefPushEnabled()) {
Notify.createNotify(this, R.string.error_beam_needed, Notify.LENGTH_LONG, Notify.Style.ERROR, new Notify.ActionListener() {
@Override
public void onAction() {
Intent intentSettings = new Intent(Settings.ACTION_NFCSHARING_SETTINGS);
startActivity(intentSettings);
}
}, R.string.menu_beam_preferences).show();
return;
}
mNfcAdapter.invokeBeam(this);
}
private void scanQrCode() {
Intent scanQrCode = new Intent(this, QrCodeScanActivity.class);
scanQrCode.setAction(QrCodeScanActivity.ACTION_SCAN_WITH_RESULT);
startActivityForResult(scanQrCode, 0);
}
private void certifyFingeprint(Uri dataUri) {
Intent intent = new Intent(this, CertifyFingerprintActivity.class);
intent.setData(dataUri);
startActivityForResult(intent, 0);
}
private void showQrCodeDialog() {
Intent qrCodeIntent = new Intent(this, QrCodeViewActivity.class);
// create the transition animation - the images in the layouts
// of both activities are defined with android:transitionName="qr_code"
Bundle opts = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ActivityOptions options = ActivityOptions
.makeSceneTransitionAnimation(this, mQrCodeLayout, "qr_code");
opts = options.toBundle();
}
qrCodeIntent.setData(mDataUri);
ActivityCompat.startActivity(this, qrCodeIntent, opts);
}
private void exportToFile(Uri dataUri, ExportHelper exportHelper, ProviderHelper providerHelper)
throws ProviderHelper.NotFoundException {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri);
@@ -255,6 +385,101 @@ public class ViewKeyActivity extends BaseActivity implements
}
}
private void encrypt(Uri dataUri, boolean text) {
// If there is no encryption key, don't bother.
if (!mHasEncrypt) {
Notify.showNotify(this, R.string.error_no_encrypt_subkey, Notify.Style.ERROR);
return;
}
try {
long keyId = new ProviderHelper(this)
.getCachedPublicKeyRing(dataUri)
.extractOrGetMasterKeyId();
long[] encryptionKeyIds = new long[]{keyId};
Intent intent;
if (text) {
intent = new Intent(this, EncryptTextActivity.class);
intent.setAction(EncryptTextActivity.ACTION_ENCRYPT_TEXT);
intent.putExtra(EncryptTextActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
} else {
intent = new Intent(this, EncryptFilesActivity.class);
intent.setAction(EncryptFilesActivity.ACTION_ENCRYPT_DATA);
intent.putExtra(EncryptFilesActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
}
// used instead of startActivity set actionbar based on callingPackage
startActivityForResult(intent, 0);
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
}
}
private void updateFromKeyserver(Uri dataUri, ProviderHelper providerHelper)
throws ProviderHelper.NotFoundException {
byte[] blob = (byte[]) providerHelper.getGenericData(
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(blob);
Intent queryIntent = new Intent(this, ImportKeysActivity.class);
queryIntent.setAction(ImportKeysActivity.ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN_RESULT);
queryIntent.putExtra(ImportKeysActivity.EXTRA_FINGERPRINT, fingerprint);
startActivityForResult(queryIntent, 0);
}
private void editKey(Uri dataUri) {
Intent editIntent = new Intent(this, EditKeyActivity.class);
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
startActivityForResult(editIntent, 0);
}
private void startSafeSlinger(Uri dataUri) {
long keyId = 0;
try {
keyId = new ProviderHelper(this)
.getCachedPublicKeyRing(dataUri)
.extractOrGetMasterKeyId();
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
}
Intent safeSlingerIntent = new Intent(this, SafeSlingerActivity.class);
safeSlingerIntent.putExtra(SafeSlingerActivity.EXTRA_MASTER_KEY_ID, keyId);
startActivityForResult(safeSlingerIntent, 0);
}
/**
* Load QR Code asynchronously and with a fade in animation
*
* @param fingerprint
*/
private void loadQrCode(final String fingerprint) {
AsyncTask<Void, Void, Bitmap> loadTask =
new AsyncTask<Void, Void, Bitmap>() {
protected Bitmap doInBackground(Void... unused) {
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
// render with minimal size
return QrCodeUtils.getQRCodeBitmap(qrCodeContent, 0);
}
protected void onPostExecute(Bitmap qrCode) {
// scale the image up to our actual size. we do this in code rather
// than let the ImageView do this because we don't require filtering.
Bitmap scaled = Bitmap.createScaledBitmap(qrCode,
mQrCode.getHeight(), mQrCode.getHeight(),
false);
mQrCode.setImageBitmap(scaled);
// simple fade-in animation
AlphaAnimation anim = new AlphaAnimation(0.0f, 1.0f);
anim.setDuration(200);
mQrCode.startAnimation(anim);
}
};
loadTask.execute();
}
/**
* NFC: Initialize NFC sharing if OS and device supports it
*/
@@ -345,25 +570,34 @@ public class ViewKeyActivity extends BaseActivity implements
}
};
static final String[] UNIFIED_PROJECTION = new String[]{
// These are the rows that we will retrieve.
static final String[] PROJECTION = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.EXPIRY,
KeychainContract.KeyRings.VERIFIED,
KeychainContract.KeyRings.HAS_ANY_SECRET,
KeychainContract.KeyRings.FINGERPRINT,
KeychainContract.KeyRings.HAS_ENCRYPT
};
static final int INDEX_UNIFIED_MASTER_KEY_ID = 1;
static final int INDEX_UNIFIED_USER_ID = 2;
static final int INDEX_UNIFIED_IS_REVOKED = 3;
static final int INDEX_UNIFIED_EXPIRY = 4;
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_USER_ID = 2;
static final int INDEX_IS_REVOKED = 3;
static final int INDEX_EXPIRY = 4;
static final int INDEX_VERIFIED = 5;
static final int INDEX_HAS_ANY_SECRET = 6;
static final int INDEX_FINGERPRINT = 7;
static final int INDEX_HAS_ENCRYPT = 8;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(this, baseUri, UNIFIED_PROJECTION, null, null, null);
return new CursorLoader(this, baseUri, PROJECTION, null, null, null);
}
default:
@@ -386,36 +620,136 @@ public class ViewKeyActivity extends BaseActivity implements
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
// get name, email, and comment from USER_ID
String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_UNIFIED_USER_ID));
String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
if (mainUserId[0] != null) {
setTitle(mainUserId[0]);
mName.setText(mainUserId[0]);
} else {
setTitle(R.string.user_id_no_name);
mName.setText(R.string.user_id_no_name);
}
// get key id from MASTER_KEY_ID
long masterKeyId = data.getLong(INDEX_UNIFIED_MASTER_KEY_ID);
getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId));
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(data.getBlob(INDEX_FINGERPRINT));
boolean isRevoked = data.getInt(INDEX_UNIFIED_IS_REVOKED) > 0;
boolean isExpired = !data.isNull(INDEX_UNIFIED_EXPIRY)
&& new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000).before(new Date());
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
mHasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
boolean isExpired = !data.isNull(INDEX_EXPIRY)
&& new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date());
mIsVerified = data.getInt(INDEX_VERIFIED) > 0;
// re-create options menu based on mIsSecret, mIsVerified
supportInvalidateOptionsMenu();
AsyncTask<String, Void, Bitmap> photoTask =
new AsyncTask<String, Void, Bitmap>() {
protected Bitmap doInBackground(String... fingerprint) {
return ContactHelper.photoFromFingerprint(getContentResolver(), fingerprint[0]);
}
protected void onPostExecute(Bitmap photo) {
mPhoto.setImageBitmap(photo);
mPhoto.setVisibility(View.VISIBLE);
}
};
// Note: order is important
int color;
if (isRevoked) {
mStatusText.setText(R.string.view_key_revoked);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, KeyFormattingUtils.STATE_REVOKED);
mStatusDivider.setVisibility(View.VISIBLE);
mStatusLayout.setVisibility(View.VISIBLE);
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
KeyFormattingUtils.STATE_REVOKED, R.color.icons, true);
color = getResources().getColor(R.color.android_red_light);
mActionEncryptFile.setVisibility(View.GONE);
mActionEncryptText.setVisibility(View.GONE);
mActionNfc.setVisibility(View.GONE);
mFab.setVisibility(View.GONE);
mQrCodeLayout.setVisibility(View.GONE);
} else if (isExpired) {
mStatusText.setText(R.string.view_key_expired);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText, KeyFormattingUtils.STATE_EXPIRED);
mStatusDivider.setVisibility(View.VISIBLE);
mStatusLayout.setVisibility(View.VISIBLE);
if (mIsSecret) {
mStatusText.setText(R.string.view_key_expired_secret);
} else {
mStatusText.setText(R.string.view_key_expired);
}
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
KeyFormattingUtils.STATE_EXPIRED, R.color.icons, true);
color = getResources().getColor(R.color.android_red_light);
mActionEncryptFile.setVisibility(View.GONE);
mActionEncryptText.setVisibility(View.GONE);
mActionNfc.setVisibility(View.GONE);
mFab.setVisibility(View.GONE);
mQrCodeLayout.setVisibility(View.GONE);
} else if (mIsSecret) {
mStatusText.setText(R.string.view_key_my_key);
mStatusImage.setVisibility(View.GONE);
color = getResources().getColor(R.color.primary);
photoTask.execute(fingerprint);
loadQrCode(fingerprint);
mQrCodeLayout.setVisibility(View.VISIBLE);
// and place leftOf qr code
RelativeLayout.LayoutParams nameParams = (RelativeLayout.LayoutParams)
mName.getLayoutParams();
// remove right margin
nameParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
nameParams.setMarginEnd(0);
}
nameParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
mName.setLayoutParams(nameParams);
RelativeLayout.LayoutParams statusParams = (RelativeLayout.LayoutParams)
mStatusText.getLayoutParams();
statusParams.setMargins(FormattingUtils.dpToPx(this, 48), 0, 0, 0);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
statusParams.setMarginEnd(0);
}
statusParams.addRule(RelativeLayout.LEFT_OF, R.id.view_key_qr_code_layout);
mStatusText.setLayoutParams(statusParams);
mActionEncryptFile.setVisibility(View.VISIBLE);
mActionEncryptText.setVisibility(View.VISIBLE);
// invokeBeam is available from API 21
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mActionNfc.setVisibility(View.VISIBLE);
} else {
mActionNfc.setVisibility(View.GONE);
}
mFab.setVisibility(View.VISIBLE);
mFab.setIconDrawable(getResources().getDrawable(R.drawable.ic_repeat_white_24dp));
} else {
mStatusDivider.setVisibility(View.GONE);
mStatusLayout.setVisibility(View.GONE);
mActionEncryptFile.setVisibility(View.VISIBLE);
mActionEncryptText.setVisibility(View.VISIBLE);
mQrCodeLayout.setVisibility(View.GONE);
mActionNfc.setVisibility(View.GONE);
if (mIsVerified) {
mStatusText.setText(R.string.view_key_verified);
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
KeyFormattingUtils.STATE_VERIFIED, R.color.icons, true);
color = getResources().getColor(R.color.primary);
photoTask.execute(fingerprint);
mFab.setVisibility(View.GONE);
} else {
mStatusText.setText(R.string.view_key_unverified);
mStatusImage.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(this, mStatusImage, mStatusText,
KeyFormattingUtils.STATE_UNVERIFIED, R.color.icons, true);
color = getResources().getColor(R.color.android_orange_light);
mFab.setVisibility(View.VISIBLE);
}
}
mToolbar.setBackgroundColor(color);
mStatusBar.setBackgroundColor(color);
mBigToolbar.setBackgroundColor(color);
mStatusImage.setAlpha(80);
break;
}

View File

@@ -0,0 +1,252 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.view.View;
import android.widget.Toast;
import com.astuetz.PagerSlidingTabStrip;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ContactHelper;
import org.sufficientlysecure.keychain.util.ExportHelper;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
public class ViewKeyAdvActivity extends BaseActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
ExportHelper mExportHelper;
ProviderHelper mProviderHelper;
protected Uri mDataUri;
public static final String EXTRA_SELECTED_TAB = "selected_tab";
public static final int TAB_MAIN = 0;
public static final int TAB_SHARE = 1;
// view
private ViewPager mViewPager;
private PagerSlidingTabStrip mSlidingTabLayout;
private PagerTabStripAdapter mTabsAdapter;
private static final int LOADER_ID_UNIFIED = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setFullScreenDialogClose(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
mExportHelper = new ExportHelper(this);
mProviderHelper = new ProviderHelper(this);
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
mSlidingTabLayout = (PagerSlidingTabStrip) findViewById(R.id.view_key_sliding_tab_layout);
int switchToTab = TAB_MAIN;
Intent intent = getIntent();
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
}
mDataUri = getIntent().getData();
if (mDataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be uri of key!");
finish();
return;
}
if (mDataUri.getHost().equals(ContactsContract.AUTHORITY)) {
mDataUri = ContactHelper.dataUriFromContactUri(this, mDataUri);
if (mDataUri == null) {
Log.e(Constants.TAG, "Contact Data missing. Should be uri of key!");
Toast.makeText(this, R.string.error_contacts_key_id_missing, Toast.LENGTH_LONG).show();
finish();
return;
}
}
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
initTabs(mDataUri);
// switch to tab selected by extra
mViewPager.setCurrentItem(switchToTab);
}
@Override
protected void initLayout() {
setContentView(R.layout.view_key_adv_activity);
}
private void initTabs(Uri dataUri) {
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
Bundle mainBundle = new Bundle();
mainBundle.putParcelable(ViewKeyAdvMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvMainFragment.class,
mainBundle, getString(R.string.key_view_tab_main));
Bundle shareBundle = new Bundle();
shareBundle.putParcelable(ViewKeyAdvMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvShareFragment.class,
shareBundle, getString(R.string.key_view_tab_share));
Bundle keysBundle = new Bundle();
keysBundle.putParcelable(ViewKeyAdvSubkeysFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvSubkeysFragment.class,
keysBundle, getString(R.string.key_view_tab_keys));
Bundle certsBundle = new Bundle();
certsBundle.putParcelable(ViewKeyAdvCertsFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyAdvCertsFragment.class,
certsBundle, getString(R.string.key_view_tab_certs));
// update layout after operations
mSlidingTabLayout.setViewPager(mViewPager);
}
// These are the rows that we will retrieve.
static final String[] PROJECTION = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.EXPIRY,
KeychainContract.KeyRings.VERIFIED,
KeychainContract.KeyRings.HAS_ANY_SECRET
};
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_USER_ID = 2;
static final int INDEX_IS_REVOKED = 3;
static final int INDEX_EXPIRY = 4;
static final int INDEX_VERIFIED = 5;
static final int INDEX_HAS_ANY_SECRET = 6;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(this, baseUri, PROJECTION, null, null, null);
}
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
// get name, email, and comment from USER_ID
String[] mainUserId = KeyRing.splitUserId(data.getString(INDEX_USER_ID));
if (mainUserId[0] != null) {
setTitle(mainUserId[0]);
} else {
setTitle(R.string.user_id_no_name);
}
// get key id from MASTER_KEY_ID
long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
getSupportActionBar().setSubtitle(KeyFormattingUtils.beautifyKeyIdWithPrefix(this, masterKeyId));
boolean isSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
boolean isExpired = !data.isNull(INDEX_EXPIRY)
&& new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date());
boolean isVerified = data.getInt(INDEX_VERIFIED) > 0;
// Note: order is important
int color;
if (isRevoked || isExpired) {
color = getResources().getColor(R.color.android_red_light);
} else if (isSecret) {
color = getResources().getColor(R.color.primary);
} else {
if (isVerified) {
color = getResources().getColor(R.color.primary);
} else {
color = getResources().getColor(R.color.android_orange_light);
}
}
mToolbar.setBackgroundColor(color);
mStatusBar.setBackgroundColor(color);
mSlidingTabLayout.setBackgroundColor(color);
break;
}
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
// if a result has been returned, display a notify
if (data != null && data.hasExtra(OperationResult.EXTRA_RESULT)) {
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
result.createNotify(this).show();
} else {
super.onActivityResult(requestCode, resultCode, data);
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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
@@ -30,7 +30,6 @@ import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
@@ -39,7 +38,6 @@ import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.WrappedSignature;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
@@ -47,23 +45,16 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
public class ViewKeyAdvancedFragment extends LoaderFragment implements
public class ViewKeyAdvCertsFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
public static final String ARG_DATA_URI = "data_uri";
private ListView mSubkeysList;
private SubkeysAdapter mSubkeysAdapter;
private StickyListHeadersListView mStickyList;
private CertListAdapter mCertsAdapter;
private Uri mDataUriSubkeys;
private Uri mDataUriCerts;
private static final int LOADER_SUBKEYS = 1;
private static final int LOADER_CERTS = 2;
// These are the rows that we will retrieve.
static final String[] CERTS_PROJECTION = new String[]{
KeychainContract.Certs._ID,
@@ -86,8 +77,8 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
/**
* Creates new instance of this fragment
*/
public static ViewKeyAdvancedFragment newInstance(Uri dataUri) {
ViewKeyAdvancedFragment frag = new ViewKeyAdvancedFragment();
public static ViewKeyAdvCertsFragment newInstance(Uri dataUri) {
ViewKeyAdvCertsFragment frag = new ViewKeyAdvCertsFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
@@ -99,9 +90,8 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_advanced_fragment, getContainer());
View view = inflater.inflate(R.layout.view_key_adv_certs_fragment, getContainer());
mSubkeysList = (ListView) view.findViewById(R.id.keys);
mStickyList = (StickyListHeadersListView) view.findViewById(R.id.certs_list);
return root;
@@ -122,7 +112,6 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
}
private void loadData(Uri dataUri) {
mDataUriSubkeys = KeychainContract.Keys.buildKeysUri(dataUri);
mDataUriCerts = KeychainContract.Certs.buildCertsUri(dataUri);
mStickyList.setAreHeadersSticky(true);
@@ -132,34 +121,23 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
mStickyList.setEmptyView(getActivity().findViewById(R.id.empty));
// Create an empty adapter we will use to display the loaded data.
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0);
mSubkeysList.setAdapter(mSubkeysAdapter);
mCertsAdapter = new CertListAdapter(getActivity(), null);
mStickyList.setAdapter(mCertsAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_SUBKEYS, null, this);
getLoaderManager().initLoader(LOADER_CERTS, null, this);
getLoaderManager().initLoader(0, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
case LOADER_SUBKEYS:
return new CursorLoader(getActivity(), mDataUriSubkeys,
SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null);
case LOADER_CERTS:
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), mDataUriCerts,
CERTS_PROJECTION, null, null, CERTS_SORT_ORDER);
default:
return null;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), mDataUriCerts,
CERTS_PROJECTION, null, null, CERTS_SORT_ORDER);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
@@ -170,15 +148,8 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_SUBKEYS:
mSubkeysAdapter.swapCursor(data);
break;
case LOADER_CERTS:
mCertsAdapter.swapCursor(data);
mStickyList.setAdapter(mCertsAdapter);
break;
}
mCertsAdapter.swapCursor(data);
mStickyList.setAdapter(mCertsAdapter);
// TODO: maybe show not before both are loaded!
setContentShown(true);
@@ -189,14 +160,7 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
* We need to make sure we are no longer using it.
*/
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case LOADER_SUBKEYS:
mSubkeysAdapter.swapCursor(null);
break;
case LOADER_CERTS:
mCertsAdapter.swapCursor(null);
break;
}
mCertsAdapter.swapCursor(null);
}
/**
@@ -307,7 +271,7 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
return mInflater.inflate(R.layout.view_key_certs_item, parent, false);
return mInflater.inflate(R.layout.view_key_adv_certs_item, parent, false);
}
/**
@@ -322,7 +286,7 @@ public class ViewKeyAdvancedFragment extends LoaderFragment implements
HeaderViewHolder holder;
if (convertView == null) {
holder = new HeaderViewHolder();
convertView = mInflater.inflate(R.layout.view_key_certs_header, parent, false);
convertView = mInflater.inflate(R.layout.view_key_adv_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);

View File

@@ -50,7 +50,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
public class ViewKeyMainFragment extends LoaderFragment implements
public class ViewKeyAdvMainFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
@@ -80,7 +80,7 @@ public class ViewKeyMainFragment extends LoaderFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_main_fragment, getContainer());
View view = inflater.inflate(R.layout.view_key_adv_main_fragment, getContainer());
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mActionEdit = view.findViewById(R.id.view_key_action_edit);
@@ -265,9 +265,10 @@ public class ViewKeyMainFragment extends LoaderFragment implements
}
}
case LOADER_ID_USER_IDS:
case LOADER_ID_USER_IDS: {
mUserIdsAdapter.swapCursor(data);
break;
}
}
setContentShown(true);

View File

@@ -56,7 +56,7 @@ import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
public class ViewKeyShareFragment extends LoaderFragment implements
public class ViewKeyAdvShareFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
@@ -81,9 +81,9 @@ public class ViewKeyShareFragment extends LoaderFragment implements
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_share_fragment, getContainer());
View view = inflater.inflate(R.layout.view_key_adv_share_fragment, getContainer());
mProviderHelper = new ProviderHelper(ViewKeyShareFragment.this.getActivity());
mProviderHelper = new ProviderHelper(ViewKeyAdvShareFragment.this.getActivity());
mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image);
@@ -358,7 +358,7 @@ public class ViewKeyShareFragment extends LoaderFragment implements
protected void onPostExecute(Bitmap qrCode) {
// only change view, if fragment is attached to activity
if (ViewKeyShareFragment.this.isAdded()) {
if (ViewKeyAdvShareFragment.this.isAdded()) {
// scale the image up to our actual size. we do this in code rather
// than let the ImageView do this because we don't require filtering.

View File

@@ -0,0 +1,125 @@
/*
* Copyright (C) 2014-2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;
import org.sufficientlysecure.keychain.util.Log;
public class ViewKeyAdvSubkeysFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "data_uri";
private ListView mSubkeysList;
private SubkeysAdapter mSubkeysAdapter;
private Uri mDataUriSubkeys;
/**
* Creates new instance of this fragment
*/
public static ViewKeyAdvSubkeysFragment newInstance(Uri dataUri) {
ViewKeyAdvSubkeysFragment frag = new ViewKeyAdvSubkeysFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_adv_subkeys_fragment, getContainer());
mSubkeysList = (ListView) view.findViewById(R.id.keys);
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
loadData(dataUri);
}
private void loadData(Uri dataUri) {
mDataUriSubkeys = KeychainContract.Keys.buildKeysUri(dataUri);
// Create an empty adapter we will use to display the loaded data.
mSubkeysAdapter = new SubkeysAdapter(getActivity(), null, 0);
mSubkeysList.setAdapter(mSubkeysAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(0, null, this);
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
return new CursorLoader(getActivity(), mDataUriSubkeys,
SubkeysAdapter.SUBKEYS_PROJECTION, null, null, null);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Avoid NullPointerExceptions, if we get an empty result set.
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mSubkeysAdapter.swapCursor(data);
// TODO: maybe show not before both are loaded!
setContentShown(true);
}
/**
* 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.
*/
public void onLoaderReset(Loader<Cursor> loader) {
mSubkeysAdapter.swapCursor(null);
}
}

View File

@@ -0,0 +1,232 @@
/*
* Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ListView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.UserIdInfoDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
public class ViewKeyFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private ListView mUserIds;
boolean mIsSecret = false;
private static final int LOADER_ID_UNIFIED = 0;
private static final int LOADER_ID_USER_IDS = 1;
private UserIdsAdapter mUserIdsAdapter;
private Uri mDataUri;
ProviderHelper mProviderHelper;
/**
* Creates new instance of this fragment
*/
public static ViewKeyFragment newInstance(Uri dataUri) {
ViewKeyFragment frag = new ViewKeyFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_DATA_URI, dataUri);
frag.setArguments(args);
return frag;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_fragment, getContainer());
mProviderHelper = new ProviderHelper(getActivity());
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mUserIds.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
showUserIdInfo(position);
}
});
return root;
}
private void showUserIdInfo(final int position) {
if (!mIsSecret) {
final boolean isRevoked = mUserIdsAdapter.getIsRevoked(position);
final int isVerified = mUserIdsAdapter.getIsVerified(position);
DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() {
public void run() {
UserIdInfoDialogFragment dialogFragment =
UserIdInfoDialogFragment.newInstance(isRevoked, isVerified);
dialogFragment.show(getActivity().getSupportFragmentManager(), "userIdInfoDialog");
}
});
}
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
loadData(dataUri);
}
// These are the rows that we will retrieve.
static final String[] UNIFIED_PROJECTION = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.EXPIRY,
KeychainContract.KeyRings.VERIFIED,
KeychainContract.KeyRings.HAS_ANY_SECRET,
KeychainContract.KeyRings.FINGERPRINT,
KeychainContract.KeyRings.HAS_ENCRYPT
};
static final int INDEX_MASTER_KEY_ID = 1;
static final int INDEX_USER_ID = 2;
static final int INDEX_IS_REVOKED = 3;
static final int INDEX_EXPIRY = 4;
static final int INDEX_VERIFIED = 5;
static final int INDEX_HAS_ANY_SECRET = 6;
static final int INDEX_FINGERPRINT = 7;
static final int INDEX_HAS_ENCRYPT = 8;
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
}
// don't show revoked user ids here, irrelevant for average users
public static final String USER_IDS_WHERE = UserPackets.IS_REVOKED + " = 0";
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
}
case LOADER_ID_USER_IDS: {
Uri baseUri = UserPackets.buildUserIdsUri(mDataUri);
return new CursorLoader(getActivity(), baseUri,
UserIdsAdapter.USER_IDS_PROJECTION, USER_IDS_WHERE, null, null);
}
default:
return null;
}
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
mIsSecret = data.getInt(INDEX_HAS_ANY_SECRET) != 0;
boolean hasEncrypt = data.getInt(INDEX_HAS_ENCRYPT) != 0;
boolean isRevoked = data.getInt(INDEX_IS_REVOKED) > 0;
boolean isExpired = !data.isNull(INDEX_EXPIRY)
&& new Date(data.getLong(INDEX_EXPIRY) * 1000).before(new Date());
boolean isVerified = data.getInt(INDEX_VERIFIED) > 0;
// load user ids after we know if it's a secret key
mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, false, !mIsSecret, null);
mUserIds.setAdapter(mUserIdsAdapter);
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
break;
}
}
case LOADER_ID_USER_IDS: {
mUserIdsAdapter.swapCursor(data);
break;
}
}
setContentShown(true);
}
/**
* 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.
*/
public void onLoaderReset(Loader<Cursor> loader) {
switch (loader.getId()) {
case LOADER_ID_USER_IDS: {
mUserIdsAdapter.swapCursor(null);
break;
}
}
}
}

View File

@@ -175,9 +175,9 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
}
if (entry.isRevoked()) {
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_REVOKED, true);
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
} else if (entry.isExpired()) {
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_EXPIRED, true);
KeyFormattingUtils.setStatusImage(getContext(), holder.status, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray);
}
if (entry.isRevoked() || entry.isExpired()) {

View File

@@ -133,11 +133,11 @@ abstract public class SelectKeyCursorAdapter extends CursorAdapter {
boolean enabled;
if (cursor.getInt(mIndexIsRevoked) != 0) {
h.statusIcon.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_REVOKED, true);
KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
enabled = false;
} else if (cursor.getInt(mIndexIsExpiry) != 0) {
h.statusIcon.setVisibility(View.VISIBLE);
KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_EXPIRED, true);
KeyFormattingUtils.setStatusImage(mContext, h.statusIcon, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray);
enabled = false;
} else {
h.statusIcon.setVisibility(View.GONE);

View File

@@ -272,12 +272,12 @@ public class SubkeysAdapter extends CursorAdapter {
PorterDuff.Mode.SRC_IN);
if (isRevoked) {
vStatus.setImageResource(R.drawable.status_signature_revoked_cutout);
vStatus.setImageResource(R.drawable.status_signature_revoked_cutout_24px);
vStatus.setColorFilter(
mContext.getResources().getColor(R.color.bg_gray),
PorterDuff.Mode.SRC_IN);
} else if (isExpired) {
vStatus.setImageResource(R.drawable.status_signature_expired_cutout);
vStatus.setImageResource(R.drawable.status_signature_expired_cutout_24px);
vStatus.setColorFilter(
mContext.getResources().getColor(R.color.bg_gray),
PorterDuff.Mode.SRC_IN);
@@ -301,7 +301,7 @@ public class SubkeysAdapter extends CursorAdapter {
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.view_key_subkey_item, null);
View view = mInflater.inflate(R.layout.view_key_adv_subkey_item, null);
if (mDefaultTextColor == null) {
TextView keyId = (TextView) view.findViewById(R.id.subkey_item_key_id);
mDefaultTextColor = keyId.getTextColors();

View File

@@ -68,7 +68,7 @@ public class SubkeysAddedAdapter extends ArrayAdapter<SaveKeyringParcel.SubkeyAd
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.view_key_subkey_item, null);
convertView = mInflater.inflate(R.layout.view_key_adv_subkey_item, null);
final ViewHolder holder = new ViewHolder();
holder.vKeyId = (TextView) convertView.findViewById(R.id.subkey_item_key_id);
holder.vKeyDetails = (TextView) convertView.findViewById(R.id.subkey_item_details);

View File

@@ -43,6 +43,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
private LayoutInflater mInflater;
private final ArrayList<Boolean> mCheckStates;
private SaveKeyringParcel mSaveKeyringParcel;
private boolean mShowStatusImages;
public static final String[] USER_IDS_PROJECTION = new String[]{
UserPackets._ID,
@@ -60,24 +61,30 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
private static final int INDEX_IS_REVOKED = 5;
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes,
SaveKeyringParcel saveKeyringParcel) {
boolean showStatusImages, SaveKeyringParcel saveKeyringParcel) {
super(context, c, flags);
mInflater = LayoutInflater.from(context);
mCheckStates = showCheckBoxes ? new ArrayList<Boolean>() : null;
mSaveKeyringParcel = saveKeyringParcel;
mShowStatusImages = showStatusImages;
}
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes,
SaveKeyringParcel saveKeyringParcel) {
this(context, c, flags, showCheckBoxes, false, saveKeyringParcel);
}
public UserIdsAdapter(Context context, Cursor c, int flags, boolean showCheckBoxes) {
this(context, c, flags, showCheckBoxes, null);
this(context, c, flags, showCheckBoxes, false, null);
}
public UserIdsAdapter(Context context, Cursor c, int flags, SaveKeyringParcel saveKeyringParcel) {
this(context, c, flags, false, saveKeyringParcel);
this(context, c, flags, false, false, saveKeyringParcel);
}
public UserIdsAdapter(Context context, Cursor c, int flags) {
this(context, c, flags, false, null);
this(context, c, flags, false, false, null);
}
@Override
@@ -157,12 +164,17 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
vVerifiedLayout.setVisibility(View.GONE);
} else {
vEditImage.setVisibility(View.GONE);
vVerifiedLayout.setVisibility(View.VISIBLE);
if (mShowStatusImages) {
vVerifiedLayout.setVisibility(View.VISIBLE);
} else {
vVerifiedLayout.setVisibility(View.GONE);
}
}
if (isRevoked) {
// set revocation icon (can this even be primary?)
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, true);
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
// disable revoked user ids
vName.setEnabled(false);
@@ -184,13 +196,13 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
int isVerified = cursor.getInt(INDEX_VERIFIED);
switch (isVerified) {
case Certs.VERIFIED_SECRET:
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, false);
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_VERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
break;
case Certs.VERIFIED_SELF:
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, false);
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_UNVERIFIED, KeyFormattingUtils.DEFAULT_COLOR);
break;
default:
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, false);
KeyFormattingUtils.setStatusImage(mContext, vVerified, null, KeyFormattingUtils.STATE_INVALID, KeyFormattingUtils.DEFAULT_COLOR);
break;
}
}
@@ -263,7 +275,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC
@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
View view = mInflater.inflate(R.layout.view_key_user_id_item, null);
View view = mInflater.inflate(R.layout.view_key_adv_user_id_item, null);
// only need to do this once ever, since mShowCheckBoxes is final
view.findViewById(R.id.user_id_item_check_box).setVisibility(mCheckStates != null ? View.VISIBLE : View.GONE);
return view;

View File

@@ -64,7 +64,7 @@ public class UserIdsAddedAdapter extends ArrayAdapter<String> {
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// Not recycled, inflate a new view
convertView = mInflater.inflate(R.layout.view_key_user_id_item, null);
convertView = mInflater.inflate(R.layout.view_key_adv_user_id_item, null);
final ViewHolder holder = new ViewHolder();
holder.vAddress = (TextView) convertView.findViewById(R.id.user_id_item_address);
holder.vName = (TextView) convertView.findViewById(R.id.user_id_item_name);

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentActivity;
import org.sufficientlysecure.keychain.R;
public class AdvancedAppSettingsDialogFragment extends DialogFragment {
private static final String ARG_PACKAGE_NAME = "package_name";
private static final String ARG_SIGNATURE = "signature";
/**
* Creates new instance of this fragment
*/
public static AdvancedAppSettingsDialogFragment newInstance(String packageName, String digest) {
AdvancedAppSettingsDialogFragment frag = new AdvancedAppSettingsDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_PACKAGE_NAME, packageName);
args.putString(ARG_SIGNATURE, digest);
frag.setArguments(args);
return frag;
}
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(R.string.api_settings_advanced);
alert.setCancelable(true);
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
}
});
String packageName = getArguments().getString(ARG_PACKAGE_NAME);
String signature = getArguments().getString(ARG_SIGNATURE);
alert.setMessage(getString(R.string.api_settings_package_name) + ": " + packageName + "\n\n"
+ getString(R.string.api_settings_package_signature) + ": " + signature);
return alert.show();
}
}

View File

@@ -26,13 +26,13 @@ public class CustomAlertDialogBuilder extends AlertDialog.Builder {
int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null);
View divider = dialog.findViewById(dividerId);
if (divider != null) {
divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.emphasis));
divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.header_text));
}
int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null);
TextView tv = (TextView) dialog.findViewById(textViewId);
if (tv != null) {
tv.setTextColor(dialog.getContext().getResources().getColor(R.color.emphasis));
tv.setTextColor(dialog.getContext().getResources().getColor(R.color.header_text));
}
return dialog;

View File

@@ -24,18 +24,12 @@ import android.text.style.StrikethroughSpan;
public class FormattingUtils {
public static SpannableStringBuilder strikeOutText(CharSequence text) {
SpannableStringBuilder sb = new SpannableStringBuilder(text);
sb.setSpan(new StrikethroughSpan(), 0, text.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE);
return sb;
}
public static int dpToPx(Context context, int dp) {
return (int) ((dp * context.getResources().getDisplayMetrics().density) + 0.5);
return (int) ((dp * context.getResources().getDisplayMetrics().density) + 0.5f);
}
public static int pxToDp(Context context, int px) {
return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5);
return (int) ((px / context.getResources().getDisplayMetrics().density) + 0.5f);
}
}

View File

@@ -377,6 +377,8 @@ public class KeyFormattingUtils {
((int) digest[2] + 256) % 256};
}
public static final int DEFAULT_COLOR = -1;
public static final int STATE_REVOKED = 1;
public static final int STATE_EXPIRED = 2;
public static final int STATE_VERIFIED = 3;
@@ -393,20 +395,32 @@ public class KeyFormattingUtils {
}
public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText, int state) {
setStatusImage(context, statusIcon, statusText, state, false);
setStatusImage(context, statusIcon, statusText, state, KeyFormattingUtils.DEFAULT_COLOR, false);
}
public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText,
int state, int color) {
setStatusImage(context, statusIcon, statusText, state, color, false);
}
/**
* Sets status image based on constant
*/
public static void setStatusImage(Context context, ImageView statusIcon, TextView statusText,
int state, boolean unobtrusive) {
int state, int color, boolean big) {
switch (state) {
/** GREEN: everything is good **/
case STATE_VERIFIED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_verified_cutout));
int color = R.color.android_green_light;
if (big) {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_96px));
} else {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_verified_cutout_24px));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_green_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -416,8 +430,10 @@ public class KeyFormattingUtils {
}
case STATE_ENCRYPTED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_lock_closed));
int color = R.color.android_green_light;
context.getResources().getDrawable(R.drawable.status_lock_closed_24px));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_green_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -427,9 +443,16 @@ public class KeyFormattingUtils {
}
/** ORANGE: mostly bad... **/
case STATE_UNVERIFIED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout));
int color = R.color.android_orange_light;
if (big) {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_96px));
} else {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unverified_cutout_24px));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_orange_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -439,8 +462,10 @@ public class KeyFormattingUtils {
}
case STATE_UNKNOWN_KEY: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout));
int color = R.color.android_orange_light;
context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24px));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_orange_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -450,11 +475,15 @@ public class KeyFormattingUtils {
}
/** RED: really bad... **/
case STATE_REVOKED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout));
int color = R.color.android_red_light;
if (unobtrusive) {
color = R.color.bg_gray;
if (big) {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_96px));
} else {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_revoked_cutout_24px));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_red_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -464,11 +493,15 @@ public class KeyFormattingUtils {
break;
}
case STATE_EXPIRED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_expired_cutout));
int color = R.color.android_red_light;
if (unobtrusive) {
color = R.color.bg_gray;
if (big) {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_96px));
} else {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_expired_cutout_24px));
}
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_red_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
@@ -479,8 +512,10 @@ public class KeyFormattingUtils {
}
case STATE_NOT_ENCRYPTED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_lock_open));
int color = R.color.android_red_light;
context.getResources().getDrawable(R.drawable.status_lock_open_24px));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_red_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -490,8 +525,10 @@ public class KeyFormattingUtils {
}
case STATE_NOT_SIGNED: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout));
int color = R.color.android_red_light;
context.getResources().getDrawable(R.drawable.status_signature_unknown_cutout_24px));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_red_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -501,8 +538,10 @@ public class KeyFormattingUtils {
}
case STATE_INVALID: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout));
int color = R.color.android_red_light;
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24px));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.android_red_light;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {
@@ -513,8 +552,10 @@ public class KeyFormattingUtils {
/** special **/
case STATE_UNAVAILABLE: {
statusIcon.setImageDrawable(
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout));
int color = R.color.bg_gray;
context.getResources().getDrawable(R.drawable.status_signature_invalid_cutout_24px));
if (color == KeyFormattingUtils.DEFAULT_COLOR) {
color = R.color.bg_gray;
}
statusIcon.setColorFilter(context.getResources().getColor(color),
PorterDuff.Mode.SRC_IN);
if (statusText != null) {

View File

@@ -26,6 +26,8 @@ import com.nispok.snackbar.Snackbar.SnackbarDuration;
import com.nispok.snackbar.SnackbarManager;
import com.nispok.snackbar.listeners.ActionClickListener;
import org.sufficientlysecure.keychain.R;
/**
* Notify wrapper which allows a more easy use of different notification libraries
*/
@@ -52,10 +54,10 @@ public class Notify {
case OK:
break;
case WARN:
bar.textColor(Color.YELLOW);
bar.textColor(activity.getResources().getColor(R.color.android_orange_light));
break;
case ERROR:
bar.textColor(Color.RED);
bar.textColor(activity.getResources().getColor(R.color.android_red_light));
break;
}
@@ -74,13 +76,13 @@ public class Notify {
switch (style) {
case OK:
bar.actionColor(Color.GREEN);
bar.actionColor(activity.getResources().getColor(R.color.android_green_light));
break;
case WARN:
bar.textColor(Color.YELLOW);
bar.textColor(activity.getResources().getColor(R.color.android_orange_light));
break;
case ERROR:
bar.textColor(Color.RED);
bar.textColor(activity.getResources().getColor(R.color.android_red_light));
break;
}
@@ -116,13 +118,13 @@ public class Notify {
switch (style) {
case OK:
bar.actionColor(Color.GREEN);
bar.actionColor(activity.getResources().getColor(R.color.android_green_light));
break;
case WARN:
bar.textColor(Color.YELLOW);
bar.textColor(activity.getResources().getColor(R.color.android_orange_light));
break;
case ERROR:
bar.textColor(Color.RED);
bar.textColor(activity.getResources().getColor(R.color.android_red_light));
break;
}

View File

@@ -59,7 +59,7 @@ public class QrCodeUtils {
for (int y = 0; y < height; y++) {
final int offset = y * width;
for (int x = 0; x < width; x++) {
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
}
}

View File

@@ -0,0 +1,138 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.ImageView;
import org.sufficientlysecure.keychain.R;
/**
* Maintains an aspect ratio based on either width or height. Disabled by default.
*
* from https://gist.github.com/JakeWharton/2856179
*/
public class AspectRatioImageView extends ImageView {
// NOTE: These must be kept in sync with the AspectRatioImageView attributes in attrs.xml.
public static final int MEASUREMENT_WIDTH = 0;
public static final int MEASUREMENT_HEIGHT = 1;
private static final float DEFAULT_ASPECT_RATIO = 1f;
private static final boolean DEFAULT_ASPECT_RATIO_ENABLED = false;
private static final int DEFAULT_DOMINANT_MEASUREMENT = MEASUREMENT_WIDTH;
private float aspectRatio;
private boolean aspectRatioEnabled;
private int dominantMeasurement;
public AspectRatioImageView(Context context) {
this(context, null);
}
public AspectRatioImageView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AspectRatioImageView);
aspectRatio = a.getFloat(R.styleable.AspectRatioImageView_aspectRatio, DEFAULT_ASPECT_RATIO);
aspectRatioEnabled = a.getBoolean(R.styleable.AspectRatioImageView_aspectRatioEnabled,
DEFAULT_ASPECT_RATIO_ENABLED);
dominantMeasurement = a.getInt(R.styleable.AspectRatioImageView_dominantMeasurement,
DEFAULT_DOMINANT_MEASUREMENT);
a.recycle();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!aspectRatioEnabled) return;
int newWidth;
int newHeight;
switch (dominantMeasurement) {
case MEASUREMENT_WIDTH:
newWidth = getMeasuredWidth();
newHeight = (int) (newWidth * aspectRatio);
break;
case MEASUREMENT_HEIGHT:
newHeight = getMeasuredHeight();
newWidth = (int) (newHeight * aspectRatio);
break;
default:
throw new IllegalStateException("Unknown measurement with ID " + dominantMeasurement);
}
setMeasuredDimension(newWidth, newHeight);
}
/**
* Get the aspect ratio for this image view.
*/
public float getAspectRatio() {
return aspectRatio;
}
/**
* Set the aspect ratio for this image view. This will update the view instantly.
*/
public void setAspectRatio(float aspectRatio) {
this.aspectRatio = aspectRatio;
if (aspectRatioEnabled) {
requestLayout();
}
}
/**
* Get whether or not forcing the aspect ratio is enabled.
*/
public boolean getAspectRatioEnabled() {
return aspectRatioEnabled;
}
/**
* set whether or not forcing the aspect ratio is enabled. This will re-layout the view.
*/
public void setAspectRatioEnabled(boolean aspectRatioEnabled) {
this.aspectRatioEnabled = aspectRatioEnabled;
requestLayout();
}
/**
* Get the dominant measurement for the aspect ratio.
*/
public int getDominantMeasurement() {
return dominantMeasurement;
}
/**
* Set the dominant measurement for the aspect ratio.
*
* @see #MEASUREMENT_WIDTH
* @see #MEASUREMENT_HEIGHT
*/
public void setDominantMeasurement(int dominantMeasurement) {
if (dominantMeasurement != MEASUREMENT_HEIGHT && dominantMeasurement != MEASUREMENT_WIDTH) {
throw new IllegalArgumentException("Invalid measurement type.");
}
this.dominantMeasurement = dominantMeasurement;
requestLayout();
}
}

View File

@@ -27,6 +27,7 @@ import android.util.AttributeSet;
import android.widget.ImageView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainDatabase;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -85,32 +86,33 @@ public class CertifyKeySpinner extends KeySpinner {
super.onLoadFinished(loader, data);
if (loader.getId() == LOADER_ID) {
// If there is only one choice, pick it by default
if (mAdapter.getCount() == 2) {
// preselect if key can certify
if (data.moveToPosition(1) && !data.isNull(mIndexHasCertify)) {
setSelection(1);
}
}
mIndexHasCertify = data.getColumnIndex(KeychainContract.KeyRings.HAS_CERTIFY);
mIndexIsRevoked = data.getColumnIndex(KeychainContract.KeyRings.IS_REVOKED);
mIndexIsExpired = data.getColumnIndex(KeychainContract.KeyRings.IS_EXPIRED);
// If there is only one choice, pick it by default
if (mAdapter.getCount() == 2) {
// preselect if key can certify
if (data.moveToPosition(0) && !data.isNull(mIndexHasCertify)) {
setSelection(1);
}
}
}
}
@Override
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
if (cursor.getInt(mIndexIsRevoked) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, true);
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
return false;
}
if (cursor.getInt(mIndexIsExpired) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, true);
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray);
return false;
}
// don't invalidate the "None" entry, which is also null!
if (cursor.getPosition() != 0 && cursor.isNull(mIndexHasCertify)) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, true);
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, R.color.bg_gray);
return false;
}

View File

@@ -24,13 +24,13 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.internal.widget.TintSpinner;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;
@@ -41,7 +41,11 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.Log;
public abstract class KeySpinner extends Spinner implements LoaderManager.LoaderCallbacks<Cursor> {
/**
* Use TintSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Related: http://stackoverflow.com/a/27713090
*/
public abstract class KeySpinner extends TintSpinner implements LoaderManager.LoaderCallbacks<Cursor> {
public interface OnKeyChangedListener {
public void onKeyChanged(long masterKeyId);
}

View File

@@ -26,6 +26,7 @@ import android.support.v4.content.Loader;
import android.util.AttributeSet;
import android.widget.ImageView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -83,15 +84,15 @@ public class SignKeySpinner extends KeySpinner {
@Override
boolean setStatus(Context context, Cursor cursor, ImageView statusView) {
if (cursor.getInt(mIndexIsRevoked) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, true);
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_REVOKED, R.color.bg_gray);
return false;
}
if (cursor.getInt(mIndexIsExpired) != 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, true);
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_EXPIRED, R.color.bg_gray);
return false;
}
if (cursor.getInt(mIndexHasSign) == 0) {
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, true);
KeyFormattingUtils.setStatusImage(getContext(), statusView, null, KeyFormattingUtils.STATE_UNAVAILABLE, R.color.bg_gray);
return false;
}

View File

@@ -172,7 +172,7 @@ public class Preferences {
}
public boolean useDefaultYubikeyPin() {
return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, true);
return mSharedPreferences.getBoolean(Pref.USE_DEFAULT_YUBIKEY_PIN, false);
}
public void setUseDefaultYubikeyPin(boolean useDefaultYubikeyPin) {