From 6d1137190529dc7add74926cea52c377883319be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominik=20Sch=C3=BCrmann?= Date: Sun, 6 Apr 2014 12:57:42 +0200 Subject: Rename folder structure from OpenPGP Keychain to OpenKeychain --- .../remote/ui/AccountSettingsActivity.java | 109 ++++++++ .../remote/ui/AccountSettingsFragment.java | 201 ++++++++++++++ .../keychain/remote/ui/AccountsListFragment.java | 198 ++++++++++++++ .../keychain/remote/ui/AppSettingsActivity.java | 134 +++++++++ .../keychain/remote/ui/AppSettingsFragment.java | 108 ++++++++ .../keychain/remote/ui/AppsListActivity.java | 35 +++ .../keychain/remote/ui/AppsListFragment.java | 174 ++++++++++++ .../keychain/remote/ui/RemoteServiceActivity.java | 303 +++++++++++++++++++++ 8 files changed, 1262 insertions(+) create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java create mode 100644 OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui') diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java new file mode 100644 index 000000000..123ed526f --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsActivity.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.util.Log; + +public class AccountSettingsActivity extends ActionBarActivity { + private Uri mAccountUri; + + private AccountSettingsFragment mAccountSettingsFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Inflate a "Done" custom action bar + ActionBarHelper.setOneButtonView(getSupportActionBar(), + R.string.api_settings_save, R.drawable.ic_action_done, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // "Done" + save(); + } + }); + + setContentView(R.layout.api_account_settings_activity); + + mAccountSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_account_settings_fragment); + + Intent intent = getIntent(); + mAccountUri = intent.getData(); + if (mAccountUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mAccountUri); + loadData(mAccountUri); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.api_account_settings, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_account_settings_delete: + deleteAccount(); + return true; + case R.id.menu_account_settings_cancel: + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void loadData(Uri accountUri) { + AccountSettings settings = ProviderHelper.getApiAccountSettings(this, accountUri); + mAccountSettingsFragment.setAccSettings(settings); + } + + private void deleteAccount() { + if (getContentResolver().delete(mAccountUri, null, null) <= 0) { + throw new RuntimeException(); + } + finish(); + } + + private void save() { + ProviderHelper.updateApiAccount(this, mAccountSettingsFragment.getAccSettings(), mAccountUri); + finish(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java new file mode 100644 index 000000000..0a3ec3c3b --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountSettingsFragment.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +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.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.Spinner; +import android.widget.TextView; + +import com.beardedhen.androidbootstrap.BootstrapButton; + +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.ui.EditKeyActivity; +import org.sufficientlysecure.keychain.ui.SelectSecretKeyLayoutFragment; +import org.sufficientlysecure.keychain.ui.adapter.KeyValueSpinnerAdapter; +import org.sufficientlysecure.keychain.util.AlgorithmNames; + +public class AccountSettingsFragment extends Fragment implements + SelectSecretKeyLayoutFragment.SelectSecretKeyCallback { + + private static final int REQUEST_CODE_CREATE_KEY = 0x00008884; + + // model + private AccountSettings mAccSettings; + + // view + private TextView mAccNameView; + private Spinner mEncryptionAlgorithm; + private Spinner mHashAlgorithm; + private Spinner mCompression; + + private SelectSecretKeyLayoutFragment mSelectKeyFragment; + private BootstrapButton mCreateKeyButton; + + KeyValueSpinnerAdapter mEncryptionAdapter; + KeyValueSpinnerAdapter mHashAdapter; + KeyValueSpinnerAdapter mCompressionAdapter; + + public AccountSettings getAccSettings() { + return mAccSettings; + } + + public void setAccSettings(AccountSettings accountSettings) { + this.mAccSettings = accountSettings; + + mAccNameView.setText(accountSettings.getAccountName()); + mSelectKeyFragment.selectKey(accountSettings.getKeyId()); + mEncryptionAlgorithm.setSelection(mEncryptionAdapter.getPosition(accountSettings + .getEncryptionAlgorithm())); + mHashAlgorithm.setSelection(mHashAdapter.getPosition(accountSettings.getHashAlgorithm())); + mCompression.setSelection(mCompressionAdapter.getPosition(accountSettings.getCompression())); + } + + /** + * Inflate the layout for this fragment + */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.api_account_settings_fragment, container, false); + initView(view); + return view; + } + + /** + * Set error String on key selection + * + * @param error + */ + public void setErrorOnSelectKeyFragment(String error) { + mSelectKeyFragment.setError(error); + } + + private void initView(View view) { + mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getFragmentManager().findFragmentById( + R.id.api_account_settings_select_key_fragment); + mSelectKeyFragment.setCallback(this); + + mAccNameView = (TextView) view.findViewById(R.id.api_account_settings_acc_name); + mEncryptionAlgorithm = (Spinner) view + .findViewById(R.id.api_account_settings_encryption_algorithm); + mHashAlgorithm = (Spinner) view.findViewById(R.id.api_account_settings_hash_algorithm); + mCompression = (Spinner) view.findViewById(R.id.api_account_settings_compression); + mCreateKeyButton = (BootstrapButton) view.findViewById(R.id.api_account_settings_create_key); + + mCreateKeyButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + createKey(); + } + }); + + AlgorithmNames algorithmNames = new AlgorithmNames(getActivity()); + + mEncryptionAdapter = new KeyValueSpinnerAdapter(getActivity(), + algorithmNames.getEncryptionNames()); + mEncryptionAlgorithm.setAdapter(mEncryptionAdapter); + mEncryptionAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAccSettings.setEncryptionAlgorithm((int) id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + mHashAdapter = new KeyValueSpinnerAdapter(getActivity(), algorithmNames.getHashNames()); + mHashAlgorithm.setAdapter(mHashAdapter); + mHashAlgorithm.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAccSettings.setHashAlgorithm((int) id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + + mCompressionAdapter = new KeyValueSpinnerAdapter(getActivity(), + algorithmNames.getCompressionNames()); + mCompression.setAdapter(mCompressionAdapter); + mCompression.setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + mAccSettings.setCompression((int) id); + } + + @Override + public void onNothingSelected(AdapterView parent) { + } + }); + } + + private void createKey() { + Intent intent = new Intent(getActivity(), EditKeyActivity.class); + intent.setAction(EditKeyActivity.ACTION_CREATE_KEY); + intent.putExtra(EditKeyActivity.EXTRA_GENERATE_DEFAULT_KEYS, true); + // set default user id to account name + intent.putExtra(EditKeyActivity.EXTRA_USER_IDS, mAccSettings.getAccountName()); + startActivityForResult(intent, REQUEST_CODE_CREATE_KEY); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + switch (requestCode) { + case REQUEST_CODE_CREATE_KEY: { + if (resultCode == Activity.RESULT_OK) { + // select newly created key + long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), data.getData()); + mSelectKeyFragment.selectKey(masterKeyId); + } + break; + } + + default: + super.onActivityResult(requestCode, resultCode, data); + + break; + } + } + + /** + * callback from select secret key fragment + */ + @Override + public void onKeySelected(long secretKeyId) { + mAccSettings.setKeyId(secretKeyId); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java new file mode 100644 index 000000000..4d99e1923 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AccountsListFragment.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ListView; +import android.widget.TextView; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.ui.widget.FixedListView; +import org.sufficientlysecure.keychain.util.Log; + +public class AccountsListFragment extends ListFragment implements + LoaderManager.LoaderCallbacks { + + private static final String ARG_DATA_URI = "uri"; + + // This is the Adapter being used to display the list's data. + AccountsAdapter mAdapter; + + private Uri mDataUri; + + /** + * Creates new instance of this fragment + */ + public static AccountsListFragment newInstance(Uri dataUri) { + AccountsListFragment frag = new AccountsListFragment(); + + Bundle args = new Bundle(); + args.putParcelable(ARG_DATA_URI, dataUri); + + frag.setArguments(args); + + return frag; + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View layout = super.onCreateView(inflater, container, + savedInstanceState); + ListView lv = (ListView) layout.findViewById(android.R.id.list); + ViewGroup parent = (ViewGroup) lv.getParent(); + + /* + * http://stackoverflow.com/a/15880684 + * Remove ListView and add FixedListView in its place. + * This is done here programatically to be still able to use the progressBar of ListFragment. + * + * We want FixedListView to be able to put this ListFragment inside a ScrollView + */ + int lvIndex = parent.indexOfChild(lv); + parent.removeViewAt(lvIndex); + FixedListView newLv = new FixedListView(getActivity()); + newLv.setId(android.R.id.list); + parent.addView(newLv, lvIndex, lv.getLayoutParams()); + return layout; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + mDataUri = getArguments().getParcelable(ARG_DATA_URI); + + getListView().setOnItemClickListener(new OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int position, long id) { + String selectedAccountName = mAdapter.getItemAccountName(position); + Uri accountUri = mDataUri.buildUpon().appendEncodedPath(selectedAccountName).build(); + Log.d(Constants.TAG, "accountUri: " + accountUri); + + // edit account settings + Intent intent = new Intent(getActivity(), AccountSettingsActivity.class); + intent.setData(accountUri); + startActivity(intent); + } + }); + + // 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_settings_accounts_empty)); + + // 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 AccountsAdapter(getActivity(), null, 0); + setListAdapter(mAdapter); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + // These are the Contacts rows that we will retrieve. + static final String[] PROJECTION = new String[]{ + KeychainContract.ApiAccounts._ID, // 0 + KeychainContract.ApiAccounts.ACCOUNT_NAME // 1 + }; + + public Loader 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. + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, + KeychainContract.ApiAccounts.ACCOUNT_NAME + " COLLATE LOCALIZED ASC"); + } + + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + } + + public void onLoaderReset(Loader 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); + } + + private class AccountsAdapter extends CursorAdapter { + private LayoutInflater mInflater; + + public AccountsAdapter(Context context, Cursor c, int flags) { + super(context, c, flags); + + mInflater = LayoutInflater.from(context); + } + + /** + * Similar to CursorAdapter.getItemId(). + * Required to build Uris for api accounts, which are not based on row ids + * + * @param position + * @return + */ + public String getItemAccountName(int position) { + if (mDataValid && mCursor != null) { + if (mCursor.moveToPosition(position)) { + return mCursor.getString(1); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView text = (TextView) view.findViewById(R.id.api_accounts_adapter_item_name); + + String accountName = cursor.getString(1); + text.setText(accountName); + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.api_accounts_adapter_list_item, null); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java new file mode 100644 index 000000000..818c296c1 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsActivity.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; +import android.view.Menu; +import android.view.MenuItem; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.util.Log; + +public class AppSettingsActivity extends ActionBarActivity { + private Uri mAppUri; + + private AppSettingsFragment mSettingsFragment; + private AccountsListFragment mAccountsListFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // let the actionbar look like Android's contact app + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setIcon(android.R.color.transparent); + actionBar.setHomeButtonEnabled(true); + + setContentView(R.layout.api_app_settings_activity); + + mSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + Intent intent = getIntent(); + mAppUri = intent.getData(); + if (mAppUri == null) { + Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!"); + finish(); + return; + } else { + Log.d(Constants.TAG, "uri: " + mAppUri); + loadData(savedInstanceState, mAppUri); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + super.onCreateOptionsMenu(menu); + getMenuInflater().inflate(R.menu.api_app_settings, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_api_settings_revoke: + revokeAccess(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void loadData(Bundle savedInstanceState, Uri appUri) { + AppSettings settings = ProviderHelper.getApiAppSettings(this, appUri); + mSettingsFragment.setAppSettings(settings); + + String appName; + PackageManager pm = getPackageManager(); + try { + ApplicationInfo ai = pm.getApplicationInfo(settings.getPackageName(), 0); + appName = (String) pm.getApplicationLabel(ai); + } catch (PackageManager.NameNotFoundException e) { + // fallback + appName = settings.getPackageName(); + } + setTitle(appName); + + Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build(); + Log.d(Constants.TAG, "accountsUri: " + accountsUri); + startListFragment(savedInstanceState, accountsUri); + } + + private void startListFragment(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; + } + + // Create an instance of the fragment + mAccountsListFragment = AccountsListFragment.newInstance(dataUri); + + // Add the fragment to the 'fragment_container' FrameLayout + // NOTE: We use commitAllowingStateLoss() to prevent weird crashes! + getSupportFragmentManager().beginTransaction() + .replace(R.id.api_accounts_list_fragment, mAccountsListFragment) + .commitAllowingStateLoss(); + // do it immediately! + getSupportFragmentManager().executePendingTransactions(); + } + + private void revokeAccess() { + if (getContentResolver().delete(mAppUri, null, null) <= 0) { + throw new RuntimeException(); + } + finish(); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java new file mode 100644 index 000000000..a6db02708 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppSettingsFragment.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.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.spongycastle.util.encoders.Hex; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.util.Log; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class AppSettingsFragment extends Fragment { + + // model + private AppSettings mAppSettings; + + // view + private TextView mAppNameView; + private ImageView mAppIconView; + private TextView mPackageName; + private TextView mPackageSignature; + + 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 = (TextView) view.findViewById(R.id.api_app_settings_app_name); + mAppIconView = (ImageView) view.findViewById(R.id.api_app_settings_app_icon); + mPackageName = (TextView) view.findViewById(R.id.api_app_settings_package_name); + mPackageSignature = (TextView) view.findViewById(R.id.api_app_settings_package_signature); + 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.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); + } + } + + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java new file mode 100644 index 000000000..f86d279f0 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListActivity.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.os.Bundle; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.ui.DrawerActivity; + +public class AppsListActivity extends DrawerActivity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.api_apps_list_activity); + + setupDrawerNavigation(savedInstanceState); + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java new file mode 100644 index 000000000..9d0e6d3ef --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/AppsListFragment.java @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2013 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.database.Cursor; +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.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; + +public class AppsListFragment extends ListFragment implements + LoaderManager.LoaderCallbacks { + + // This is the Adapter being used to display the list's data. + RegisteredAppsAdapter 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); + // edit app settings + Intent intent = new Intent(getActivity(), AppSettingsActivity.class); + intent.setData(KeychainContract.ApiApps.buildByPackageNameUri(selectedPackageName)); + startActivity(intent); + } + }); + + // 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)); + + // 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); + setListAdapter(mAdapter); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + // These are the Contacts rows that we will retrieve. + static final String[] PROJECTION = new String[]{ + ApiApps._ID, // 0 + ApiApps.PACKAGE_NAME // 1 + }; + + public Loader 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 CursorLoader(getActivity(), baseUri, PROJECTION, null, null, + ApiApps.PACKAGE_NAME + " COLLATE LOCALIZED ASC"); + } + + public void onLoadFinished(Loader loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + } + + public void onLoaderReset(Loader 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); + } + + private class RegisteredAppsAdapter extends CursorAdapter { + + private LayoutInflater mInflater; + private PackageManager mPM; + + public RegisteredAppsAdapter(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 + * + * @param position + * @return + */ + public String getItemPackageName(int position) { + if (mDataValid && mCursor != null) { + if (mCursor.moveToPosition(position)) { + return mCursor.getString(1); + } else { + return null; + } + } else { + return null; + } + } + + @Override + public void bindView(View view, Context context, Cursor cursor) { + TextView text = (TextView) view.findViewById(R.id.api_apps_adapter_item_name); + ImageView icon = (ImageView) view.findViewById(R.id.api_apps_adapter_item_icon); + + String packageName = cursor.getString(cursor.getColumnIndex(ApiApps.PACKAGE_NAME)); + if (packageName != null) { + // get application name + try { + ApplicationInfo ai = mPM.getApplicationInfo(packageName, 0); + + text.setText(mPM.getApplicationLabel(ai)); + icon.setImageDrawable(mPM.getApplicationIcon(ai)); + } catch (final PackageManager.NameNotFoundException e) { + // fallback + text.setText(packageName); + } + } else { + // fallback + text.setText(packageName); + } + + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return mInflater.inflate(R.layout.api_apps_adapter_list_item, null); + } + } + +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java new file mode 100644 index 000000000..ab95f2691 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/ui/RemoteServiceActivity.java @@ -0,0 +1,303 @@ +/* + * Copyright (C) 2013-2014 Dominik Schürmann + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.remote.ui; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.support.v7.app.ActionBarActivity; +import android.view.View; + +import org.openintents.openpgp.util.OpenPgpApi; +import org.sufficientlysecure.htmltextview.HtmlTextView; +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.Id; +import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.remote.AccountSettings; +import org.sufficientlysecure.keychain.remote.AppSettings; +import org.sufficientlysecure.keychain.ui.SelectPublicKeyFragment; +import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment; +import org.sufficientlysecure.keychain.util.Log; + +import java.util.ArrayList; + +public class RemoteServiceActivity extends ActionBarActivity { + + public static final String ACTION_REGISTER = Constants.INTENT_PREFIX + "API_ACTIVITY_REGISTER"; + public static final String ACTION_CREATE_ACCOUNT = Constants.INTENT_PREFIX + + "API_ACTIVITY_CREATE_ACCOUNT"; + public static final String ACTION_CACHE_PASSPHRASE = Constants.INTENT_PREFIX + + "API_ACTIVITY_CACHE_PASSPHRASE"; + public static final String ACTION_SELECT_PUB_KEYS = Constants.INTENT_PREFIX + + "API_ACTIVITY_SELECT_PUB_KEYS"; + public static final String ACTION_ERROR_MESSAGE = Constants.INTENT_PREFIX + + "API_ACTIVITY_ERROR_MESSAGE"; + + public static final String EXTRA_MESSENGER = "messenger"; + + public static final String EXTRA_DATA = "data"; + + // passphrase action + public static final String EXTRA_SECRET_KEY_ID = "secret_key_id"; + // register action + public static final String EXTRA_PACKAGE_NAME = "package_name"; + public static final String EXTRA_PACKAGE_SIGNATURE = "package_signature"; + // create acc action + public static final String EXTRA_ACC_NAME = "acc_name"; + // select pub keys action + public static final String EXTRA_SELECTED_MASTER_KEY_IDS = "master_key_ids"; + public static final String EXTRA_MISSING_USER_IDS = "missing_user_ids"; + public static final String EXTRA_DUBLICATE_USER_IDS = "dublicate_user_ids"; + // error message + public static final String EXTRA_ERROR_MESSAGE = "error_message"; + + // register view + private AppSettingsFragment mAppSettingsFragment; + // create acc view + private AccountSettingsFragment mAccSettingsFragment; + // select pub keys view + private SelectPublicKeyFragment mSelectFragment; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + handleActions(getIntent(), savedInstanceState); + } + + protected void handleActions(Intent intent, Bundle savedInstanceState) { + + String action = intent.getAction(); + final Bundle extras = intent.getExtras(); + + + if (ACTION_REGISTER.equals(action)) { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final byte[] packageSignature = extras.getByteArray(EXTRA_PACKAGE_SIGNATURE); + Log.d(Constants.TAG, "ACTION_REGISTER packageName: " + packageName); + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setTwoButtonView(getSupportActionBar(), + R.string.api_register_allow, R.drawable.ic_action_done, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Allow + + ProviderHelper.insertApiApp(RemoteServiceActivity.this, + mAppSettingsFragment.getAppSettings()); + + // give data through for new service call + Intent resultData = extras.getParcelable(EXTRA_DATA); + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); + } + }, R.string.api_register_disallow, R.drawable.ic_action_cancel, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Disallow + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + } + ); + + setContentView(R.layout.api_remote_register_app); + + mAppSettingsFragment = (AppSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_app_settings_fragment); + + AppSettings settings = new AppSettings(packageName, packageSignature); + mAppSettingsFragment.setAppSettings(settings); + } else if (ACTION_CREATE_ACCOUNT.equals(action)) { + final String packageName = extras.getString(EXTRA_PACKAGE_NAME); + final String accName = extras.getString(EXTRA_ACC_NAME); + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setTwoButtonView(getSupportActionBar(), + R.string.api_settings_save, R.drawable.ic_action_done, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Save + + // user needs to select a key! + if (mAccSettingsFragment.getAccSettings().getKeyId() == Id.key.none) { + mAccSettingsFragment.setErrorOnSelectKeyFragment( + getString(R.string.api_register_error_select_key)); + } else { + ProviderHelper.insertApiAccount(RemoteServiceActivity.this, + KeychainContract.ApiAccounts.buildBaseUri(packageName), + mAccSettingsFragment.getAccSettings()); + + // give data through for new service call + Intent resultData = extras.getParcelable(EXTRA_DATA); + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); + } + } + }, R.string.api_settings_cancel, R.drawable.ic_action_cancel, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // Cancel + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + } + ); + + setContentView(R.layout.api_remote_create_account); + + mAccSettingsFragment = (AccountSettingsFragment) getSupportFragmentManager().findFragmentById( + R.id.api_account_settings_fragment); + + AccountSettings settings = new AccountSettings(accName); + mAccSettingsFragment.setAccSettings(settings); + } else if (ACTION_CACHE_PASSPHRASE.equals(action)) { + long secretKeyId = extras.getLong(EXTRA_SECRET_KEY_ID); + final Intent resultData = extras.getParcelable(EXTRA_DATA); + + PassphraseDialogFragment.show(this, secretKeyId, + new Handler() { + @Override + public void handleMessage(Message message) { + if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) { + // return given params again, for calling the service method again + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + } else { + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + } + + RemoteServiceActivity.this.finish(); + } + }); + + } else if (ACTION_SELECT_PUB_KEYS.equals(action)) { + long[] selectedMasterKeyIds = intent.getLongArrayExtra(EXTRA_SELECTED_MASTER_KEY_IDS); + ArrayList missingUserIds = intent + .getStringArrayListExtra(EXTRA_MISSING_USER_IDS); + ArrayList dublicateUserIds = intent + .getStringArrayListExtra(EXTRA_DUBLICATE_USER_IDS); + + // TODO: do this with spannable instead of HTML to prevent parsing failures with weird user ids + String text = "" + getString(R.string.api_select_pub_keys_text) + ""; + text += "

"; + if (missingUserIds != null && missingUserIds.size() > 0) { + text += getString(R.string.api_select_pub_keys_missing_text); + text += "
"; + text += "
    "; + for (String userId : missingUserIds) { + text += "
  • " + userId + "
  • "; + } + text += "
"; + text += "
"; + } + if (dublicateUserIds != null && dublicateUserIds.size() > 0) { + text += getString(R.string.api_select_pub_keys_dublicates_text); + text += "
"; + text += "
    "; + for (String userId : dublicateUserIds) { + text += "
  • " + userId + "
  • "; + } + text += "
"; + } + + // Inflate a "Done"/"Cancel" custom action bar view + ActionBarHelper.setTwoButtonView(getSupportActionBar(), + R.string.btn_okay, R.drawable.ic_action_done, + new View.OnClickListener() { + @Override + public void onClick(View v) { + // add key ids to params Bundle for new request + Intent resultData = extras.getParcelable(EXTRA_DATA); + resultData.putExtra(OpenPgpApi.EXTRA_KEY_IDS, + mSelectFragment.getSelectedMasterKeyIds()); + + RemoteServiceActivity.this.setResult(RESULT_OK, resultData); + RemoteServiceActivity.this.finish(); + } + }, R.string.btn_do_not_save, R.drawable.ic_action_cancel, new View.OnClickListener() { + @Override + public void onClick(View v) { + // cancel + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + } + ); + + setContentView(R.layout.api_remote_select_pub_keys); + + // set text on view + HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_select_pub_keys_text); + textView.setHtmlFromString(text); + + /* Load select pub keys fragment */ + // Check that the activity is using the layout version with + // the fragment_container FrameLayout + if (findViewById(R.id.api_select_pub_keys_fragment_container) != null) { + + // 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; + } + + // Create an instance of the fragment + mSelectFragment = SelectPublicKeyFragment.newInstance(selectedMasterKeyIds); + + // Add the fragment to the 'fragment_container' FrameLayout + getSupportFragmentManager().beginTransaction() + .add(R.id.api_select_pub_keys_fragment_container, mSelectFragment).commit(); + } + } else if (ACTION_ERROR_MESSAGE.equals(action)) { + String errorMessage = intent.getStringExtra(EXTRA_ERROR_MESSAGE); + + String text = "" + errorMessage + ""; + + // Inflate a "Done" custom action bar view + ActionBarHelper.setOneButtonView(getSupportActionBar(), + R.string.btn_okay, R.drawable.ic_action_done, + new View.OnClickListener() { + + @Override + public void onClick(View v) { + RemoteServiceActivity.this.setResult(RESULT_CANCELED); + RemoteServiceActivity.this.finish(); + } + }); + + setContentView(R.layout.api_remote_error_message); + + // set text on view + HtmlTextView textView = (HtmlTextView) findViewById(R.id.api_app_error_message_text); + textView.setHtmlFromString(text); + } else { + Log.e(Constants.TAG, "Action does not exist!"); + setResult(RESULT_CANCELED); + finish(); + } + } +} -- cgit v1.2.3