Java tutorial
/** * HoTT Transmitter Config * Copyright (C) 2013 Oliver Treichel * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.treichels.hott.ui.android; import de.treichels.hott.model.BaseModel; import de.treichels.hott.model.enums.ModelType; import de.treichels.hott.model.enums.Section; import de.treichels.hott.model.enums.TransmitterType; import de.treichels.hott.serial.ModelInfo; import java.net.MalformedURLException; import android.annotation.TargetApi; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.net.Uri; import android.os.Bundle; import android.print.PrintManager; import android.support.v4.widget.DrawerLayout; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.webkit.WebView; import android.widget.AdapterView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.Toast; import de.treichels.hott.android.background.serial.DeviceHandler; import de.treichels.hott.android.background.serial.bluetooth.BluetoothDeviceHandler; import de.treichels.hott.android.background.serial.tasks.GetModelFromMemoryTask; import de.treichels.hott.android.background.serial.tasks.GetModelFromSdTask; import de.treichels.hott.android.background.serial.usb.UsbDeviceHandler; import de.treichels.hott.ui.android.dialogs.AbstractTxDialog; import de.treichels.hott.ui.android.dialogs.DialogClosedListener; import de.treichels.hott.ui.android.dialogs.bluetooth.OpenFromMemoryDialogBluetooth; import de.treichels.hott.ui.android.dialogs.bluetooth.OpenFromSdDialogBluetooth; import de.treichels.hott.ui.android.dialogs.usb.OpenFromMemoryDialogUsb; import de.treichels.hott.ui.android.dialogs.usb.OpenFromSdDialogUsb; import de.treichels.hott.ui.android.html.GenerateHtmlTask; import de.treichels.hott.ui.android.html.GetModelFromUriTask; import de.treichels.hott.ui.android.html.SectionAdapter; /** * The main activity for the MdlViewer. * * @author Oliver Treichel <oli@treichels.de> */ public class MdlViewerActivity extends Activity implements ListView.OnItemClickListener { public static final String CACHE_FILE_NAME = "MdlViewer.html"; private static final String MDL_MIME_TYPE = "application/octet-stream"; private static final String MODEL_NAME = MdlViewerActivity.class.getName() + ".modelName"; private static final String MODEL_TYPE = MdlViewerActivity.class.getName() + ".modelType"; private static final int READ_REQUEST_CODE = 42; private static final String TRANSMITTER_TYPE = MdlViewerActivity.class.getName() + ".transmitterType"; /** Table of contents list */ private ListView drawer = null; /** Table of contents container */ private DrawerLayout drawerLayout = null; /** * Url to temporary file. * * This is a ugly workaround, to enable TOC navigation via {@code webView.loadUrl(loadUrl + "#" + section.name());}. It would be more elegant to navigate via * JavaScript calls. However, this is only supported on Anroid 4.4 or higher. */ private String loadUrl = null; /** Model name */ private String modelName = null; /** Model type */ private ModelType modelType = null; /** Progress Bar */ private ProgressBar progressBar = null; private Toast toast = null; /** Transmitter type */ private TransmitterType transmitterType = null; /** Html view */ private WebView webView = null; /** * Handle file selection in response to a {@link Intent.ACTION_GET_CONTENT} action intent. */ @Override public void onActivityResult(final int requestCode, final int resultCode, final Intent resultData) { if (resultCode == Activity.RESULT_OK && resultData != null && requestCode == READ_REQUEST_CODE) { updateUI(resultData.getData()); } } /** * Called when the activity is first created. * * @param savedInstanceState * If the activity is being re-initialized after previously being shut down then this Bundle contains the data it most recently supplied in * onSaveInstanceState(Bundle). <b>Note: Otherwise it is null.</b> */ @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // set the version of the application from the manifest try { final PackageManager pm = getPackageManager(); final CharSequence label = pm.getApplicationLabel(getApplicationInfo()); final PackageInfo packageInfo = pm.getPackageInfo(getPackageName(), 0); final String versionName = packageInfo.versionName; if (label != null && label.length() > 0 && versionName != null && versionName.length() > 0) { setTitle(label + " " + versionName); } } catch (final NameNotFoundException e) { // ignore } setContentView(R.layout.main); // temporary file to store html try { loadUrl = getFileStreamPath(CACHE_FILE_NAME).toURI().toURL().toString(); } catch (final MalformedURLException e) { throw new RuntimeException(e); } progressBar = (ProgressBar) findViewById(R.id.progressBar); progressBar.setVisibility(View.INVISIBLE); webView = (WebView) findViewById(R.id.webwiew); webView.getSettings().setBuiltInZoomControls(true); drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); drawer = (ListView) findViewById(R.id.drawer); drawer.setOnItemClickListener(this); final Intent intent = getIntent(); if (intent != null && intent.getAction() == Intent.ACTION_VIEW) { // activity was started via an intent updateUI(intent.getData()); } else if (savedInstanceState != null) { // restore from saved state if (savedInstanceState.containsKey(MODEL_NAME)) { modelName = savedInstanceState.getString(MODEL_NAME); } if (savedInstanceState.containsKey(MODEL_TYPE)) { modelType = ModelType.valueOf(savedInstanceState.getString(MODEL_TYPE)); } if (savedInstanceState.containsKey(TRANSMITTER_TYPE)) { transmitterType = TransmitterType.valueOf(savedInstanceState.getString(TRANSMITTER_TYPE)); } updateUI(); // } else { // fall back to load from file // performFileSearch(); } } /** * Handle menu button * * @param menu * @return */ @Override public boolean onCreateOptionsMenu(final Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.options_menu, menu); return true; } /** * Handle toc selection in drawer. * * Navigate webView to the anchor with the same name as the section in the drawer that was clicked. * * @param parent * @param view * @param position * @param id */ @Override public void onItemClick(final AdapterView<?> parent, final View view, final int position, final long id) { drawerLayout.closeDrawer(drawer); final Section section = Section.values()[(int) id]; webView.loadUrl(loadUrl + "#" + section.name()); } /** * Handle menu selection. * * Launch functionality according to menu item selected. * * @param item * @return */ @Override public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.action_print: print(); return true; case R.id.action_load_from_file: performFileSearch(); return true; case R.id.action_load_from_sd_usb: performSdSearchUsb(item); return true; case R.id.action_load_from_tx_usb: performMemorySearchUsb(item); return true; case R.id.action_load_from_sd_bt: performSdSearchBluetooth(item); return true; case R.id.action_load_from_tx_bt: performMemorySearchBluetooth(item); return true; default: return super.onOptionsItemSelected(item); } } @Override public boolean onPrepareOptionsMenu(final Menu menu) { final boolean isUsbHost = UsbDeviceHandler.isUsbHost(this); // dynamically enable/disable these menu items if devices are attached in USB host mode menu.findItem(R.id.action_load_from_sd_usb).setVisible(isUsbHost); menu.findItem(R.id.action_load_from_tx_usb).setVisible(isUsbHost); final boolean isBluetoothHost = BluetoothDeviceHandler.isBluetoothHost(this); // dynamically enable/disable these menu items if devices are attached via Bluetooth menu.findItem(R.id.action_load_from_sd_bt).setVisible(isBluetoothHost); menu.findItem(R.id.action_load_from_tx_bt).setVisible(isBluetoothHost); return super.onPrepareOptionsMenu(menu); } @Override protected void onSaveInstanceState(final Bundle outState) { super.onSaveInstanceState(outState); if (modelName != null) { outState.putString(MODEL_NAME, modelName); } if (modelType != null) { outState.putString(MODEL_TYPE, modelType.name()); } if (transmitterType != null) { outState.putString(TRANSMITTER_TYPE, transmitterType.name()); } } /** * Fires an intent to spin up the "file chooser" UI and select a file. Response will be handled by {@link MdlViewerActivity.onActivityResult()} */ public void performFileSearch() { Toast.makeText(getApplicationContext(), R.string.msg_select_mdl, Toast.LENGTH_SHORT).show(); final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType(MDL_MIME_TYPE); startActivityForResult(intent, READ_REQUEST_CODE); } public void performMemorySearch(final AbstractTxDialog<ModelInfo, ?> dialog) { dialog.setDialogClosedListener(new DialogClosedListener() { @Override public void onDialogClosed(final int resultStatus) { if (resultStatus == DialogClosedListener.OK) { final DeviceHandler<?> handler = dialog.getDeviceHandler(); final ModelInfo info = dialog.getResult(); if (info != null) { new GetModelFromMemoryTask(handler) { @Override protected void onError(final String message, final Throwable throwable) { showDialog(message, throwable); } @Override protected void onOk(final String message, final BaseModel result) { updateUI(result); } @Override protected void onPreExecute() { // tell user to be patient setWait(R.string.msg_decode_data); } }.execute(info.getModelNumber()); } } } }); try { dialog.show(getFragmentManager(), "open_from_tx"); } catch (final Exception e) { dialog.dismiss(); showDialog(e.getLocalizedMessage()); } } /** * Select models from transmitter memory * * @param item */ public void performMemorySearchBluetooth(final MenuItem item) { performMemorySearch(new OpenFromMemoryDialogBluetooth()); } public void performMemorySearchUsb(final MenuItem item) { performMemorySearch(new OpenFromMemoryDialogUsb()); } private void performSdSearch(final AbstractTxDialog<String, ?> dialog) { dialog.setDialogClosedListener(new DialogClosedListener() { @Override public void onDialogClosed(final int resultStatus) { if (resultStatus == DialogClosedListener.OK) { final DeviceHandler<?> handler = dialog.getDeviceHandler(); final String filePath = dialog.getResult(); if (filePath != null) { new GetModelFromSdTask(handler) { @Override protected void onError(final String message, final Throwable throwable) { showDialog(message, throwable); } @Override protected void onOk(final String message, final BaseModel result) { updateUI(result); } @Override protected void onPreExecute() { // tell user to be patient setWait(R.string.msg_decode_data); } }.execute(filePath); } } } }); try { dialog.show(getFragmentManager(), "open_from_sd"); } catch (final Exception e) { dialog.dismiss(); showDialog(e.getLocalizedMessage()); } } private void performSdSearchBluetooth(final MenuItem item) { performSdSearch(new OpenFromSdDialogBluetooth()); } private void performSdSearchUsb(final MenuItem item) { performSdSearch(new OpenFromSdDialogUsb()); } /** * Create a PDF version of the document. Only support on Android 4.4 and newer. */ @SuppressWarnings("deprecation") @TargetApi(19) public void print() { final PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); final String documentName = "HoTTMdlViewer - " + modelName; // deprecated method is needed for backward compatibility with Android 4.4 (api level 19) printManager.print(documentName, webView.createPrintDocumentAdapter(), null); } private void setWait(final int msgId) { if (toast != null) { toast.cancel(); } if (msgId == 0) { webView.setVisibility(View.VISIBLE); progressBar.setVisibility(View.INVISIBLE); } else { toast = Toast.makeText(MdlViewerActivity.this, msgId, Toast.LENGTH_LONG); toast.show(); webView.setVisibility(View.INVISIBLE); progressBar.setVisibility(View.VISIBLE); } } /** * Show a simple error dialog. * * @param title * @param messages */ private void showDialog(final String message) { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.msg_error); builder.setMessage(message); builder.setPositiveButton(android.R.string.ok, null); builder.create().show(); } private void showDialog(final String message, final Throwable throwable) { setWait(0); if (message != null) { showDialog(message); } else if (throwable != null) { showDialog(throwable.getLocalizedMessage()); } else { showDialog("Background task failed!"); } } /** * Reload the ui from saved state. */ public void updateUI() { if (modelName != null && modelType != null && transmitterType != null) { Log.d("updateUI()", modelName); Log.d("updateUI()", modelType.name()); Log.d("updateUI()", transmitterType.name()); setWait(R.string.msg_loading); webView.loadUrl(loadUrl); setTitle(modelName); drawer.setAdapter(new SectionAdapter(MdlViewerActivity.this, modelType, transmitterType)); // turn off busy indicator setWait(0); } } /** * Update WebView in a background thread without blocking the UI thread. Convert a {@link BaseModel} to html. */ public void updateUI(final BaseModel model) { Log.d("updateUI(BaseModel)", model.getModelName()); modelName = model.getModelName(); modelType = model.getModelType(); transmitterType = model.getTransmitterType(); // Generate HTML in background task new GenerateHtmlTask(this) { @Override protected void onError(final String message, final Throwable throwable) { showDialog(message, throwable); } @Override protected void onOk(final String message, final String result) { // update webwiew updateUI(); } @Override protected void onPreExecute() { // tell user to be patient setWait(R.string.msg_convert_to_html); } }.execute(model); } /** * Update WebView in a background thread without blocking the UI thread. Read data from {@link Uri} and decode into a {@link BaseModel}. */ public void updateUI(final Uri uri) { Log.d("updateUI(Uri)", uri.toString()); new GetModelFromUriTask(this) { @Override protected void onError(final String message, final Throwable throwable) { showDialog(message, throwable); } @Override protected void onOk(final String message, final BaseModel result) { // convert to HTML updateUI(result); } @Override protected void onPreExecute() { // tell user to be patient setWait(R.string.msg_decode_data); } }.execute(uri); } }