Java tutorial
/* This software is subject to the license described in the License.txt file included with this software distribution. You may not use this file except in compliance with this license. Copyright (c) Dynastream Innovations Inc. 2013 All rights reserved. */ package com.dsi.ant.antplus.pluginsampler.watchdownloader; import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.SimpleAdapter; import android.widget.TextView; import android.widget.Toast; import com.dsi.ant.antplus.pluginsampler.R; import com.dsi.ant.plugins.antplus.common.AntFsCommon.IAntFsProgressUpdateReceiver; import com.dsi.ant.plugins.antplus.common.FitFileCommon.FitFile; import com.dsi.ant.plugins.antplus.common.FitFileCommon.IFitFileDownloadedReceiver; import com.dsi.ant.plugins.antplus.pcc.AntPlusWatchDownloaderPcc; import com.dsi.ant.plugins.antplus.pcc.AntPlusWatchDownloaderPcc.DeviceInfo; import com.dsi.ant.plugins.antplus.pcc.AntPlusWatchDownloaderPcc.DeviceListUpdateCode; import com.dsi.ant.plugins.antplus.pcc.AntPlusWatchDownloaderPcc.IAvailableDeviceListReceiver; import com.dsi.ant.plugins.antplus.pcc.AntPlusWatchDownloaderPcc.IDownloadActivitiesFinishedReceiver; import com.dsi.ant.plugins.antplus.pcc.defines.AntFsRequestStatus; import com.dsi.ant.plugins.antplus.pcc.defines.AntFsState; import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState; import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult; import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IDeviceStateChangeReceiver; import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IPluginAccessResultReceiver; import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle; import com.garmin.fit.Decode; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Controls the watch download sampler */ public class Activity_WatchScanList extends FragmentActivity { AntPlusWatchDownloaderPcc watchPcc = null; PccReleaseHandle<AntPlusWatchDownloaderPcc> releaseHandle = null; TextView tv_status; List<Map<String, String>> deviceList_Display; DeviceInfo[] deviceInfoArray; SimpleAdapter adapter_deviceList_Display; ProgressDialog antFsProgressDialog; List<FitFile> fitFileList; boolean bDevicesInList; /** Called when the activity is first created. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_device_list); tv_status = (TextView) findViewById(R.id.textView_Status); deviceList_Display = new ArrayList<Map<String, String>>(); adapter_deviceList_Display = new SimpleAdapter(this, deviceList_Display, android.R.layout.simple_list_item_2, new String[] { "title", "desc" }, new int[] { android.R.id.text1, android.R.id.text2 }); ListView listView_Devices = (ListView) findViewById(R.id.listView_deviceList); listView_Devices.setAdapter(adapter_deviceList_Display); //Set the list to download the data for the selected device and display it. listView_Devices.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, final int pos, long id) { if (!bDevicesInList || watchPcc == null) return; final CharSequence[] downloadOptions = { "All Activities", "New Activities", "Wait For New Activities" }; AlertDialog.Builder builder = new AlertDialog.Builder(Activity_WatchScanList.this); builder.setTitle("Download..."); builder.setItems(downloadOptions, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int downloadSelection) { antFsProgressDialog = new ProgressDialog(Activity_WatchScanList.this); antFsProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); antFsProgressDialog.setMessage("Sending Request..."); antFsProgressDialog.setCancelable(false); antFsProgressDialog.setIndeterminate(false); fitFileList = new ArrayList<FitFile>(); if (downloadSelection == 0) // download all { if (watchPcc.requestDownloadAllActivities(deviceInfoArray[pos].getDeviceUUID(), new DownloadActivitiesFinished(pos), new FileDownloadedReceiver(), new AntFsUpdateReceiver())) { antFsProgressDialog.show(); } } else if (downloadSelection == 1) // download new { if (watchPcc.requestDownloadNewActivities(deviceInfoArray[pos].getDeviceUUID(), new DownloadActivitiesFinished(pos), new FileDownloadedReceiver(), new AntFsUpdateReceiver())) { antFsProgressDialog.show(); } } else if (downloadSelection == 2) { boolean reqSubmitted = watchPcc.listenForNewActivities( deviceInfoArray[pos].getDeviceUUID(), new IDownloadActivitiesFinishedReceiver() { @Override public void onNewDownloadActivitiesFinished(AntFsRequestStatus status) { //Only received on cancel right now, only thing to do is cancel dialog, already taken care of below } }, new FileDownloadedReceiver() { @Override public void onNewFitFileDownloaded(FitFile downloadedFitFile) { super.onNewFitFileDownloaded(downloadedFitFile); //Now show each file as we get it List<FitFile> newActivityOnly = new ArrayList<FitFile>(fitFileList); fitFileList.clear(); final Dialog_WatchData dataDialog = new Dialog_WatchData( deviceInfoArray[pos], newActivityOnly); runOnUiThread(new Runnable() { @Override public void run() { dataDialog.show(getSupportFragmentManager(), "DeviceData"); } }); } }); //Once the listener is started we leave this dialog open until it is cancelled //Note: Because the listener is an asynchronous process, you do not need to block the UI like the sample app does with this, you can leave it invisible to the user if (reqSubmitted) { AlertDialog.Builder builder = new AlertDialog.Builder(Activity_WatchScanList.this); LayoutInflater inflater = Activity_WatchScanList.this.getLayoutInflater(); // Inflate and set the layout for the dialog // Pass null as the parent view because its going in the dialog layout View detailsView = inflater.inflate(R.layout.dialog_progresswaiter, null); TextView textView_status = (TextView) detailsView .findViewById(R.id.textView_Status); textView_status.setText("Waiting for new activities on " + deviceInfoArray[pos].getDisplayName() + "..."); builder.setView(detailsView); builder.setNegativeButton("Cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { //Let onCancelListener take care of business dialog.cancel(); } }); builder.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (watchPcc != null) { watchPcc.cancelListenForNewActivities( deviceInfoArray[pos].getDeviceUUID()); } } }); AlertDialog waitDialog = builder.create(); waitDialog.show(); } } } }); AlertDialog alert = builder.create(); alert.show(); } }); resetPcc(); } /** * Resets the PCC connection to request access again and clears any existing display data. */ private void resetPcc() { //Release the old access if it exists if (releaseHandle != null) { releaseHandle.close(); } //Reset the device list display bDevicesInList = false; deviceList_Display.clear(); HashMap<String, String> listItem = new HashMap<String, String>(); listItem.put("title", "No Devices Found"); listItem.put("desc", "No results received from plugin yet..."); deviceList_Display.add(listItem); adapter_deviceList_Display.notifyDataSetChanged(); tv_status.setText("Connecting..."); //Make the access request releaseHandle = AntPlusWatchDownloaderPcc.requestDeviceListAccess(this, new IPluginAccessResultReceiver<AntPlusWatchDownloaderPcc>() { @Override public void onResultReceived(AntPlusWatchDownloaderPcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) { switch (resultCode) { case SUCCESS: watchPcc = result; tv_status.setText(result.getDeviceName() + ": " + initialDeviceState); watchPcc.requestCurrentDeviceList(); //subscribeToEvents(); break; case CHANNEL_NOT_AVAILABLE: Toast.makeText(Activity_WatchScanList.this, "Channel Not Available", Toast.LENGTH_SHORT) .show(); tv_status.setText("Error. Do Menu->Reset."); break; case ADAPTER_NOT_DETECTED: Toast.makeText(Activity_WatchScanList.this, "ANT Adapter Not Available. Built-in ANT hardware or external adapter required.", Toast.LENGTH_SHORT).show(); tv_status.setText("Error. Do Menu->Reset."); break; case BAD_PARAMS: //Note: Since we compose all the params ourself, we should never see this result Toast.makeText(Activity_WatchScanList.this, "Bad request parameters.", Toast.LENGTH_SHORT).show(); tv_status.setText("Error. Do Menu->Reset."); break; case OTHER_FAILURE: Toast.makeText(Activity_WatchScanList.this, "RequestAccess failed. See logcat for details.", Toast.LENGTH_SHORT).show(); tv_status.setText("Error. Do Menu->Reset."); break; case DEPENDENCY_NOT_INSTALLED: tv_status.setText("Error. Do Menu->Reset."); AlertDialog.Builder adlgBldr = new AlertDialog.Builder(Activity_WatchScanList.this); adlgBldr.setTitle("Missing Dependency"); adlgBldr.setMessage("The required service\n\"" + AntPlusWatchDownloaderPcc.getMissingDependencyName() + "\"\n was not found. You need to install the ANT+ Plugins service or you may need to update your existing version if you already have it. Do you want to launch the Play Store to get it?"); adlgBldr.setCancelable(true); adlgBldr.setPositiveButton("Go to Store", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent startStore = null; startStore = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + AntPlusWatchDownloaderPcc.getMissingDependencyPackageName())); startStore.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); Activity_WatchScanList.this.startActivity(startStore); } }); adlgBldr.setNegativeButton("Cancel", new OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); final AlertDialog waitDialog = adlgBldr.create(); waitDialog.show(); break; case USER_CANCELLED: tv_status.setText("Cancelled. Do Menu->Reset."); break; case UNRECOGNIZED: Toast.makeText(Activity_WatchScanList.this, "Failed: UNRECOGNIZED. PluginLib Upgrade Required?", Toast.LENGTH_SHORT).show(); tv_status.setText("Error. Do Menu->Reset."); break; default: Toast.makeText(Activity_WatchScanList.this, "Unrecognized result: " + resultCode, Toast.LENGTH_SHORT).show(); tv_status.setText("Error. Do Menu->Reset."); break; } } }, //Receives state changes and shows it on the status display line new IDeviceStateChangeReceiver() { @Override public void onDeviceStateChange(final DeviceState newDeviceState) { runOnUiThread(new Runnable() { @Override public void run() { tv_status.setText(watchPcc.getDeviceName() + ": " + newDeviceState); if (newDeviceState == DeviceState.DEAD) watchPcc = null; } }); } }, //Receives the device list updates and displays the current list new IAvailableDeviceListReceiver() { @Override public void onNewAvailableDeviceList(DeviceListUpdateCode listUpdateCode, final DeviceInfo[] deviceInfos, DeviceInfo deviceChanging) { switch (listUpdateCode) { case NO_CHANGE: case DEVICE_ADDED_TO_LIST: case DEVICE_REMOVED_FROM_LIST: // Note: This is for reference only and is not necessarily the optimal way to update. runOnUiThread(new Runnable() { @Override public void run() { deviceList_Display.clear(); if (deviceInfos.length != 0) { deviceInfoArray = deviceInfos; bDevicesInList = true; for (int i = 0; i < deviceInfos.length; ++i) { HashMap<String, String> listItem = new HashMap<String, String>(); listItem.put("title", deviceInfos[i].getDisplayName()); listItem.put("desc", Integer.toString(deviceInfos[i].getAntfsDeviceType())); deviceList_Display.add(listItem); } } else { bDevicesInList = false; HashMap<String, String> listItem = new HashMap<String, String>(); listItem.put("title", "No Devices Found"); listItem.put("desc", "No watches detected in range yet..."); deviceList_Display.add(listItem); } adapter_deviceList_Display.notifyDataSetChanged(); } }); break; case UNRECOGNIZED: runOnUiThread(new Runnable() { @Override public void run() { Toast.makeText(Activity_WatchScanList.this, "Failed: UNRECOGNIZED. PluginLib Upgrade Required?", Toast.LENGTH_SHORT) .show(); } }); break; default: // Unknown code received return; } } }); } @Override protected void onDestroy() { releaseHandle.close(); super.onDestroy(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.activity_heart_rate, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_reset: resetPcc(); tv_status.setText("Resetting..."); return true; default: return super.onOptionsItemSelected(item); } } /** * Handles receiving the download activities results, displaying the activity info on success. */ public class DownloadActivitiesFinished implements IDownloadActivitiesFinishedReceiver { private final int pos; /** * Constructor. * @param pos Index in the device array */ public DownloadActivitiesFinished(int pos) { this.pos = pos; } public void onNewDownloadActivitiesFinished(final AntFsRequestStatus status) { runOnUiThread(new Runnable() { @Override public void run() { antFsProgressDialog.dismiss(); switch (status) { case SUCCESS: final Dialog_WatchData dataDialog = new Dialog_WatchData(deviceInfoArray[pos], fitFileList); runOnUiThread(new Runnable() { @Override public void run() { dataDialog.show(getSupportFragmentManager(), "DeviceData"); } }); return; case FAIL_ALREADY_BUSY_EXTERNAL: Toast.makeText(Activity_WatchScanList.this, "Download failed, device busy.", Toast.LENGTH_SHORT).show(); break; case FAIL_DEVICE_COMMUNICATION_FAILURE: Toast.makeText(Activity_WatchScanList.this, "Download failed, communication error.", Toast.LENGTH_SHORT).show(); break; case FAIL_AUTHENTICATION_REJECTED: //NOTE: This is thrown when authentication has failed, most likely when user action is required to enable pairing Toast.makeText(Activity_WatchScanList.this, "Download failed, authentication rejected.", Toast.LENGTH_LONG).show(); break; case FAIL_DEVICE_TRANSMISSION_LOST: Toast.makeText(Activity_WatchScanList.this, "Download failed, transmission lost.", Toast.LENGTH_SHORT).show(); break; case UNRECOGNIZED: Toast.makeText(Activity_WatchScanList.this, "Failed: UNRECOGNIZED. PluginLib Upgrade Required?", Toast.LENGTH_SHORT).show(); break; default: break; } } }); } } /** * Stores the downloaded files in a list until the entire request is finished. */ public class FileDownloadedReceiver implements IFitFileDownloadedReceiver { public void onNewFitFileDownloaded(FitFile downloadedFitFile) { Log.e("WatchDownloaderSampler", "Received FIT file of size " + downloadedFitFile.getRawBytes().length); InputStream fitFile = downloadedFitFile.getInputStream(); if (!Decode.checkIntegrity(fitFile)) { Toast.makeText(Activity_WatchScanList.this, "FIT file integrity check failed.", Toast.LENGTH_SHORT) .show(); return; } fitFileList.add(downloadedFitFile); // NOTE: This sampler app caches all download data and performs all processing when the file transfer // session has finished. If dealing with larger files or a large number of files, it // may be better to store the files on the file system instead of just keeping them in memory. } } /** * Handles displaying the ANTFS status and progress in the progress dialog. */ public class AntFsUpdateReceiver implements IAntFsProgressUpdateReceiver { public void onNewAntFsProgressUpdate(final AntFsState state, final long transferredBytes, final long totalBytes) { runOnUiThread(new Runnable() { @Override public void run() { switch (state) { //In Link state and requesting to link with the device in order to pass to Auth state case LINK_REQUESTING_LINK: antFsProgressDialog.setMax(4); antFsProgressDialog.setProgress(1); antFsProgressDialog.setMessage("In Link State: Requesting Link."); break; //In Authentication state, processing authentication commands case AUTHENTICATION: antFsProgressDialog.setMax(4); antFsProgressDialog.setProgress(2); antFsProgressDialog.setMessage("In Authentication State."); break; //In Authentication state, currently attempting to pair with the device //NOTE: Feedback SHOULD be given to the user here as pairing typically requires user interaction with the device case AUTHENTICATION_REQUESTING_PAIRING: antFsProgressDialog.setMax(4); antFsProgressDialog.setProgress(2); antFsProgressDialog.setMessage("In Authentication State: User Pairing Requested."); break; //In Transport state, no requests are currently being processed case TRANSPORT_IDLE: antFsProgressDialog.setMax(4); antFsProgressDialog.setProgress(3); antFsProgressDialog.setMessage("Requesting download (In Transport State: Idle)..."); break; //In Transport state, files are currently being downloaded case TRANSPORT_DOWNLOADING: antFsProgressDialog.setMessage("In Transport State: Downloading."); antFsProgressDialog.setMax(100); if (transferredBytes >= 0 && totalBytes > 0) { int progress = (int) (transferredBytes * 100 / totalBytes); antFsProgressDialog.setProgress(progress); } break; case UNRECOGNIZED: Toast.makeText(Activity_WatchScanList.this, "Failed: UNRECOGNIZED. PluginLib Upgrade Required?", Toast.LENGTH_SHORT).show(); break; default: Log.w("WatchDownloaderSampler", "Unknown ANT-FS State Code Received: " + state); break; } } }); } } }