package org.fdroid.fdroid.views; import android.annotation.TargetApi; import android.content.BroadcastReceiver; import android.content.ContentValues; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.net.Uri; import android.nfc.NdefMessage; import android.nfc.NfcAdapter; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.support.v4.app.NavUtils; import android.support.v4.content.LocalBroadcastManager; import android.support.v7.app.ActionBarActivity; import android.support.v7.app.AlertDialog; import android.text.TextUtils; import android.text.format.DateUtils; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.NfcHelper; import org.fdroid.fdroid.NfcNotEnabledActivity; import org.fdroid.fdroid.QrGenAsyncTask; import org.fdroid.fdroid.R; import org.fdroid.fdroid.UpdateService; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema.RepoTable; public class RepoDetailsActivity extends ActionBarActivity { private static final String TAG = "RepoDetailsActivity"; public static final String ARG_REPO_ID = "repo_id"; /** * If the repo has been updated at least once, then we will show * all of this info, otherwise they will be hidden. */ private static final int[] SHOW_IF_EXISTS = { R.id.label_repo_name, R.id.text_repo_name, R.id.text_description, R.id.label_num_apps, R.id.text_num_apps, R.id.label_last_update, R.id.text_last_update, R.id.label_username, R.id.text_username, R.id.button_edit_credentials, R.id.label_repo_fingerprint, R.id.text_repo_fingerprint, R.id.text_repo_fingerprint_description, }; /** * If the repo has <em>not</em> been updated yet, then we only show * these, otherwise they are hidden. */ private static final int[] HIDE_IF_EXISTS = { R.id.text_not_yet_updated, }; private Repo repo; private long repoId; private View repoView; /** * Help function to make switching between two view states easier. * Perhaps there is a better way to do this. I recall that using Adobe * Flex, there was a thing called "ViewStates" for exactly this. Wonder if * that exists in Android? */ private static void setMultipleViewVisibility(View parent, int[] viewIds, int visibility) { for (int viewId : viewIds) { parent.findViewById(viewId).setVisibility(visibility); } } @Override protected void onCreate(Bundle savedInstanceState) { ((FDroidApp) getApplication()).applyTheme(this); super.onCreate(savedInstanceState); getSupportActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.repodetails); repoView = findViewById(R.id.repoView); repoId = getIntent().getLongExtra(ARG_REPO_ID, 0); final String[] projection = { RepoTable.Cols.NAME, RepoTable.Cols.ADDRESS, RepoTable.Cols.FINGERPRINT, }; repo = RepoProvider.Helper.findById(this, repoId, projection); TextView inputUrl = (TextView) findViewById(R.id.input_repo_url); inputUrl.setText(repo.address); Uri uri = Uri.parse(repo.address); uri = uri.buildUpon().appendQueryParameter("fingerprint", repo.fingerprint).build(); String qrUriString = uri.toString(); new QrGenAsyncTask(this, R.id.qr_code).execute(qrUriString); } @TargetApi(14) private void setNfc() { if (NfcHelper.setPushMessage(this, Utils.getSharingUri(repo))) { findViewById(android.R.id.content).post(new Runnable() { @Override public void run() { onNewIntent(getIntent()); } }); } } @Override public void onResume() { super.onResume(); /* * After, for example, a repo update, the details will have changed in the * database. However, or local reference to the Repo object will not * have been updated. The safest way to deal with this is to reload the * repo object directly from the database. */ repo = RepoProvider.Helper.findById(this, repoId); updateRepoView(); LocalBroadcastManager.getInstance(this).registerReceiver(broadcastReceiver, new IntentFilter(UpdateService.LOCAL_ACTION_STATUS)); // FDroid.java and AppDetails set different NFC actions, so reset here setNfc(); processIntent(getIntent()); } @Override public void onNewIntent(Intent i) { // onResume gets called after this to handle the intent setIntent(i); } private void processIntent(Intent i) { if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(i.getAction())) { Parcelable[] rawMsgs = i.getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES); NdefMessage msg = (NdefMessage) rawMsgs[0]; String url = new String(msg.getRecords()[0].getPayload()); Utils.debugLog(TAG, "Got this URL: " + url); Toast.makeText(this, "Got this URL: " + url, Toast.LENGTH_LONG).show(); Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.setClass(this, ManageReposActivity.class); startActivity(intent); finish(); } } private final BroadcastReceiver broadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { int statusCode = intent.getIntExtra(UpdateService.EXTRA_STATUS_CODE, -1); if (statusCode == UpdateService.STATUS_COMPLETE_WITH_CHANGES) { updateRepoView(); } } }; @Override protected void onPause() { super.onPause(); LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiver); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.repo_details_activity, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: NavUtils.navigateUpFromSameTask(this); return true; case R.id.menu_delete: promptForDelete(); return true; case R.id.menu_enable_nfc: Intent intent = new Intent(this, NfcNotEnabledActivity.class); startActivity(intent); return true; } return super.onOptionsItemSelected(item); } @Override public boolean onPrepareOptionsMenu(Menu menu) { if (Build.VERSION.SDK_INT >= 14) { prepareNfcMenuItems(menu); } return true; } @TargetApi(16) private void prepareNfcMenuItems(Menu menu) { NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); MenuItem menuItem = menu.findItem(R.id.menu_enable_nfc); if (nfcAdapter == null) { menuItem.setVisible(false); return; } boolean needsEnableNfcMenuItem; if (Build.VERSION.SDK_INT < 16) { needsEnableNfcMenuItem = !nfcAdapter.isEnabled(); } else { needsEnableNfcMenuItem = !nfcAdapter.isNdefPushEnabled(); } menuItem.setVisible(needsEnableNfcMenuItem); } private void setupDescription(View parent, Repo repo) { TextView descriptionLabel = (TextView) parent.findViewById(R.id.label_description); TextView description = (TextView) parent.findViewById(R.id.text_description); if (TextUtils.isEmpty(repo.description)) { descriptionLabel.setVisibility(View.GONE); description.setVisibility(View.GONE); description.setText(""); } else { descriptionLabel.setVisibility(View.VISIBLE); description.setVisibility(View.VISIBLE); description.setText(repo.description.replaceAll("\n", " ")); } } private void setupRepoFingerprint(View parent, Repo repo) { TextView repoFingerprintView = (TextView) parent.findViewById(R.id.text_repo_fingerprint); TextView repoFingerprintDescView = (TextView) parent.findViewById(R.id.text_repo_fingerprint_description); String repoFingerprint; // TODO show the current state of the signature check, not just whether there is a key or not if (TextUtils.isEmpty(repo.fingerprint) && TextUtils.isEmpty(repo.signingCertificate)) { repoFingerprint = getResources().getString(R.string.unsigned); repoFingerprintView.setTextColor(getResources().getColor(R.color.unsigned)); repoFingerprintDescView.setVisibility(View.VISIBLE); repoFingerprintDescView.setText(getResources().getString(R.string.unsigned_description)); } else { // this is based on repo.fingerprint always existing, which it should repoFingerprint = Utils.formatFingerprint(this, repo.fingerprint); repoFingerprintDescView.setVisibility(View.GONE); } repoFingerprintView.setText(repoFingerprint); } private void setupCredentials(View parent, Repo repo) { TextView usernameLabel = (TextView) parent.findViewById(R.id.label_username); TextView username = (TextView) parent.findViewById(R.id.text_username); Button changePassword = (Button) parent.findViewById(R.id.button_edit_credentials); if (TextUtils.isEmpty(repo.username)) { usernameLabel.setVisibility(View.GONE); username.setVisibility(View.GONE); username.setText(""); changePassword.setVisibility(View.GONE); } else { usernameLabel.setVisibility(View.VISIBLE); username.setVisibility(View.VISIBLE); username.setText(repo.username); changePassword.setVisibility(View.VISIBLE); } } private void updateRepoView() { if (repo.hasBeenUpdated()) { updateViewForExistingRepo(repoView); } else { updateViewForNewRepo(repoView); } } private void updateViewForNewRepo(View repoView) { setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.VISIBLE); setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.GONE); } private void updateViewForExistingRepo(View repoView) { setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.VISIBLE); setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.GONE); TextView name = (TextView) repoView.findViewById(R.id.text_repo_name); TextView numApps = (TextView) repoView.findViewById(R.id.text_num_apps); TextView lastUpdated = (TextView) repoView.findViewById(R.id.text_last_update); name.setText(repo.name); int appCount = RepoProvider.Helper.countAppsForRepo(this, repoId); numApps.setText(Integer.toString(appCount)); setupDescription(repoView, repo); setupRepoFingerprint(repoView, repo); setupCredentials(repoView, repo); // Repos that existed before this feature was supported will have an // "Unknown" last update until next time they update... if (repo.lastUpdated == null) { lastUpdated.setText(R.string.unknown); } else { int format = DateUtils.isToday(repo.lastUpdated.getTime()) ? DateUtils.FORMAT_SHOW_TIME : DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_DATE; lastUpdated.setText(DateUtils.formatDateTime(this, repo.lastUpdated.getTime(), format)); } } private void promptForDelete() { new AlertDialog.Builder(this) .setTitle(R.string.repo_confirm_delete_title) .setMessage(R.string.repo_confirm_delete_body) .setPositiveButton(R.string.delete, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { RepoProvider.Helper.remove(getApplicationContext(), repoId); finish(); } }).setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // Do nothing... } } ).show(); } public void showChangePasswordDialog(final View parentView) { final View view = getLayoutInflater().inflate(R.layout.login, null); final AlertDialog credentialsDialog = new AlertDialog.Builder(this).setView(view).create(); final EditText nameInput = (EditText) view.findViewById(R.id.edit_name); final EditText passwordInput = (EditText) view.findViewById(R.id.edit_password); nameInput.setText(repo.username); passwordInput.requestFocus(); credentialsDialog.setTitle(R.string.repo_edit_credentials); credentialsDialog.setButton(DialogInterface.BUTTON_NEGATIVE, getString(R.string.cancel), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); credentialsDialog.setButton(DialogInterface.BUTTON_POSITIVE, getString(R.string.ok), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final String name = nameInput.getText().toString(); final String password = passwordInput.getText().toString(); if (!TextUtils.isEmpty(name)) { final ContentValues values = new ContentValues(2); values.put(RepoTable.Cols.USERNAME, name); values.put(RepoTable.Cols.PASSWORD, password); RepoProvider.Helper.update(RepoDetailsActivity.this, repo, values); updateRepoView(); dialog.dismiss(); } else { Toast.makeText(RepoDetailsActivity.this, R.string.repo_error_empty_username, Toast.LENGTH_LONG).show(); } } }); credentialsDialog.show(); } }