Java tutorial
/* Copyright 2015-2016 Dzung Tran (dzungductran@yahoo.com) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.notalenthack.blaster; import android.app.Activity; import android.app.AlarmManager; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.Button; import android.widget.ImageView; import android.widget.ListView; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.Toast; import com.notalenthack.blaster.dialog.EditCommandDialog; import com.notalenthack.blaster.dialog.LaunchCommandDialog; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.TimeZone; /** * Class that displays all the commands */ public class CommandActivity extends Activity implements EditCommandDialog.CommandCallback, View.OnClickListener, LaunchCommandDialog.LaunchCallback { private static final String TAG = "CommandActivity"; private static final boolean D = false; private EdisonDevice mDevice; // Broadcast receiver for receiving intents private BroadcastReceiver mReceiver; // fields in the layout private TextView mModelName; private TextView mCores; private TextView mCacheSize; private TextView mConnectStatus; private ImageView mDeviceStatus; private ImageView mBatteryStatus; private ListView mCmdListView; private Button mBtnExecAll; private Activity mThisActivity; private View mPrevSlideOut = null; private CommandListAdapter mListAdapter; private static BluetoothSerialService mSerialService = null; /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mThisActivity = this; final Intent launchingIntent = getIntent(); // Retrieve the Query Type and Position from the intent or bundle if (savedInstanceState != null) { mDevice = savedInstanceState.getParcelable(Constants.KEY_DEVICE_STATE); } else if (launchingIntent != null) { mDevice = launchingIntent.getParcelableExtra(Constants.KEY_DEVICE_STATE); } setContentView(R.layout.commands); // get the fields mModelName = (TextView) findViewById(R.id.modelName); mCores = (TextView) findViewById(R.id.cores); mCacheSize = (TextView) findViewById(R.id.cacheSize); mConnectStatus = (TextView) findViewById(R.id.status); mDeviceStatus = (ImageView) findViewById(R.id.deviceStatusIcon); mBatteryStatus = (ImageView) findViewById(R.id.batteryStatus); mCmdListView = (ListView) findViewById(R.id.listView); ; mBtnExecAll = (Button) findViewById(R.id.btnExecAll); if (mDevice != null) { getActionBar().setHomeButtonEnabled(true); getActionBar().setIcon(R.drawable.ic_action_navigation_previous_item); mSerialService = new BluetoothSerialService(this, mHandlerBT); mSerialService.connect(mDevice.getBluetoothDevice()); // Execute all the commands mBtnExecAll.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // do a quick status update on the commands List<Command> commands = mListAdapter.getCommands(); // this list should be in the same order as in the ListBox for (Command cmd : commands) { if (cmd.getCommandStart().equalsIgnoreCase(Command.LAUNCHER_START)) { launchCommand(cmd); } else if (!cmd.getCommandStart().equalsIgnoreCase(Command.OBEX_FTP_START)) { executeCommand(cmd); } } } }); setupCommandList(); } else { Log.e(TAG, "Bluetooth device is not initialized"); finish(); } } private void executeCommand(Command cmd) { String outType = Constants.SERIAL_TYPE_STDERR; if (cmd.getDisplayOutput()) { outType = Constants.SERIAL_TYPE_STDOUT_ERR; // capture stdout+stderr } mSerialService.sendCommand(Constants.SERIAL_CMD_START, cmd.getCommandStart(), outType); } private void setupCommandList() { mListAdapter = new CommandListAdapter(this); // read from storage and initialze the adapter. restoreCommands(); mCmdListView.setAdapter(mListAdapter); mCmdListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { handlePlayCommand(position); } }); // do a quick status update on the commands List<Command> commands = mListAdapter.getCommands(); // this list should be in the same order as in the ListBox int i = 0; for (Command cmd : commands) { mSerialService.sendStatusCommand(cmd.getCommandStat(), i, true); i++; } } // The Handler that gets information back from the BluetoothService private final Handler mHandlerBT = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case Constants.MESSAGE_STATE_CHANGE_CMD: if (D) Log.i(TAG, "MESSAGE_STATE_CHANGE: " + msg.arg1); switch (msg.arg1) { case BluetoothSerialService.STATE_CONNECTING: mDeviceStatus.setImageResource(R.drawable.ic_bluetooth_paired); mConnectStatus.setText(R.string.connecting); break; case BluetoothSerialService.STATE_CONNECTED: mDeviceStatus.setImageResource(R.drawable.ic_bluetooth_connected); mConnectStatus.setText(R.string.connected); // connected, so ask for CPU Info mSerialService.sendCommand(Constants.SERIAL_CMD_CPU_INFO); break; case BluetoothSerialService.STATE_NONE: //case BluetoothSerialService.STATE_CANTCONNECT: mDeviceStatus.setImageResource(R.drawable.ic_bluetooth_disabled); mConnectStatus.setText(R.string.none); break; } break; case Constants.MESSAGE_WRITE_CMD: // data is in if (D) { byte[] writeBuf = (byte[]) msg.obj; Log.d(TAG, "write data: " + writeBuf); } break; case Constants.MESSAGE_READ_CMD: if (D) { Log.d(TAG, "read data: " + msg.obj); } break; // command coming back from device case Constants.MESSAGE_DEVICE_SERIAL_CMD: String jsonStr = msg.getData().getString(Constants.KEY_JSON_STR); try { JSONObject jsonObject = new JSONObject(jsonStr); int cmd = jsonObject.getInt(Constants.KEY_COMMAND_TYPE); switch (cmd) { case Constants.SERIAL_CMD_ERROR: String errStr = jsonObject.getString(Constants.KEY_TOAST); Toast.makeText(getApplicationContext(), errStr, Toast.LENGTH_SHORT).show(); break; case Constants.SERIAL_CMD_CLOSE: finish(); // assuming that onDestroy is called to clean up break; case Constants.SERIAL_CMD_STATUS: int percent = jsonObject.getInt(Constants.KEY_PERCENT); int id = jsonObject.getInt(Constants.KEY_IDENTIFIER); String state = jsonObject.getString(Constants.KEY_PROCESS_STATE); boolean quick = jsonObject.getInt(Constants.KEY_QUICK_STATUS) == 1 ? true : false; if (!quick) { mListAdapter.updateCpuUsage(id, percent); } mListAdapter.updateStatus(id, getStatusFromState(state)); mListAdapter.notifyDataSetChanged(); break; case Constants.SERIAL_CMD_CPU_INFO: String modelName = jsonObject.getString(Constants.KEY_MODEL_NAME); int cores = jsonObject.getInt(Constants.KEY_CPU_CORES); String cacheSize = jsonObject.getString(Constants.KEY_CACHE_SIZE); mModelName.setText(modelName); mCores.setText(cores + " cores"); mCacheSize.setText(cacheSize + " cache"); break; } } catch (JSONException ex) { Log.e(TAG, "Invalid JSON " + ex.getMessage()); Toast.makeText(getApplicationContext(), "Invalid JSON commadn from device " + ex.getMessage(), Toast.LENGTH_LONG).show(); } break; case Constants.MESSAGE_DEVICE_NAME_CMD: // save the connected device's name String deviceName = msg.getData().getString(Constants.KEY_DEVICE_NAME); Toast.makeText(getApplicationContext(), getString(R.string.toast_connected_to) + " " + deviceName, Toast.LENGTH_SHORT).show(); break; case Constants.MESSAGE_TOAST_CMD: Toast.makeText(getApplicationContext(), msg.getData().getString(Constants.KEY_TOAST), Toast.LENGTH_LONG).show(); break; } } }; @Override protected void onSaveInstanceState(Bundle savedInstanceState) { if (mDevice != null) { savedInstanceState.putParcelable(Constants.KEY_DEVICE_STATE, mDevice); } super.onSaveInstanceState(savedInstanceState); } protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); mDevice = savedInstanceState.getParcelable(Constants.KEY_DEVICE_STATE); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater menuInflater = getMenuInflater(); menuInflater.inflate(R.menu.add_command, menu); // Calling super after populating the menu is necessary here to ensure that the // action bar helpers have a chance to handle this event. return super.onCreateOptionsMenu(menu); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { mSerialService.sendCommand(Constants.SERIAL_CMD_CLOSE); mSerialService.stop(); finish(); } else if (item.getItemId() == R.id.menu_add_command) { Command cmd = new Command(); editCommand(cmd); } return super.onOptionsItemSelected(item); } @Override public synchronized void onResume() { super.onResume(); if (mSerialService != null) { // Only if the state is STATE_NONE, do we know that we haven't started already if (mSerialService.getState() == BluetoothSerialService.STATE_NONE) { // Start the Bluetooth services mSerialService.start(); } } setupFilter(); startStatusUpdate(); } @Override protected void onDestroy() { super.onDestroy(); cancelStatusUpdate(); if (mReceiver != null) unregisterReceiver(mReceiver); mReceiver = null; if (mSerialService != null) mSerialService.stop(); } public void send(byte[] out) { out = Utils.handleEndOfLineChars(out); if (out.length > 0) { mSerialService.write(out); } } /* Mapping Linux state to our Enum status * http://man7.org/linux/man-pages/man5/proc.5.html * * R Running * S Sleeping in an interruptible wait * D Waiting in uninterruptible disk sleep * Z Zombie * T Stopped (on a signal) or (before Linux 2.6.33) trace stopped * t Tracing stop (Linux 2.6.33 onward) * X Dead (from Linux 2.6.0 onward) */ private Command.Status getStatusFromState(String state) { // only care about R, S, Z byte[] s = state.getBytes(); switch (s[0]) { case 'R': return Command.Status.RUNNING; case 'S': return Command.Status.SLEEPING; case 'Z': return Command.Status.ZOMBIE; default: return Command.Status.NOT_RUNNING; } } private void editCommand(Command command) { FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment prev = getFragmentManager().findFragmentByTag(EditCommandDialog.TAG_EDIT_COMMAND_DIALOG); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); // Create and show the dialog. DialogFragment newFragment = EditCommandDialog.newInstance(command, this); newFragment.show(ft, EditCommandDialog.TAG_EDIT_COMMAND_DIALOG); } public void launch(Command cmd, boolean bLaunch) { if (bLaunch) { Toast.makeText(getApplicationContext(), "Launch...", Toast.LENGTH_SHORT).show(); executeCommand(cmd); } } private void launchCommand(Command cmd) { FragmentTransaction ft = getFragmentManager().beginTransaction(); Fragment prev = getFragmentManager().findFragmentByTag(LaunchCommandDialog.TAG_LAUNCH_DIALOG); if (prev != null) { ft.remove(prev); } ft.addToBackStack(null); // Create and show the dialog. DialogFragment newFragment = LaunchCommandDialog.newInstance(cmd, this); newFragment.show(ft, LaunchCommandDialog.TAG_LAUNCH_DIALOG); } @Override public void onClick(View v) { Integer position = (Integer) v.getTag(); if (v.getId() == R.id.btnEditCommand) { final Command cmd = mListAdapter.getCommand(position); Log.d(TAG, "Edit button click for position " + position); //Creating the instance of PopupMenu PopupMenu popup = new PopupMenu(this, v); //Inflating the Popup using xml file popup.getMenuInflater().inflate(R.menu.edit_delete, popup.getMenu()); popup.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { if (item.getItemId() == R.id.edit) { editCommand(cmd); } else if (item.getItemId() == R.id.delete) { mListAdapter.deleteCommand(cmd); } else { return false; } saveCommands(); // update commands in pref for presistent mListAdapter.notifyDataSetChanged(); return true; } }); // show the popup popup.show(); } else if (v.getId() == R.id.btnCommandAction) { Log.d(TAG, "Play button click for position " + position); handlePlayCommand(position); } } // Callback when a command is created public void newCommand(Command command, boolean bNew) { if (D) Log.d(TAG, "command is done " + command.getName()); if (bNew) { mListAdapter.addCommand(1, command); } mListAdapter.notifyDataSetChanged(); saveCommands(); } private void handlePlayCommand(int position) { Command command = mListAdapter.getCommand(position); if (command != null) { if (D) Log.d(TAG, "command " + command.getCommandStart()); if (command.getCommandStart().equalsIgnoreCase(Command.OBEX_FTP_START)) { Intent launchingIntent = new Intent(mThisActivity, FileListActivity.class); launchingIntent.putExtra(Constants.KEY_DEVICE_STATE, mDevice); if (D) Log.d(TAG, "Launch file list screen: " + mDevice.getName()); startActivity(launchingIntent); } else { Command.Status status = command.getStatus(); String outType = Constants.SERIAL_TYPE_STDERR; if (command.getDisplayOutput()) { outType = Constants.SERIAL_TYPE_STDOUT_ERR; // capture stdout+stderr } if (status == Command.Status.NOT_RUNNING) { if (command.getCommandStart().equalsIgnoreCase(Command.LAUNCHER_START)) { launchCommand(command); } else { executeCommand(command); } } else if (status == Command.Status.ZOMBIE) { mSerialService.sendCommand(Constants.SERIAL_CMD_KILL, command.getCommandStart(), outType); } else if (status == Command.Status.RUNNING || status == Command.Status.SLEEPING) { if (command.getCommandStop().isEmpty()) { if (command.getKillMethod() == Command.KillMethod.TERMINATE) { mSerialService.sendCommand(Constants.SERIAL_CMD_TERM, command.getCommandStart(), outType); } else if (command.getKillMethod() == Command.KillMethod.KILL) { mSerialService.sendCommand(Constants.SERIAL_CMD_KILL, command.getCommandStart(), outType); } else { Toast.makeText(getApplicationContext(), "Unknown stop command: " + command.getKillMethod(), Toast.LENGTH_SHORT).show(); } } else { mSerialService.sendCommand(Constants.SERIAL_CMD_START, command.getCommandStop(), outType); } } else { Toast.makeText(getApplicationContext(), "Unknown command state: " + status, Toast.LENGTH_SHORT) .show(); return; } mSerialService.sendStatusCommand(command.getCommandStat(), position, true); } } } // Setup receiver to get message from alarm private void setupFilter() { final IntentFilter filter = new IntentFilter(); filter.addAction(Constants.ACTION_REFRESH_STATUS); mReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // Got intent to clean up if (intent.getAction().equals(Constants.ACTION_REFRESH_STATUS)) { if (D) Log.d(TAG, "onReceive intent " + intent.toString()); List<Command> commands = mListAdapter.getCommands(); // this list should be in the same order as in the ListBox int i = 0; for (Command cmd : commands) { if (cmd.getDisplayStatus()) { mSerialService.sendStatusCommand(cmd.getCommandStat(), i, false); } i++; } } } }; registerReceiver(mReceiver, filter); } private String handleFileSubstitution(String command) { if (command.contains("%f")) { return command.replace("%f", getTimeStamp(new Date())); } else { return command; } } private String getTimeStamp(Date date) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); simpleDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); return simpleDateFormat.format(date); } private void startStatusUpdate() { // Setup expiration if we never get a message from the service AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(); intent.setAction(Constants.ACTION_REFRESH_STATUS); PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); // Set repeating updating of status, will need to cancel if activity is gone Calendar cal = Calendar.getInstance(); am.setRepeating(AlarmManager.RTC_WAKEUP, cal.getTimeInMillis(), Constants.UPATE_STATUS_PERIOD * 1000, pi); } private void cancelStatusUpdate() { // Setup expiration if we never get a message from the service AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); Intent intent = new Intent(); intent.setAction(Constants.ACTION_REFRESH_STATUS); PendingIntent pi = PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); am.cancel(pi); } private void saveCommands() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences.Editor editor = preferences.edit(); editor.putString(mDevice.getAddress(), mListAdapter.getCommandsAsJSONArray().toString()); editor.commit(); } private void restoreCommands() { SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this); // Get the stored command String jsonArrStr = preferences.getString(mDevice.getAddress(), getDefaultCommands().toString()); try { JSONArray jsonArray = new JSONArray(jsonArrStr); int len = jsonArray.length(); for (int i = 0; i < len; ++i) { JSONObject json = jsonArray.getJSONObject(i); Command command = new Command(json); command.setStatus(Command.Status.NOT_RUNNING); command.setCpuUsage(0); mListAdapter.addCommand(command); } } catch (JSONException ex) { Log.e(TAG, "Bad JSON " + ex.getMessage()); } } // default set of commands private JSONArray getDefaultCommands() { Command cmd; JSONArray jsonArray = new JSONArray(); try { cmd = new Command("Download files", R.drawable.ic_sample_3, Command.OBEX_FTP_START, Command.OBEX_FTP_STOP, Command.KillMethod.NONE, Command.OBEX_FTP_STAT, false, true, true); jsonArray.put(cmd.toJSON()); cmd = new Command("Video recording", R.drawable.ic_sample_10, Command.VIDEO_START, "", Command.KillMethod.TERMINATE, "", false, false, false); jsonArray.put(cmd.toJSON()); cmd = new Command("Record GPS data", R.drawable.ic_sample_8, Command.LSM9DS0_START, Command.LSM9DS0_STOP, Command.KillMethod.NONE, Command.LSM9DS0_STAT, false, false, false); jsonArray.put(cmd.toJSON()); cmd = new Command("Launch Rocket", R.drawable.ic_launcher, Command.LAUNCHER_START, Command.LAUNCHER_STOP, Command.KillMethod.NONE, Command.LAUNCHER_STAT, false, false, false); jsonArray.put(cmd.toJSON()); } catch (JSONException ex) { Log.e(TAG, "Bad JSON object " + ex.toString()); return null; } return jsonArray; } }