use SQLDelight, remove ApiApps access from KeychainProvider
This commit is contained in:
@@ -138,7 +138,7 @@ public class ApiPendingIntentFactory {
|
||||
|
||||
PendingIntent createSelectSignKeyIdLegacyPendingIntent(Intent data, String packageName, String preferredUserId) {
|
||||
Intent intent = new Intent(mContext, SelectSignKeyIdActivity.class);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
|
||||
intent.putExtra(SelectSignKeyIdActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(SelectSignKeyIdActivity.EXTRA_USER_ID, preferredUserId);
|
||||
|
||||
return createInternal(data, intent);
|
||||
@@ -147,7 +147,6 @@ public class ApiPendingIntentFactory {
|
||||
PendingIntent createSelectSignKeyIdPendingIntent(Intent data, String packageName,
|
||||
byte[] packageSignature, String preferredUserId, boolean showAutocryptHint) {
|
||||
Intent intent = new Intent(mContext, RemoteSelectIdKeyActivity.class);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
|
||||
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_PACKAGE_SIGNATURE, packageSignature);
|
||||
intent.putExtra(RemoteSelectIdKeyActivity.EXTRA_USER_ID, preferredUserId);
|
||||
@@ -158,7 +157,6 @@ public class ApiPendingIntentFactory {
|
||||
|
||||
PendingIntent createSelectAuthenticationKeyIdPendingIntent(Intent data, String packageName) {
|
||||
Intent intent = new Intent(mContext, RemoteSelectAuthenticationKeyActivity.class);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(packageName));
|
||||
intent.putExtra(RemoteSelectAuthenticationKeyActivity.EXTRA_PACKAGE_NAME, packageName);
|
||||
|
||||
return createInternal(data, intent);
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.remote;
|
||||
|
||||
public class AppSettings {
|
||||
private String mPackageName;
|
||||
private byte[] mPackageCertificate;
|
||||
|
||||
public AppSettings() {
|
||||
|
||||
}
|
||||
|
||||
public AppSettings(String packageName, byte[] packageSignature) {
|
||||
super();
|
||||
this.mPackageName = packageName;
|
||||
this.mPackageCertificate = packageSignature;
|
||||
}
|
||||
|
||||
public String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
public void setPackageName(String packageName) {
|
||||
this.mPackageName = packageName;
|
||||
}
|
||||
|
||||
public byte[] getPackageCertificate() {
|
||||
return mPackageCertificate;
|
||||
}
|
||||
|
||||
public void setPackageCertificate(byte[] packageCertificate) {
|
||||
this.mPackageCertificate = packageCertificate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import android.arch.persistence.db.SupportSQLiteDatabase;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
@@ -42,7 +43,6 @@ import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptRecommendationResult;
|
||||
import org.sufficientlysecure.keychain.provider.AutocryptPeerDataAccessObject.AutocryptState;
|
||||
import org.sufficientlysecure.keychain.provider.DatabaseNotifyManager;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Certs;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
|
||||
@@ -54,7 +54,6 @@ import org.sufficientlysecure.keychain.provider.KeychainExternalContract.Autocry
|
||||
import org.sufficientlysecure.keychain.provider.KeychainExternalContract.EmailStatus;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainProvider;
|
||||
import org.sufficientlysecure.keychain.provider.SimpleContentResolverInterface;
|
||||
import org.sufficientlysecure.keychain.util.CloseDatabaseCursorFactory;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -64,9 +63,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
private static final int AUTOCRYPT_STATUS = 201;
|
||||
private static final int AUTOCRYPT_STATUS_INTERNAL = 202;
|
||||
|
||||
private static final int API_APPS = 301;
|
||||
private static final int API_APPS_BY_PACKAGE_NAME = 302;
|
||||
|
||||
public static final String TEMP_TABLE_QUERIED_ADDRESSES = "queried_addresses";
|
||||
public static final String TEMP_TABLE_COLUMN_ADDRES = "address";
|
||||
|
||||
@@ -113,7 +109,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
|
||||
internalKeychainProvider = new KeychainProvider();
|
||||
internalKeychainProvider.attachInfo(context, null);
|
||||
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(internalKeychainProvider));
|
||||
apiPermissionHelper = new ApiPermissionHelper(context, new ApiDataAccessObject(getContext()));
|
||||
databaseNotifyManager = DatabaseNotifyManager.create(context);
|
||||
return true;
|
||||
}
|
||||
@@ -127,13 +123,6 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
switch (match) {
|
||||
case EMAIL_STATUS:
|
||||
return EmailStatus.CONTENT_TYPE;
|
||||
|
||||
case API_APPS:
|
||||
return ApiApps.CONTENT_TYPE;
|
||||
|
||||
case API_APPS_BY_PACKAGE_NAME:
|
||||
return ApiApps.CONTENT_ITEM_TYPE;
|
||||
|
||||
default:
|
||||
throw new UnsupportedOperationException("Unknown uri: " + uri);
|
||||
}
|
||||
@@ -154,7 +143,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
|
||||
String groupBy = null;
|
||||
|
||||
SQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
|
||||
SupportSQLiteDatabase db = new KeychainDatabase(getContext()).getReadableDatabase();
|
||||
|
||||
String callingPackageName = apiPermissionHelper.getCurrentCallingPackage();
|
||||
|
||||
@@ -169,7 +158,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
ContentValues cv = new ContentValues();
|
||||
for (String address : selectionArgs) {
|
||||
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
|
||||
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv);
|
||||
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
|
||||
}
|
||||
|
||||
HashMap<String, String> projectionMap = new HashMap<>();
|
||||
@@ -256,7 +245,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
ContentValues cv = new ContentValues();
|
||||
for (String address : selectionArgs) {
|
||||
cv.put(TEMP_TABLE_COLUMN_ADDRES, address);
|
||||
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, null, cv);
|
||||
db.insert(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_FAIL, cv);
|
||||
}
|
||||
|
||||
boolean isWildcardSelector = selectionArgs.length == 1 && selectionArgs[0].contains("%");
|
||||
@@ -321,8 +310,8 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
}
|
||||
|
||||
qb.setStrict(true);
|
||||
qb.setCursorFactory(new CloseDatabaseCursorFactory());
|
||||
Cursor cursor = qb.query(db, projection, null, null, groupBy, null, orderBy);
|
||||
String query = qb.buildQuery(projection, null, null, groupBy, null, orderBy);
|
||||
Cursor cursor = db.query(query);
|
||||
if (cursor != null) {
|
||||
// Tell the cursor what uri to watch, so it knows when its source data changes
|
||||
cursor.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
@@ -337,7 +326,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
return cursor;
|
||||
}
|
||||
|
||||
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
|
||||
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
|
||||
AutocryptPeerDataAccessObject autocryptPeerDao, String[] peerIds) {
|
||||
List<AutocryptRecommendationResult> autocryptStates =
|
||||
autocryptPeerDao.determineAutocryptRecommendations(peerIds);
|
||||
@@ -345,7 +334,7 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
fillTempTableWithAutocryptRecommendations(db, autocryptStates);
|
||||
}
|
||||
|
||||
private void fillTempTableWithAutocryptRecommendations(SQLiteDatabase db,
|
||||
private void fillTempTableWithAutocryptRecommendations(SupportSQLiteDatabase db,
|
||||
List<AutocryptRecommendationResult> autocryptRecommendations) {
|
||||
ContentValues cv = new ContentValues();
|
||||
for (AutocryptRecommendationResult peerResult : autocryptRecommendations) {
|
||||
@@ -359,12 +348,12 @@ public class KeychainExternalProvider extends ContentProvider implements SimpleC
|
||||
KeychainExternalContract.KEY_STATUS_UNVERIFIED);
|
||||
}
|
||||
|
||||
db.update(TEMP_TABLE_QUERIED_ADDRESSES, cv,TEMP_TABLE_COLUMN_ADDRES + "=?",
|
||||
db.update(TEMP_TABLE_QUERIED_ADDRESSES, SQLiteDatabase.CONFLICT_IGNORE, cv,TEMP_TABLE_COLUMN_ADDRES + "=?",
|
||||
new String[] { peerResult.peerId });
|
||||
}
|
||||
}
|
||||
|
||||
private void fillTempTableWithUidResult(SQLiteDatabase db, boolean isWildcardSelector) {
|
||||
private void fillTempTableWithUidResult(SupportSQLiteDatabase db, boolean isWildcardSelector) {
|
||||
String cmpOperator = isWildcardSelector ? " LIKE " : " = ";
|
||||
long unixSeconds = System.currentTimeMillis() / 1000;
|
||||
db.execSQL("REPLACE INTO " + TEMP_TABLE_QUERIED_ADDRESSES +
|
||||
|
||||
@@ -915,8 +915,7 @@ public class OpenPgpService extends Service {
|
||||
|
||||
private HashSet<Long> getAllowedKeyIds() {
|
||||
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
||||
return mApiDao.getAllowedKeyIdsForApp(
|
||||
KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
||||
return mApiDao.getAllowedKeyIdsForApp(currentPkg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,12 +17,13 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
|
||||
public class PackageUninstallReceiver extends BroadcastReceiver {
|
||||
|
||||
@@ -34,8 +35,9 @@ public class PackageUninstallReceiver extends BroadcastReceiver {
|
||||
return;
|
||||
}
|
||||
String packageName = uri.getEncodedSchemeSpecificPart();
|
||||
Uri appUri = KeychainContract.ApiApps.buildByPackageNameUri(packageName);
|
||||
context.getContentResolver().delete(appUri, null, null);
|
||||
|
||||
ApiDataAccessObject apiDao = new ApiDataAccessObject(context);
|
||||
apiDao.deleteApiApp(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,19 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.remote;
|
||||
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.util.Log;
|
||||
|
||||
import org.bouncycastle.bcpg.HashAlgorithmTags;
|
||||
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
|
||||
import org.openintents.ssh.authentication.ISshAuthenticationService;
|
||||
@@ -40,7 +48,6 @@ import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.KeyRepository;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
||||
import org.sufficientlysecure.keychain.ssh.AuthenticationData;
|
||||
@@ -50,13 +57,6 @@ import org.sufficientlysecure.keychain.ssh.AuthenticationResult;
|
||||
import org.sufficientlysecure.keychain.ssh.signature.SshSignatureConverter;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class SshAuthenticationService extends Service {
|
||||
private static final String TAG = "SshAuthService";
|
||||
@@ -394,7 +394,7 @@ public class SshAuthenticationService extends Service {
|
||||
|
||||
private HashSet<Long> getAllowedKeyIds() {
|
||||
String currentPkg = mApiPermissionHelper.getCurrentCallingPackage();
|
||||
return mApiDao.getAllowedKeyIdsForApp(KeychainContract.ApiAllowedKeys.buildBaseUri(currentPkg));
|
||||
return mApiDao.getAllowedKeyIdsForApp(currentPkg);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -26,7 +26,6 @@ import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.Menu;
|
||||
@@ -36,24 +35,26 @@ import android.widget.TextView;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.AdvancedAppSettingsDialogFragment;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class AppSettingsActivity extends BaseActivity {
|
||||
private Uri mAppUri;
|
||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||
|
||||
private String packageName;
|
||||
|
||||
private TextView mAppNameView;
|
||||
private ImageView mAppIconView;
|
||||
|
||||
|
||||
// model
|
||||
AppSettings mAppSettings;
|
||||
ApiApp mApiApp;
|
||||
private ApiDataAccessObject apiDataAccessObject;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -68,15 +69,16 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
setTitle(null);
|
||||
|
||||
Intent intent = getIntent();
|
||||
mAppUri = intent.getData();
|
||||
if (mAppUri == null) {
|
||||
Timber.e("Intent data missing. Should be Uri of app!");
|
||||
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||
if (packageName == null) {
|
||||
Timber.e("Required extra package_name missing!");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
Timber.d("uri: %s", mAppUri);
|
||||
loadData(savedInstanceState, mAppUri);
|
||||
apiDataAccessObject = new ApiDataAccessObject(this);
|
||||
|
||||
loadData(savedInstanceState);
|
||||
}
|
||||
|
||||
private void save() {
|
||||
@@ -138,7 +140,7 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
// advanced info: package certificate SHA-256
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(mAppSettings.getPackageCertificate());
|
||||
md.update(mApiApp.package_signature());
|
||||
byte[] digest = md.digest();
|
||||
certificate = new String(Hex.encode(digest));
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
@@ -146,7 +148,7 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
AdvancedAppSettingsDialogFragment dialogFragment =
|
||||
AdvancedAppSettingsDialogFragment.newInstance(mAppSettings.getPackageName(), certificate);
|
||||
AdvancedAppSettingsDialogFragment.newInstance(mApiApp.package_name(), certificate);
|
||||
|
||||
dialogFragment.show(getSupportFragmentManager(), "advancedDialog");
|
||||
}
|
||||
@@ -155,7 +157,7 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
Intent i;
|
||||
PackageManager manager = getPackageManager();
|
||||
try {
|
||||
i = manager.getLaunchIntentForPackage(mAppSettings.getPackageName());
|
||||
i = manager.getLaunchIntentForPackage(mApiApp.package_name());
|
||||
if (i == null)
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
// start like the Android launcher would do
|
||||
@@ -167,31 +169,29 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void loadData(Bundle savedInstanceState, Uri appUri) {
|
||||
mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);
|
||||
private void loadData(Bundle savedInstanceState) {
|
||||
mApiApp = apiDataAccessObject.getApiApp(packageName);
|
||||
|
||||
// get application name and icon from package manager
|
||||
String appName;
|
||||
Drawable appIcon = null;
|
||||
PackageManager pm = getApplicationContext().getPackageManager();
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0);
|
||||
ApplicationInfo ai = pm.getApplicationInfo(mApiApp.package_name(), 0);
|
||||
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
appIcon = pm.getApplicationIcon(ai);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = mAppSettings.getPackageName();
|
||||
appName = mApiApp.package_name();
|
||||
}
|
||||
mAppNameView.setText(appName);
|
||||
mAppIconView.setImageDrawable(appIcon);
|
||||
|
||||
Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
|
||||
Timber.d("allowedKeysUri: " + allowedKeysUri);
|
||||
startListFragments(savedInstanceState, allowedKeysUri);
|
||||
startListFragments(savedInstanceState);
|
||||
}
|
||||
|
||||
private void startListFragments(Bundle savedInstanceState, Uri allowedKeysUri) {
|
||||
private void startListFragments(Bundle savedInstanceState) {
|
||||
// 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.
|
||||
@@ -199,7 +199,8 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(allowedKeysUri);
|
||||
// Create an instance of the fragments
|
||||
AppSettingsAllowedKeysListFragment allowedKeysFragment = AppSettingsAllowedKeysListFragment.newInstance(packageName);
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
@@ -210,9 +211,7 @@ public class AppSettingsActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void revokeAccess() {
|
||||
if (getContentResolver().delete(mAppUri, null, null) <= 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
apiDataAccessObject.deleteApiApp(packageName);
|
||||
finish();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,9 @@ package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import android.content.OperationApplicationException;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
@@ -40,25 +38,24 @@ import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.adapter.KeySelectableAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.widget.FixedListView;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
private static final String ARG_PACKAGE_NAME = "package_name";
|
||||
|
||||
private KeySelectableAdapter mAdapter;
|
||||
private ApiDataAccessObject mApiDao;
|
||||
|
||||
private Uri mDataUri;
|
||||
private String packageName;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static AppSettingsAllowedKeysListFragment newInstance(Uri dataUri) {
|
||||
public static AppSettingsAllowedKeysListFragment newInstance(String packageName) {
|
||||
AppSettingsAllowedKeysListFragment frag = new AppSettingsAllowedKeysListFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putString(ARG_PACKAGE_NAME, packageName);
|
||||
|
||||
frag.setArguments(args);
|
||||
|
||||
@@ -101,13 +98,13 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
packageName = getArguments().getString(ARG_PACKAGE_NAME);
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
setEmptyText(getString(R.string.list_empty));
|
||||
|
||||
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(mDataUri);
|
||||
Set<Long> checked = mApiDao.getAllowedKeyIdsForApp(packageName);
|
||||
mAdapter = new KeySelectableAdapter(getActivity(), null, 0, checked);
|
||||
setListAdapter(mAdapter);
|
||||
getListView().setOnItemClickListener(mAdapter);
|
||||
@@ -140,11 +137,7 @@ public class AppSettingsAllowedKeysListFragment extends ListFragmentWorkaround i
|
||||
} */
|
||||
|
||||
public void saveAllowedKeys() {
|
||||
try {
|
||||
mApiDao.saveAllowedKeyIdsForApp(mDataUri, getSelectedMasterKeyIds());
|
||||
} catch (RemoteException | OperationApplicationException e) {
|
||||
Timber.e(e, "Problem saving allowed key ids!");
|
||||
}
|
||||
mApiDao.saveAllowedKeyIdsForApp(packageName, getSelectedMasterKeyIds());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* 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.remote.ui;
|
||||
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.bouncycastle.util.encoders.Hex;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import timber.log.Timber;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class AppSettingsHeaderFragment extends Fragment {
|
||||
|
||||
// model
|
||||
private AppSettings mAppSettings;
|
||||
|
||||
// view
|
||||
private TextView mAppNameView;
|
||||
private ImageView mAppIconView;
|
||||
private TextView mPackageName;
|
||||
private TextView mPackageCertificate;
|
||||
|
||||
public AppSettings getAppSettings() {
|
||||
return mAppSettings;
|
||||
}
|
||||
|
||||
public void setAppSettings(AppSettings appSettings) {
|
||||
this.mAppSettings = appSettings;
|
||||
updateView(appSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inflate the layout for this fragment
|
||||
*/
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
View view = inflater.inflate(R.layout.api_app_settings_fragment, container, false);
|
||||
mAppNameView = view.findViewById(R.id.api_app_settings_app_name);
|
||||
mAppIconView = view.findViewById(R.id.api_app_settings_app_icon);
|
||||
mPackageName = view.findViewById(R.id.api_app_settings_package_name);
|
||||
mPackageCertificate = view.findViewById(R.id.api_app_settings_package_certificate);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void updateView(AppSettings appSettings) {
|
||||
// get application name and icon from package manager
|
||||
String appName;
|
||||
Drawable appIcon = null;
|
||||
PackageManager pm = getActivity().getApplicationContext().getPackageManager();
|
||||
try {
|
||||
ApplicationInfo ai = pm.getApplicationInfo(appSettings.getPackageName(), 0);
|
||||
|
||||
appName = (String) pm.getApplicationLabel(ai);
|
||||
appIcon = pm.getApplicationIcon(ai);
|
||||
} catch (NameNotFoundException e) {
|
||||
// fallback
|
||||
appName = appSettings.getPackageName();
|
||||
}
|
||||
mAppNameView.setText(appName);
|
||||
mAppIconView.setImageDrawable(appIcon);
|
||||
|
||||
// advanced info: package name
|
||||
mPackageName.setText(appSettings.getPackageName());
|
||||
|
||||
// advanced info: package signature SHA-256
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("SHA-256");
|
||||
md.update(appSettings.getPackageCertificate());
|
||||
byte[] digest = md.digest();
|
||||
String signature = new String(Hex.encode(digest));
|
||||
|
||||
mPackageCertificate.setText(signature);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
Timber.e(e, "Should not happen!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -17,87 +17,77 @@
|
||||
|
||||
package org.sufficientlysecure.keychain.remote.ui;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.CursorJoiner;
|
||||
import android.database.MatrixCursor;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.v4.app.ListFragment;
|
||||
import android.support.v4.app.LoaderManager;
|
||||
import android.support.v4.content.CursorLoader;
|
||||
import android.support.v4.content.Loader;
|
||||
import android.support.v4.widget.CursorAdapter;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.AdapterView.OnItemClickListener;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.ApiApps;
|
||||
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment.ApiAppAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
||||
import org.sufficientlysecure.keychain.ui.keyview.loader.AsyncTaskLiveData;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
public class AppsListFragment extends ListFragment implements
|
||||
LoaderManager.LoaderCallbacks<Cursor>, OnItemClickListener {
|
||||
|
||||
AppsAdapter mAdapter;
|
||||
public class AppsListFragment extends RecyclerFragment<ApiAppAdapter> {
|
||||
private ApiAppAdapter adapter;
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
getListView().setOnItemClickListener(this);
|
||||
|
||||
// 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 AppsAdapter(getActivity(), null, 0);
|
||||
setListAdapter(mAdapter);
|
||||
adapter = new ApiAppAdapter(getActivity());
|
||||
setAdapter(adapter);
|
||||
setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.VERTICAL, false));
|
||||
|
||||
// NOTE: Loader is started in onResume!
|
||||
new ApiAppsLiveData(getContext()).observe(this, this::onLoad);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
||||
// Start out with a progress indicator.
|
||||
setListShown(false);
|
||||
|
||||
// After coming back from Google Play -> reload
|
||||
getLoaderManager().restartLoader(0, null, this);
|
||||
private void onLoad(List<ListedApp> apiApps) {
|
||||
if (apiApps == null) {
|
||||
hideList(false);
|
||||
adapter.setData(null);
|
||||
return;
|
||||
}
|
||||
adapter.setData(apiApps);
|
||||
showList(true);
|
||||
}
|
||||
|
||||
@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);
|
||||
public void onItemClick(int position) {
|
||||
ListedApp listedApp = adapter.data.get(position);
|
||||
|
||||
if (installed) {
|
||||
if (registered) {
|
||||
if (listedApp.isInstalled) {
|
||||
if (listedApp.isRegistered) {
|
||||
// Edit app settings
|
||||
Intent intent = new Intent(getActivity(), AppSettingsActivity.class);
|
||||
intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName));
|
||||
intent.putExtra(AppSettingsActivity.EXTRA_PACKAGE_NAME, listedApp.packageName);
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Intent i;
|
||||
PackageManager manager = getActivity().getPackageManager();
|
||||
try {
|
||||
i = manager.getLaunchIntentForPackage(selectedPackageName);
|
||||
i = manager.getLaunchIntentForPackage(listedApp.packageName);
|
||||
if (i == null) {
|
||||
throw new PackageManager.NameNotFoundException();
|
||||
}
|
||||
@@ -112,256 +102,163 @@ public class AppsListFragment extends ListFragment implements
|
||||
} else {
|
||||
try {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("market://details?id=" + selectedPackageName)));
|
||||
Uri.parse("market://details?id=" + listedApp.packageName)));
|
||||
} catch (ActivityNotFoundException anfe) {
|
||||
startActivity(new Intent(Intent.ACTION_VIEW,
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=" + selectedPackageName)));
|
||||
Uri.parse("https://play.google.com/store/apps/details?id=" + listedApp.packageName)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
public class ApiAppAdapter extends Adapter<ApiAppViewHolder> {
|
||||
private final LayoutInflater inflater;
|
||||
|
||||
static final String[] PROJECTION = new String[]{
|
||||
ApiApps._ID, // 0
|
||||
ApiApps.PACKAGE_NAME, // 1
|
||||
"null as " + TEMP_COLUMN_NAME, // installed apps can retrieve app name from Android OS
|
||||
"0 as " + TEMP_COLUMN_INSTALLED, // changed later in cursor joiner
|
||||
"1 as " + TEMP_COLUMN_REGISTERED, // if it is in db it is registered
|
||||
"0 as " + TEMP_COLUMN_ICON_RES_ID // not used
|
||||
private List<ListedApp> data;
|
||||
|
||||
ApiAppAdapter(Context context) {
|
||||
super();
|
||||
|
||||
inflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ApiAppViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
|
||||
return new ApiAppViewHolder(inflater.inflate(R.layout.api_apps_adapter_list_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ApiAppViewHolder holder, int position) {
|
||||
ListedApp item = data.get(position);
|
||||
holder.bind(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return data != null ? data.size() : 0;
|
||||
}
|
||||
|
||||
public void setData(List<ListedApp> data) {
|
||||
this.data = data;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public class ApiAppViewHolder extends RecyclerView.ViewHolder {
|
||||
private final TextView text;
|
||||
private final ImageView icon;
|
||||
private final ImageView installIcon;
|
||||
|
||||
ApiAppViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
text = itemView.findViewById(R.id.api_apps_adapter_item_name);
|
||||
icon = itemView.findViewById(R.id.api_apps_adapter_item_icon);
|
||||
installIcon = itemView.findViewById(R.id.api_apps_adapter_install_icon);
|
||||
itemView.setOnClickListener((View view) -> onItemClick(getAdapterPosition()));
|
||||
}
|
||||
|
||||
void bind(ListedApp listedApp) {
|
||||
text.setText(listedApp.readableName);
|
||||
if (listedApp.applicationIconRes != null) {
|
||||
icon.setImageResource(listedApp.applicationIconRes);
|
||||
} else {
|
||||
icon.setImageDrawable(listedApp.applicationIcon);
|
||||
}
|
||||
installIcon.setVisibility(listedApp.isInstalled ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ApiAppsLiveData extends AsyncTaskLiveData<List<ListedApp>> {
|
||||
private final ApiDataAccessObject apiDao;
|
||||
private final PackageManager packageManager;
|
||||
|
||||
ApiAppsLiveData(Context context) {
|
||||
super(context, null);
|
||||
|
||||
packageManager = getContext().getPackageManager();
|
||||
apiDao = new ApiDataAccessObject(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ListedApp> asyncLoadData() {
|
||||
ArrayList<ListedApp> result = new ArrayList<>();
|
||||
|
||||
loadRegisteredApps(result);
|
||||
addPlaceholderApps(result);
|
||||
|
||||
Collections.sort(result, (o1, o2) -> o1.readableName.compareTo(o2.readableName));
|
||||
return result;
|
||||
}
|
||||
|
||||
private void loadRegisteredApps(ArrayList<ListedApp> result) {
|
||||
List<ApiApp> registeredApiApps = apiDao.getAllApiApps();
|
||||
|
||||
for (ApiApp apiApp : registeredApiApps) {
|
||||
ListedApp listedApp;
|
||||
try {
|
||||
ApplicationInfo ai = packageManager.getApplicationInfo(apiApp.package_name(), 0);
|
||||
CharSequence applicationLabel = packageManager.getApplicationLabel(ai);
|
||||
Drawable applicationIcon = packageManager.getApplicationIcon(ai);
|
||||
|
||||
listedApp = new ListedApp(apiApp.package_name(), true, true, applicationLabel, applicationIcon, null);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
listedApp = new ListedApp(apiApp.package_name(), false, true, apiApp.package_name(), null, null);
|
||||
}
|
||||
result.add(listedApp);
|
||||
}
|
||||
}
|
||||
|
||||
private void addPlaceholderApps(ArrayList<ListedApp> result) {
|
||||
for (ListedApp placeholderApp : PLACERHOLDER_APPS) {
|
||||
if (!containsByPackageName(result, placeholderApp.packageName)) {
|
||||
try {
|
||||
packageManager.getApplicationInfo(placeholderApp.packageName, 0);
|
||||
result.add(placeholderApp.withIsInstalled());
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
result.add(placeholderApp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsByPackageName(ArrayList<ListedApp> result, String packageName) {
|
||||
for (ListedApp app : result) {
|
||||
if (packageName.equals(app.packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListedApp {
|
||||
final String packageName;
|
||||
final boolean isInstalled;
|
||||
final boolean isRegistered;
|
||||
final String readableName;
|
||||
final Drawable applicationIcon;
|
||||
final Integer applicationIconRes;
|
||||
|
||||
ListedApp(String packageName, boolean isInstalled, boolean isRegistered, CharSequence readableName,
|
||||
Drawable applicationIcon, Integer applicationIconRes) {
|
||||
this.packageName = packageName;
|
||||
this.isInstalled = isInstalled;
|
||||
this.isRegistered = isRegistered;
|
||||
this.readableName = readableName.toString();
|
||||
this.applicationIcon = applicationIcon;
|
||||
this.applicationIconRes = applicationIconRes;
|
||||
}
|
||||
|
||||
public ListedApp withIsInstalled() {
|
||||
return new ListedApp(packageName, true, isRegistered, readableName, applicationIcon, applicationIconRes);
|
||||
}
|
||||
}
|
||||
|
||||
private static final ListedApp[] PLACERHOLDER_APPS = {
|
||||
new ListedApp("com.fsck.k9", false, false, "K-9 Mail", null, R.drawable.apps_k9),
|
||||
new ListedApp("com.zeapo.pwdstore", false, false, "Password Store", null, R.drawable.apps_password_store),
|
||||
new ListedApp("eu.siacs.conversations", false, false, "Conversations (Instant Messaging)", null,
|
||||
R.drawable.apps_conversations)
|
||||
};
|
||||
|
||||
private static final int INDEX_ID = 0;
|
||||
private static final int INDEX_PACKAGE_NAME = 1;
|
||||
private static final int INDEX_NAME = 2;
|
||||
private static final int INDEX_INSTALLED = 3;
|
||||
private static final int INDEX_REGISTERED = 4;
|
||||
private static final int INDEX_ICON_RES_ID = 5;
|
||||
|
||||
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
|
||||
// This is called when a new Loader needs to be created. This
|
||||
// sample only has one Loader, so we don't care about the ID.
|
||||
// First, pick the base URI to use depending on whether we are
|
||||
// currently filtering.
|
||||
Uri baseUri = ApiApps.CONTENT_URI;
|
||||
|
||||
// Now create and return a CursorLoader that will take care of
|
||||
// creating a Cursor for the data being displayed.
|
||||
return new AppsLoader(getActivity(), baseUri, PROJECTION, null, null,
|
||||
ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC");
|
||||
}
|
||||
|
||||
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
|
||||
// Swap the new cursor in. (The framework will take care of closing the
|
||||
// old cursor once we return.)
|
||||
mAdapter.swapCursor(data);
|
||||
|
||||
// The list should now be shown.
|
||||
setListShown(true);
|
||||
}
|
||||
|
||||
public void onLoaderReset(Loader<Cursor> loader) {
|
||||
// This is called when the last Cursor provided to onLoadFinished()
|
||||
// above is about to be closed. We need to make sure we are no
|
||||
// longer using it.
|
||||
mAdapter.swapCursor(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.mipmap.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.mipmap.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 AppsAdapter(Context context, Cursor c, int flags) {
|
||||
super(context, c, flags);
|
||||
|
||||
mInflater = LayoutInflater.from(context);
|
||||
mPM = context.getApplicationContext().getPackageManager();
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to CursorAdapter.getItemId().
|
||||
* Required to build Uris for api apps, which are not based on row ids
|
||||
*/
|
||||
public String getItemPackageName(int position) {
|
||||
if (mDataValid && mCursor != null && mCursor.moveToPosition(position)) {
|
||||
return mCursor.getString(INDEX_PACKAGE_NAME);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean getItemIsInstalled(int position) {
|
||||
return mDataValid && mCursor != null
|
||||
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_INSTALLED) == 1);
|
||||
}
|
||||
|
||||
public boolean getItemIsRegistered(int position) {
|
||||
return mDataValid && mCursor != null
|
||||
&& mCursor.moveToPosition(position) && (mCursor.getInt(INDEX_REGISTERED) == 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindView(View view, Context context, Cursor cursor) {
|
||||
TextView text = view.findViewById(R.id.api_apps_adapter_item_name);
|
||||
ImageView icon = view.findViewById(R.id.api_apps_adapter_item_icon);
|
||||
ImageView installIcon = view.findViewById(R.id.api_apps_adapter_install_icon);
|
||||
|
||||
String packageName = cursor.getString(INDEX_PACKAGE_NAME);
|
||||
Timber.d("packageName: " + packageName);
|
||||
int installed = cursor.getInt(INDEX_INSTALLED);
|
||||
String name = cursor.getString(INDEX_NAME);
|
||||
int iconResName = cursor.getInt(INDEX_ICON_RES_ID);
|
||||
|
||||
// get application name and icon
|
||||
try {
|
||||
ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0);
|
||||
|
||||
text.setText(mPM.getApplicationLabel(ai));
|
||||
icon.setImageDrawable(mPM.getApplicationIcon(ai));
|
||||
} catch (final PackageManager.NameNotFoundException e) {
|
||||
// fallback
|
||||
if (name == null) {
|
||||
text.setText(packageName);
|
||||
} else {
|
||||
text.setText(name);
|
||||
try {
|
||||
icon.setImageDrawable(getResources().getDrawable(iconResName));
|
||||
} catch (Resources.NotFoundException e1) {
|
||||
// silently fail
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (installed == 1) {
|
||||
installIcon.setVisibility(View.GONE);
|
||||
} else {
|
||||
installIcon.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View newView(Context context, Cursor cursor, ViewGroup parent) {
|
||||
return mInflater.inflate(R.layout.api_apps_adapter_list_item, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import timber.log.Timber;
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ class RemoteRegisterPresenter {
|
||||
|
||||
private RemoteRegisterView view;
|
||||
private Intent resultData;
|
||||
private AppSettings appSettings;
|
||||
private ApiApp apiApp;
|
||||
|
||||
|
||||
RemoteRegisterPresenter(Context context) {
|
||||
@@ -54,7 +54,7 @@ class RemoteRegisterPresenter {
|
||||
}
|
||||
|
||||
void setupFromIntentData(Intent resultData, String packageName, byte[] packageSignature) {
|
||||
this.appSettings = new AppSettings(packageName, packageSignature);
|
||||
this.apiApp = ApiApp.create(packageName, packageSignature);
|
||||
this.resultData = resultData;
|
||||
|
||||
try {
|
||||
@@ -76,7 +76,7 @@ class RemoteRegisterPresenter {
|
||||
}
|
||||
|
||||
void onClickAllow() {
|
||||
apiDao.insertApiApp(appSettings);
|
||||
apiDao.insertApiApp(apiApp);
|
||||
view.finishWithResult(resultData);
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import timber.log.Timber;
|
||||
|
||||
public class SelectSignKeyIdActivity extends BaseActivity {
|
||||
|
||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||
public static final String EXTRA_USER_ID = OpenPgpApi.EXTRA_USER_ID;
|
||||
public static final String EXTRA_DATA = "data";
|
||||
|
||||
@@ -68,19 +69,18 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
||||
});
|
||||
|
||||
Intent intent = getIntent();
|
||||
Uri appUri = intent.getData();
|
||||
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||
mPreferredUserId = intent.getStringExtra(EXTRA_USER_ID);
|
||||
mData = intent.getParcelableExtra(EXTRA_DATA);
|
||||
if (appUri == null) {
|
||||
if (packageName == null) {
|
||||
Timber.e("Intent data missing. Should be Uri of app!");
|
||||
finish();
|
||||
} else {
|
||||
Timber.d("uri: " + appUri);
|
||||
startListFragments(savedInstanceState, appUri, mData, mPreferredUserId);
|
||||
startListFragments(savedInstanceState, packageName, mData, mPreferredUserId);
|
||||
}
|
||||
}
|
||||
|
||||
private void startListFragments(Bundle savedInstanceState, Uri dataUri, Intent data, String preferredUserId) {
|
||||
private void startListFragments(Bundle savedInstanceState, String packageName, Intent data, String preferredUserId) {
|
||||
// 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.
|
||||
@@ -90,7 +90,7 @@ public class SelectSignKeyIdActivity extends BaseActivity {
|
||||
|
||||
// Create an instance of the fragments
|
||||
SelectSignKeyIdListFragment listFragment = SelectSignKeyIdListFragment
|
||||
.newInstance(dataUri, data, preferredUserId);
|
||||
.newInstance(packageName, data, preferredUserId);
|
||||
// Add the fragment to the 'fragment_container' FrameLayout
|
||||
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
|
||||
getSupportFragmentManager().beginTransaction()
|
||||
|
||||
@@ -33,35 +33,33 @@ import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.pgp.KeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.remote.ui.adapter.SelectSignKeyAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.CreateKeyActivity;
|
||||
import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter;
|
||||
import org.sufficientlysecure.keychain.ui.base.RecyclerFragment;
|
||||
import timber.log.Timber;
|
||||
import org.sufficientlysecure.keychain.ui.util.adapter.CursorAdapter;
|
||||
|
||||
|
||||
public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyAdapter>
|
||||
implements SelectSignKeyAdapter.SelectSignKeyListener, LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
private static final String ARG_DATA_URI = "uri";
|
||||
private static final String ARG_PACKAGE_NAME = "package_name";
|
||||
private static final String ARG_PREF_UID = "pref_uid";
|
||||
public static final String ARG_DATA = "data";
|
||||
|
||||
private Uri mDataUri;
|
||||
private Intent mResult;
|
||||
private String mPrefUid;
|
||||
private ApiDataAccessObject mApiDao;
|
||||
private String mPackageName;
|
||||
|
||||
/**
|
||||
* Creates new instance of this fragment
|
||||
*/
|
||||
public static SelectSignKeyIdListFragment newInstance(Uri dataUri, Intent data, String preferredUserId) {
|
||||
public static SelectSignKeyIdListFragment newInstance(String packageName, Intent data, String preferredUserId) {
|
||||
SelectSignKeyIdListFragment frag = new SelectSignKeyIdListFragment();
|
||||
Bundle args = new Bundle();
|
||||
|
||||
args.putParcelable(ARG_DATA_URI, dataUri);
|
||||
args.putString(ARG_PACKAGE_NAME, packageName);
|
||||
args.putParcelable(ARG_DATA, data);
|
||||
args.putString(ARG_PREF_UID, preferredUserId);
|
||||
|
||||
@@ -85,7 +83,7 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyA
|
||||
|
||||
mResult = getArguments().getParcelable(ARG_DATA);
|
||||
mPrefUid = getArguments().getString(ARG_PREF_UID);
|
||||
mDataUri = getArguments().getParcelable(ARG_DATA_URI);
|
||||
mPackageName = getArguments().getString(ARG_PACKAGE_NAME);
|
||||
|
||||
// Give some text to display if there is no data. In a real
|
||||
// application this would come from a resource.
|
||||
@@ -175,16 +173,9 @@ public class SelectSignKeyIdListFragment extends RecyclerFragment<SelectSignKeyA
|
||||
|
||||
@Override
|
||||
public void onSelectKeyItemClicked(long masterKeyId) {
|
||||
Uri allowedKeysUri = mDataUri.buildUpon()
|
||||
.appendPath(KeychainContract.PATH_ALLOWED_KEYS)
|
||||
.build();
|
||||
|
||||
mApiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
|
||||
mApiDao.addAllowedKeyIdForApp(mPackageName, masterKeyId);
|
||||
mResult.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, masterKeyId);
|
||||
|
||||
Timber.d("allowedKeyId: " + masterKeyId);
|
||||
Timber.d("allowedKeysUri: " + allowedKeysUri);
|
||||
|
||||
getActivity().setResult(Activity.RESULT_OK, mResult);
|
||||
getActivity().finish();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
package org.sufficientlysecure.keychain.remote.ui.dialog;
|
||||
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
@@ -27,7 +29,6 @@ import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.Drawable.ConstantState;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
@@ -48,21 +49,17 @@ import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.mikepenz.materialdrawer.util.KeyboardUtil;
|
||||
|
||||
import org.openintents.ssh.authentication.SshAuthenticationApi;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
|
||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.remote.ui.RemoteSecurityTokenOperationActivity;
|
||||
import org.sufficientlysecure.keychain.remote.ui.dialog.RemoteSelectAuthenticationKeyPresenter.RemoteSelectAuthenticationKeyView;
|
||||
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
|
||||
import org.sufficientlysecure.keychain.ui.util.ThemeChanger;
|
||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.DividerItemDecoration;
|
||||
import org.sufficientlysecure.keychain.ui.util.recyclerview.RecyclerItemClickListener;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
||||
public static final String EXTRA_PACKAGE_NAME = "package_name";
|
||||
@@ -71,6 +68,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
||||
|
||||
|
||||
private RemoteSelectAuthenticationKeyPresenter presenter;
|
||||
private String packageName;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -92,8 +90,7 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
||||
super.onStart();
|
||||
|
||||
Intent intent = getIntent();
|
||||
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||
|
||||
packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||
|
||||
presenter.setupFromIntentData(packageName);
|
||||
presenter.startLoaders(getSupportLoaderManager());
|
||||
@@ -104,14 +101,8 @@ public class RemoteSelectAuthenticationKeyActivity extends FragmentActivity {
|
||||
Intent originalIntent = callingIntent.getParcelableExtra(
|
||||
RemoteSecurityTokenOperationActivity.EXTRA_DATA);
|
||||
|
||||
Uri appUri = callingIntent.getData();
|
||||
|
||||
Uri allowedKeysUri = appUri.buildUpon()
|
||||
.appendPath(KeychainContract.PATH_ALLOWED_KEYS)
|
||||
.build();
|
||||
|
||||
ApiDataAccessObject apiDao = new ApiDataAccessObject(getBaseContext());
|
||||
apiDao.addAllowedKeyIdForApp(allowedKeysUri, masterKeyId);
|
||||
apiDao.addAllowedKeyIdForApp(packageName, masterKeyId);
|
||||
|
||||
originalIntent.putExtra(SshAuthenticationApi.EXTRA_KEY_ID, String.valueOf(masterKeyId));
|
||||
|
||||
|
||||
@@ -34,12 +34,12 @@ import org.openintents.openpgp.util.OpenPgpUtils.UserId;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeyInfo;
|
||||
import org.sufficientlysecure.keychain.livedata.KeyInfoInteractor.KeySelector;
|
||||
import org.sufficientlysecure.keychain.model.ApiApp;
|
||||
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
|
||||
import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult;
|
||||
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
|
||||
import org.sufficientlysecure.keychain.provider.ApiDataAccessObject;
|
||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||
import org.sufficientlysecure.keychain.remote.AppSettings;
|
||||
import org.sufficientlysecure.keychain.service.ImportKeyringParcel;
|
||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||
import timber.log.Timber;
|
||||
@@ -58,7 +58,7 @@ class RemoteSelectIdentityKeyPresenter {
|
||||
private long selectedMasterKeyId;
|
||||
private byte[] generatedKeyData;
|
||||
private ApiDataAccessObject apiDao;
|
||||
private AppSettings appSettings;
|
||||
private ApiApp apiApp;
|
||||
|
||||
|
||||
RemoteSelectIdentityKeyPresenter(Context context, RemoteSelectIdViewModel viewModel, LifecycleOwner lifecycleOwner) {
|
||||
@@ -103,7 +103,7 @@ class RemoteSelectIdentityKeyPresenter {
|
||||
Drawable appIcon = packageManager.getApplicationIcon(applicationInfo);
|
||||
CharSequence appLabel = packageManager.getApplicationLabel(applicationInfo);
|
||||
|
||||
appSettings = new AppSettings(packageName, packageSignature);
|
||||
apiApp = ApiApp.create(packageName, packageSignature);
|
||||
|
||||
view.setTitleClientIconAndName(appIcon, appLabel);
|
||||
}
|
||||
@@ -200,15 +200,15 @@ class RemoteSelectIdentityKeyPresenter {
|
||||
}
|
||||
|
||||
void onHighlightFinished() {
|
||||
apiDao.insertApiApp(appSettings);
|
||||
apiDao.addAllowedKeyIdForApp(appSettings.getPackageName(), selectedMasterKeyId);
|
||||
apiDao.insertApiApp(apiApp);
|
||||
apiDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId);
|
||||
view.finishAndReturn(selectedMasterKeyId);
|
||||
}
|
||||
|
||||
void onImportOpSuccess(ImportKeyResult result) {
|
||||
long importedMasterKeyId = result.getImportedMasterKeyIds()[0];
|
||||
apiDao.insertApiApp(appSettings);
|
||||
apiDao.addAllowedKeyIdForApp(appSettings.getPackageName(), selectedMasterKeyId);
|
||||
apiDao.insertApiApp(apiApp);
|
||||
apiDao.addAllowedKeyIdForApp(apiApp.package_name(), selectedMasterKeyId);
|
||||
view.finishAndReturn(importedMasterKeyId);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user