package com.radicaldynamic.groupinform.activities; import java.io.File; import java.util.ArrayList; import java.util.List; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.radicaldynamic.groupinform.R; import com.radicaldynamic.groupinform.application.Collect; import com.radicaldynamic.groupinform.logic.AccountDevice; import com.radicaldynamic.groupinform.logic.InformOnlineState; import com.radicaldynamic.groupinform.utilities.FileUtilsExtended; import com.radicaldynamic.groupinform.utilities.HttpUtils; public class AccountDeviceActivity extends Activity { private static final String t = "AccountDeviceActivity: "; private static final int MENU_RESET_DEVICE = 0; private static final int MENU_REMOVE_DEVICE = 1; public static final int SAVING_DIALOG = 0; public static final int REMOVING_DIALOG = 1; public static final int CONFIRM_REMOVAL_DIALOG = 2; public static final int RESET_PROGRESS_DIALOG = 3; public static final int CONFIRM_RESET_DIALOG = 4; public static final int CANNOT_RESET_OWN_DEVICE = 5; // Keys for saving and restoring activity state public static final String KEY_DEVICE_ID = "key_device_id"; // Also used to accept device ID into activity @SuppressWarnings("unused") private AlertDialog mAlertDialog; private ProgressDialog mProgressDialog; private String mDeviceId; private AccountDevice mDevice; private EditText mDeviceAlias; private EditText mDeviceEmail; private TextView mDevicePin; private TextView mDeviceCheckin; private TextView mDeviceStatus; private Spinner mDeviceRole; private ArrayAdapter<CharSequence> mDeviceRoleOptions; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.device); setTitle(getString(R.string.app_name) + " > " + getString(R.string.tf_account_device)); if (savedInstanceState == null) { Intent i = getIntent(); mDeviceId = i.getStringExtra(KEY_DEVICE_ID); } else { if (savedInstanceState.containsKey(KEY_DEVICE_ID)) mDeviceId = savedInstanceState.getString(KEY_DEVICE_ID); } mDevice = Collect.getInstance().getInformOnlineState().getAccountDevices().get(mDeviceId); mDeviceAlias = (EditText) findViewById(R.id.alias); mDeviceCheckin = (TextView) findViewById(R.id.checkin); mDeviceEmail = (EditText) findViewById(R.id.email); mDevicePin = (TextView) findViewById(R.id.pin); mDeviceRole = (Spinner) findViewById(R.id.role); mDeviceStatus = (TextView) findViewById(R.id.status); mDeviceAlias.setText(mDevice.getAlias()); mDeviceCheckin.setText(lastCheckinToString()); mDeviceEmail.setText(mDevice.getEmail()); mDevicePin.setText(mDevice.getPin()); mDeviceRoleOptions = ArrayAdapter.createFromResource(this, R.array.tf_device_roles, android.R.layout.simple_spinner_item); mDeviceRoleOptions.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mDeviceRole.setAdapter(mDeviceRoleOptions); // Initialize role field if (mDevice.getRole() == null) { // If the device role has never been set if (mDevice.getId() == Collect.getInstance().getInformOnlineState().getDeviceId()) { if (Collect.getInstance().getInformOnlineState().isAccountOwner()) { // Set to administrator if we are viewing our own device profile and we're an account owner mDeviceRole.setSelection(0); } } else { // Otherwise, default to Mobile Worker mDeviceRole.setSelection(1); } } else { if (mDevice.getRole().equals(AccountDevice.ROLE_ADMIN)) { mDeviceRole.setSelection(0); } else if (mDevice.getRole().equals(AccountDevice.ROLE_MOBILE_WORKER)) { mDeviceRole.setSelection(1); } else if (mDevice.getRole().equals(AccountDevice.ROLE_DATA_ENTRY)) { mDeviceRole.setSelection(2); } else { Log.w(Collect.LOGTAG, t + "unknown device role"); mDeviceRole.setSelection(3); } } // Initialize status field if (mDevice.getStatus().equals(AccountDevice.STATUS_ACTIVE)) mDeviceStatus.setText(getString(R.string.tf_device_admin_status_inuse)); else mDeviceStatus.setText(getString(R.string.tf_device_admin_status_unused)); // Warn user that changes cannot be saved while offline if (!Collect.getInstance().getIoService().isSignedIn()) Toast.makeText(getApplicationContext(), getString(R.string.tf_while_offline), Toast.LENGTH_LONG).show(); } /* * (non-Javadoc) * * @see android.app.Activity#onCreateDialog(int) */ @Override protected Dialog onCreateDialog(int id) { switch (id) { case SAVING_DIALOG: mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage(getText(R.string.tf_saving_please_wait)); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); return mProgressDialog; case REMOVING_DIALOG: mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage(getText(R.string.tf_removing_please_wait)); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); return mProgressDialog; case CONFIRM_REMOVAL_DIALOG: mAlertDialog = new AlertDialog.Builder(this) .setIcon(R.drawable.ic_dialog_alert) .setTitle(getString(R.string.tf_remove_device) + "?") .setMessage(R.string.tf_remove_device_dialog_msg) .setPositiveButton(R.string.tf_remove, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { new RemoveDeviceTask().execute(); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }) .show(); break; case CONFIRM_RESET_DIALOG: mAlertDialog = new AlertDialog.Builder(this) .setCancelable(false) .setIcon(R.drawable.ic_dialog_alert) .setMessage(R.string.tf_reset_warning_admin_msg) .setTitle(getString(R.string.tf_reset_device) + "?") .setPositiveButton(R.string.tf_reset, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { new ResetDeviceTask().execute(); } }) .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.cancel(); } }) .show(); break; case RESET_PROGRESS_DIALOG: mProgressDialog = new ProgressDialog(this); mProgressDialog.setMessage(getText(R.string.tf_resetting_please_wait)); mProgressDialog.setIndeterminate(true); mProgressDialog.setCancelable(false); return mProgressDialog; case CANNOT_RESET_OWN_DEVICE: mAlertDialog = new AlertDialog.Builder(this) .setCancelable(false) .setMessage(R.string.tf_unable_to_reset_self) .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { dialog.cancel(); } }) .show(); } return null; } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); boolean enabled = false; if (Collect.getInstance().getIoService().isSignedIn()) enabled = true; menu.add(0, MENU_RESET_DEVICE, 0, getString(R.string.tf_reset_device)) .setIcon(R.drawable.ic_menu_close_clear_cancel) .setEnabled(enabled); menu.add(0, MENU_REMOVE_DEVICE, 0, getString(R.string.tf_remove_device)) .setIcon(R.drawable.ic_menu_delete) .setEnabled(enabled); return true; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: if (Collect.getInstance().getIoService().isSignedIn()) new UpdateDeviceInfoTask().execute(); else finish(); return true; } return super.onKeyDown(keyCode, event); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_RESET_DEVICE: showDialog(CONFIRM_RESET_DIALOG); return true; case MENU_REMOVE_DEVICE: showDialog(CONFIRM_REMOVAL_DIALOG); return true; } return super.onOptionsItemSelected(item); } @Override public void onSaveInstanceState(Bundle savedInstanceState) { savedInstanceState.putString(KEY_DEVICE_ID, mDeviceId); super.onSaveInstanceState(savedInstanceState); } private class RemoveDeviceTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... nothing) { String removeUrl = Collect.getInstance().getInformOnlineState().getServerUrl() + "/device/remove/" + mDeviceId; return HttpUtils.getUrlData(removeUrl); } @Override protected void onPreExecute() { showDialog(REMOVING_DIALOG); } @Override protected void onPostExecute(String getResult) { mProgressDialog.cancel(); JSONObject update; try { if (Collect.Log.DEBUG) Log.d(Collect.LOGTAG, t + "parsing getResult " + getResult); update = (JSONObject) new JSONTokener(getResult).nextValue(); String result = update.optString(InformOnlineState.RESULT, InformOnlineState.ERROR); // Update successful if (result.equals(InformOnlineState.OK)) { Toast.makeText(getApplicationContext(), getString(R.string.tf_removed_with_param, mDevice.getDisplayName()), Toast.LENGTH_SHORT).show(); // Force the list to refresh (do not be destructive in case something bad happens later) new File(getCacheDir(), FileUtilsExtended.DEVICE_CACHE_FILE).setLastModified(0); // Get out of here finish(); } else if (result.equals(InformOnlineState.FAILURE)) { // TODO: user tried to remove self is the only possible failure (implement at some point) Toast.makeText(getApplicationContext(), getString(R.string.tf_unable_to_remove_self), Toast.LENGTH_LONG).show(); } else { // Something bad happened if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "system error while processing getResult"); Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); } } catch (NullPointerException e) { // Communication error if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "no getResult to parse. Communication error with node.js server?"); Toast.makeText(getApplicationContext(), getString(R.string.tf_communication_error_try_again), Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (JSONException e) { // Parse error (malformed result) if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "failed to parse getResult " + getResult); Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); e.printStackTrace(); } } } public class ResetDeviceTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) { String url = Collect.getInstance().getInformOnlineState().getServerUrl() + "/device/reset/" + mDeviceId; return HttpUtils.getUrlData(url); } @Override protected void onPreExecute() { showDialog(RESET_PROGRESS_DIALOG); } @Override protected void onPostExecute(String getResult) { mProgressDialog.cancel(); JSONObject reset; try { if (Collect.Log.DEBUG) Log.d(Collect.LOGTAG, t + "parsing getResult " + getResult); reset = (JSONObject) new JSONTokener(getResult).nextValue(); String result = reset.optString(InformOnlineState.RESULT, InformOnlineState.ERROR); if (result.equals(InformOnlineState.OK)) { Toast.makeText(getApplicationContext(), getString(R.string.tf_reset_with_param, mDevice.getDisplayName()), Toast.LENGTH_SHORT).show(); // Force the list to refresh (do not be destructive in case something bad happens later) new File(getCacheDir(), FileUtilsExtended.DEVICE_CACHE_FILE).setLastModified(0); // Get out of here finish(); } else if (result.equals(InformOnlineState.FAILURE)) { showDialog(CANNOT_RESET_OWN_DEVICE); } else { Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "system error while processing getResult"); } } catch (NullPointerException e) { // Communication error Toast.makeText(getApplicationContext(), getString(R.string.tf_communication_error_try_again), Toast.LENGTH_LONG).show(); if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "no getResult to parse. Communication error with node.js server?"); e.printStackTrace(); } catch (JSONException e) { // Parse error (malformed result) Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "failed to parse getResult " + getResult); e.printStackTrace(); } } } private class UpdateDeviceInfoTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... nothing) { List<NameValuePair> params = new ArrayList<NameValuePair>(); params.add(new BasicNameValuePair("deviceId", mDevice.getId())); params.add(new BasicNameValuePair("alias", mDeviceAlias.getText().toString().trim())); params.add(new BasicNameValuePair("email", mDeviceEmail.getText().toString().trim())); switch (mDeviceRole.getSelectedItemPosition()) { case 0: params.add(new BasicNameValuePair("role", AccountDevice.ROLE_ADMIN)); break; case 1: params.add(new BasicNameValuePair("role", AccountDevice.ROLE_MOBILE_WORKER)); break; case 2: params.add(new BasicNameValuePair("role", AccountDevice.ROLE_DATA_ENTRY)); break; case 3: params.add(new BasicNameValuePair("role", AccountDevice.ROLE_UNASSIGNED)); break; } String updateUrl = Collect.getInstance().getInformOnlineState().getServerUrl() + "/device/update"; return HttpUtils.postUrlData(updateUrl, params); } @Override protected void onPreExecute() { showDialog(SAVING_DIALOG); } @Override protected void onPostExecute(String postResult) { JSONObject update; mProgressDialog.cancel(); try { update = (JSONObject) new JSONTokener(postResult).nextValue(); String result = update.optString(InformOnlineState.RESULT, InformOnlineState.FAILURE); // Update successful if (result.equals(InformOnlineState.OK)) { Toast.makeText(getApplicationContext(), getString(R.string.tf_saved_data), Toast.LENGTH_SHORT).show(); // Force the list to refresh (do not be destructive in case something bad happens later) new File(getCacheDir(), FileUtilsExtended.DEVICE_CACHE_FILE).setLastModified(0); // Commit changes to the cache-in-memory to avoid running InformOnlineService.loadDeviceHash() Collect.getInstance().getInformOnlineState().getAccountDevices().get(mDeviceId).setAlias(mDeviceAlias.getText().toString().trim()); Collect.getInstance().getInformOnlineState().getAccountDevices().get(mDeviceId).setEmail(mDeviceEmail.getText().toString().trim()); } else if (result.equals(InformOnlineState.FAILURE)) { // Update failed because of something the user did String reason = update.optString(InformOnlineState.REASON, ClientRegistrationActivity.REASON_UNKNOWN); if (reason.equals(ClientRegistrationActivity.REASON_INVALID_EMAIL)) { Toast.makeText(getApplicationContext(), getString(R.string.tf_invalid_email), Toast.LENGTH_LONG).show(); } else if (reason.equals(ClientRegistrationActivity.REASON_EMAIL_ASSIGNED)) { Toast.makeText(getApplicationContext(), getString(R.string.tf_registration_error_email_in_use), Toast.LENGTH_LONG).show(); } else { // Unhandled response if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "system error while processing postResult"); Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); } } else { // Something bad happened if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "system error while processing postResult"); Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); } } catch (NullPointerException e) { // Communication error if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "no postResult to parse. Communication error with node.js server?"); Toast.makeText(getApplicationContext(), getString(R.string.tf_communication_error_try_again), Toast.LENGTH_LONG).show(); e.printStackTrace(); } catch (JSONException e) { // Parse error (malformed result) if (Collect.Log.ERROR) Log.e(Collect.LOGTAG, t + "failed to parse postResult " + postResult); Toast.makeText(getApplicationContext(), getString(R.string.tf_system_error_dialog_msg), Toast.LENGTH_LONG).show(); e.printStackTrace(); } finally { // Get out of here -- don't trap a user who may have become disconnected finish(); } } } // Derive a string from the lastCheckin time private String lastCheckinToString() { // lastCheck is delivered in milliseconds Integer lastCheckin; try { lastCheckin = Integer.valueOf(mDevice.getLastCheckin()) / 1000; } catch (NumberFormatException e) { // Not sure why we run into problems with this lastCheckin = 0; } String approximation = ""; String period = ""; String unit = ""; if (lastCheckin / 86400 > 0) { period = Integer.toString(lastCheckin / 86400); approximation = "About "; unit = " days"; } else if ((lastCheckin % 86400) / 3600 > 0) { period = Integer.toString((lastCheckin % 86400) / 3600); approximation = "About "; unit = " hours"; } else if (((lastCheckin % 86400) % 3600) / 60 > 0) { period = Integer.toString(((lastCheckin % 86400) % 3600) / 60); unit = " minutes"; } else if ((lastCheckin % 86400) % 3600 > 0) { period = Integer.toString((lastCheckin % 86400) % 3600); unit = " seconds"; } else { approximation = getString(R.string.tf_unavailable); } // Hack to turn "minutes" into "minute" or whatever if (period.equals("1")) { unit = unit.substring(0, unit.length() - 1); } String text = approximation + period + unit; if (!text.equals(getString(R.string.tf_unavailable).toString())) { text = text + " ago"; } return text; } }