diff --git a/.idea/misc.xml b/.idea/misc.xml index 047238c..1011831 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -39,7 +39,7 @@ - + diff --git a/README.md b/README.md index cacaad0..6333c0a 100644 --- a/README.md +++ b/README.md @@ -96,12 +96,12 @@ and finally: port.close(); ``` - For a simple example, see [UsbSerialExamples](https://github.com/mik3y/usb-serial-for-android/blob/master/usbSerialExamples) folder in this project. -For a more complete example, see separate github project +For a more complete example with background service to stay connected while +the app is not visible or rotating, see separate github project [SimpleUsbTerminal](https://github.com/kai-morich/SimpleUsbTerminal). ## Probing for Unrecognized Devices diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..399f577 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +#android.enableJetifier=true +android.useAndroidX=true diff --git a/usbSerialExamples/build.gradle b/usbSerialExamples/build.gradle index 5f27979..7fdae7e 100644 --- a/usbSerialExamples/build.gradle +++ b/usbSerialExamples/build.gradle @@ -4,6 +4,11 @@ android { compileSdkVersion 28 buildToolsVersion '28.0.3' + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + defaultConfig { minSdkVersion 17 targetSdkVersion 28 @@ -21,4 +26,6 @@ android { dependencies { implementation project(':usbSerialForAndroid') + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'com.google.android.material:material:1.1.0' } diff --git a/usbSerialExamples/src/main/AndroidManifest.xml b/usbSerialExamples/src/main/AndroidManifest.xml index efa205f..d264658 100644 --- a/usbSerialExamples/src/main/AndroidManifest.xml +++ b/usbSerialExamples/src/main/AndroidManifest.xml @@ -1,37 +1,33 @@ - - + xmlns:tools="http://schemas.android.com/tools" + package="com.hoho.android.usbserial.examples"> + + android:icon="@mipmap/ic_launcher" + android:label="@string/app_name" + android:theme="@style/AppTheme" + tools:ignore="AllowBackup,GoogleAppIndexingWarning"> + + android:launchMode="singleTask" + android:windowSoftInputMode="stateHidden|adjustResize"> - - - - - - - \ No newline at end of file + diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java new file mode 100644 index 0000000..0447317 --- /dev/null +++ b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/CustomProber.java @@ -0,0 +1,21 @@ +package com.hoho.android.usbserial.examples; + +import com.hoho.android.usbserial.driver.CdcAcmSerialDriver; +import com.hoho.android.usbserial.driver.ProbeTable; +import com.hoho.android.usbserial.driver.UsbSerialProber; + +/** + * add devices here, that are not known to DefaultProber + * + * if the App should auto start for these devices, also + * add IDs to app/src/main/res/xml/device_filter.xml + */ +class CustomProber { + + static UsbSerialProber getCustomProber() { + ProbeTable customTable = new ProbeTable(); + customTable.addProduct(0x16d0, 0x087e, CdcAcmSerialDriver.class); // e.g. Digispark CDC + return new UsbSerialProber(customTable); + } + +} diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/DeviceListActivity.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/DeviceListActivity.java deleted file mode 100644 index a69a3c0..0000000 --- a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/DeviceListActivity.java +++ /dev/null @@ -1,233 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.hoho.android.usbserial.examples; - -import android.app.Activity; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.hardware.usb.UsbDevice; -import android.hardware.usb.UsbManager; -import android.os.AsyncTask; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; -import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.ListView; -import android.widget.ProgressBar; -import android.widget.TextView; -import android.widget.Toast; -import android.widget.TwoLineListItem; - -import com.hoho.android.usbserial.driver.UsbSerialDriver; -import com.hoho.android.usbserial.driver.UsbSerialPort; -import com.hoho.android.usbserial.driver.UsbSerialProber; - -import java.util.ArrayList; -import java.util.List; - -/** - * Shows a {@link ListView} of available USB devices. - * - * @author mike wakerly (opensource@hoho.com) - */ -public class DeviceListActivity extends Activity { - - private final String TAG = DeviceListActivity.class.getSimpleName(); - - private UsbManager mUsbManager; - private UsbSerialPort mSerialPort; - private ListView mListView; - private TextView mProgressBarTitle; - private ProgressBar mProgressBar; - - private static final int MESSAGE_REFRESH = 101; - private static final long REFRESH_TIMEOUT_MILLIS = 5000; - public static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB"; - - private final Handler mHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MESSAGE_REFRESH: - refreshDeviceList(); - mHandler.sendEmptyMessageDelayed(MESSAGE_REFRESH, REFRESH_TIMEOUT_MILLIS); - break; - default: - super.handleMessage(msg); - break; - } - } - - }; - private BroadcastReceiver mUsbReceiver; - - private List mEntries = new ArrayList(); - private ArrayAdapter mAdapter; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.main); - final Context context = this; - - mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - mListView = findViewById(R.id.deviceList); - mProgressBar = findViewById(R.id.progressBar); - mProgressBarTitle = findViewById(R.id.progressBarTitle); - - mUsbReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - if(intent.getAction().equals(INTENT_ACTION_GRANT_USB)) { - if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { - showConsoleActivity(mSerialPort); - } else { - Toast.makeText(context, "USB permission denied", Toast.LENGTH_SHORT).show(); - } - } - } - }; - - mAdapter = new ArrayAdapter(this, - android.R.layout.simple_expandable_list_item_2, mEntries) { - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final TwoLineListItem row; - if (convertView == null){ - final LayoutInflater inflater = - (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); - row = (TwoLineListItem) inflater.inflate(android.R.layout.simple_list_item_2, null); - } else { - row = (TwoLineListItem) convertView; - } - - final UsbSerialPort port = mEntries.get(position); - final UsbSerialDriver driver = port.getDriver(); - final UsbDevice device = driver.getDevice(); - - final String title = String.format("Vendor %4X Product %4X", device.getVendorId(), device.getProductId()); - row.getText1().setText(title); - - final String subtitle = driver.getClass().getSimpleName(); - row.getText2().setText(subtitle); - - return row; - } - - }; - mListView.setAdapter(mAdapter); - - mListView.setOnItemClickListener(new ListView.OnItemClickListener() { - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - Log.d(TAG, "Pressed item " + position); - if (position >= mEntries.size()) { - Log.w(TAG, "Illegal position."); - return; - } - - mSerialPort = mEntries.get(position); - UsbDevice device = mSerialPort.getDriver().getDevice(); - if (!mUsbManager.hasPermission(device)) { - PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(INTENT_ACTION_GRANT_USB), 0); - mUsbManager.requestPermission(device, usbPermissionIntent); - } else { - showConsoleActivity(mSerialPort); - } - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - mHandler.sendEmptyMessage(MESSAGE_REFRESH); - registerReceiver(mUsbReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); - } - - @Override - protected void onPause() { - super.onPause(); - mHandler.removeMessages(MESSAGE_REFRESH); - unregisterReceiver(mUsbReceiver); - } - - private void refreshDeviceList() { - showProgressBar(); - - new AsyncTask>() { - @Override - protected List doInBackground(Void... params) { - Log.d(TAG, "Refreshing device list ..."); - SystemClock.sleep(1000); - - final List drivers = - UsbSerialProber.getDefaultProber().findAllDrivers(mUsbManager); - - final List result = new ArrayList(); - for (final UsbSerialDriver driver : drivers) { - final List ports = driver.getPorts(); - Log.d(TAG, String.format("+ %s: %s port%s", - driver, Integer.valueOf(ports.size()), ports.size() == 1 ? "" : "s")); - result.addAll(ports); - } - - return result; - } - - @Override - protected void onPostExecute(List result) { - mEntries.clear(); - mEntries.addAll(result); - mAdapter.notifyDataSetChanged(); - mProgressBarTitle.setText( - String.format("%s device(s) found",Integer.valueOf(mEntries.size()))); - hideProgressBar(); - Log.d(TAG, "Done refreshing, " + mEntries.size() + " entries found."); - } - - }.execute((Void) null); - } - - private void showProgressBar() { - mProgressBar.setVisibility(View.VISIBLE); - mProgressBarTitle.setText(R.string.refreshing); - } - - private void hideProgressBar() { - mProgressBar.setVisibility(View.INVISIBLE); - } - - private void showConsoleActivity(UsbSerialPort port) { - SerialConsoleActivity.show(this, port); - } - -} diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/DevicesFragment.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/DevicesFragment.java new file mode 100644 index 0000000..adfc38b --- /dev/null +++ b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/DevicesFragment.java @@ -0,0 +1,151 @@ +package com.hoho.android.usbserial.examples; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.ListFragment; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.TextView; +import android.widget.Toast; + +import com.hoho.android.usbserial.driver.UsbSerialDriver; +import com.hoho.android.usbserial.driver.UsbSerialProber; + +import java.util.ArrayList; +import java.util.Locale; + +public class DevicesFragment extends ListFragment { + + class ListItem { + UsbDevice device; + int port; + UsbSerialDriver driver; + + ListItem(UsbDevice device, int port, UsbSerialDriver driver) { + this.device = device; + this.port = port; + this.driver = driver; + } + } + + private ArrayList listItems = new ArrayList<>(); + private ArrayAdapter listAdapter; + private int baudRate = 19200; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + listAdapter = new ArrayAdapter(getActivity(), 0, listItems) { + @Override + public View getView(int position, View view, ViewGroup parent) { + ListItem item = listItems.get(position); + if (view == null) + view = getActivity().getLayoutInflater().inflate(R.layout.device_list_item, parent, false); + TextView text1 = view.findViewById(R.id.text1); + TextView text2 = view.findViewById(R.id.text2); + if(item.driver == null) + text1.setText(""); + else if(item.driver.getPorts().size() == 1) + text1.setText(item.driver.getClass().getSimpleName().replace("SerialDriver","")); + else + text1.setText(item.driver.getClass().getSimpleName().replace("SerialDriver","")+", Port "+item.port); + text2.setText(String.format(Locale.US, "Vendor %04X, Product %04X", item.device.getVendorId(), item.device.getProductId())); + return view; + } + }; + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + setListAdapter(null); + View header = getActivity().getLayoutInflater().inflate(R.layout.device_list_header, null, false); + getListView().addHeaderView(header, null, false); + setEmptyText(""); + ((TextView) getListView().getEmptyView()).setTextSize(18); + setListAdapter(listAdapter); + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_devices, menu); + } + + @Override + public void onResume() { + super.onResume(); + refresh(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if(id == R.id.refresh) { + refresh(); + return true; + } else if (id ==R.id.baud_rate) { + final String[] baudRates = getResources().getStringArray(R.array.baud_rates); + int pos = java.util.Arrays.asList(baudRates).indexOf(String.valueOf(baudRate)); + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle("Baud rate"); + builder.setSingleChoiceItems(baudRates, pos, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + baudRate = Integer.valueOf(baudRates[item]); + dialog.dismiss(); + } + }); + builder.create().show(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + void refresh() { + UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE); + UsbSerialProber usbDefaultProber = UsbSerialProber.getDefaultProber(); + UsbSerialProber usbCustomProber = CustomProber.getCustomProber(); + listItems.clear(); + for(UsbDevice device : usbManager.getDeviceList().values()) { + UsbSerialDriver driver = usbDefaultProber.probeDevice(device); + if(driver == null) { + driver = usbCustomProber.probeDevice(device); + } + if(driver != null) { + for(int port = 0; port < driver.getPorts().size(); port++) + listItems.add(new ListItem(device, port, driver)); + } else { + listItems.add(new ListItem(device, 0, null)); + } + } + listAdapter.notifyDataSetChanged(); + } + + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + ListItem item = listItems.get(position-1); + if(item.driver == null) { + Toast.makeText(getActivity(), "no driver", Toast.LENGTH_SHORT).show(); + } else { + Bundle args = new Bundle(); + args.putInt("device", item.device.getDeviceId()); + args.putInt("port", item.port); + args.putInt("baud", baudRate); + Fragment fragment = new TerminalFragment(); + fragment.setArguments(args); + getFragmentManager().beginTransaction().replace(R.id.fragment, fragment, "terminal").addToBackStack(null).commit(); + } + } + +} diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/MainActivity.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/MainActivity.java new file mode 100644 index 0000000..27d88e5 --- /dev/null +++ b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/MainActivity.java @@ -0,0 +1,45 @@ +package com.hoho.android.usbserial.examples; + +import android.content.Intent; +import android.os.Bundle; +import androidx.fragment.app.FragmentManager; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; + +public class MainActivity extends AppCompatActivity implements FragmentManager.OnBackStackChangedListener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + getSupportFragmentManager().addOnBackStackChangedListener(this); + if (savedInstanceState == null) + getSupportFragmentManager().beginTransaction().add(R.id.fragment, new DevicesFragment(), "devices").commit(); + else + onBackStackChanged(); + } + + @Override + public void onBackStackChanged() { + getSupportActionBar().setDisplayHomeAsUpEnabled(getSupportFragmentManager().getBackStackEntryCount()>0); + } + + @Override + public boolean onSupportNavigateUp() { + onBackPressed(); + return true; + } + + @Override + protected void onNewIntent(Intent intent) { + if(intent.getAction().equals("android.hardware.usb.action.USB_DEVICE_ATTACHED")) { + TerminalFragment terminal = (TerminalFragment)getSupportFragmentManager().findFragmentByTag("terminal"); + if (terminal != null) + terminal.status("USB device detected"); + } + super.onNewIntent(intent); + } + +} diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/SerialConsoleActivity.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/SerialConsoleActivity.java deleted file mode 100644 index 1a7d71d..0000000 --- a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/SerialConsoleActivity.java +++ /dev/null @@ -1,230 +0,0 @@ -/* Copyright 2011-2013 Google Inc. - * Copyright 2013 mike wakerly - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library 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 - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, - * USA. - * - * Project home page: https://github.com/mik3y/usb-serial-for-android - */ - -package com.hoho.android.usbserial.examples; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.hardware.usb.UsbDeviceConnection; -import android.hardware.usb.UsbManager; -import android.os.Bundle; -import android.util.Log; -import android.widget.CheckBox; -import android.widget.CompoundButton; -import android.widget.ScrollView; -import android.widget.TextView; - -import com.hoho.android.usbserial.driver.UsbSerialPort; -import com.hoho.android.usbserial.util.HexDump; -import com.hoho.android.usbserial.util.SerialInputOutputManager; - -import java.io.IOException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; - -/** - * Monitors a single {@link UsbSerialPort} instance, showing all data - * received. - * - * @author mike wakerly (opensource@hoho.com) - */ -public class SerialConsoleActivity extends Activity { - - private final String TAG = SerialConsoleActivity.class.getSimpleName(); - - /** - * Driver instance, passed in statically via - * {@link #show(Context, UsbSerialPort)}. - * - *

- * This is a devious hack; it'd be cleaner to re-create the driver using - * arguments passed in with the {@link #startActivity(Intent)} intent. We - * can get away with it because both activities will run in the same - * process, and this is a simple demo. - */ - private static UsbSerialPort sPort = null; - - private TextView mTitleTextView; - private TextView mDumpTextView; - private ScrollView mScrollView; - private CheckBox chkDTR; - private CheckBox chkRTS; - - private final ExecutorService mExecutor = Executors.newSingleThreadExecutor(); - - private SerialInputOutputManager mSerialIoManager; - - private final SerialInputOutputManager.Listener mListener = - new SerialInputOutputManager.Listener() { - - @Override - public void onRunError(Exception e) { - Log.d(TAG, "Runner stopped."); - } - - @Override - public void onNewData(final byte[] data) { - SerialConsoleActivity.this.runOnUiThread(new Runnable() { - @Override - public void run() { - SerialConsoleActivity.this.updateReceivedData(data); - } - }); - } - }; - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.serial_console); - mTitleTextView = (TextView) findViewById(R.id.demoTitle); - mDumpTextView = (TextView) findViewById(R.id.consoleText); - mScrollView = (ScrollView) findViewById(R.id.demoScroller); - chkDTR = (CheckBox) findViewById(R.id.checkBoxDTR); - chkRTS = (CheckBox) findViewById(R.id.checkBoxRTS); - - chkDTR.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - try { - sPort.setDTR(isChecked); - }catch (IOException x){} - } - }); - - chkRTS.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - try { - sPort.setRTS(isChecked); - }catch (IOException x){} - } - }); - - } - - - @Override - protected void onPause() { - super.onPause(); - stopIoManager(); - if (sPort != null) { - try { - sPort.close(); - } catch (IOException e) { - // Ignore. - } - sPort = null; - } - finish(); - } - - void showStatus(TextView theTextView, String theLabel, boolean theValue){ - String msg = theLabel + ": " + (theValue ? "enabled" : "disabled") + "\n"; - theTextView.append(msg); - } - - @Override - protected void onResume() { - super.onResume(); - Log.d(TAG, "Resumed, port=" + sPort); - if (sPort == null) { - mTitleTextView.setText("No serial device."); - } else { - final UsbManager usbManager = (UsbManager) getSystemService(Context.USB_SERVICE); - - UsbDeviceConnection connection = usbManager.openDevice(sPort.getDriver().getDevice()); - if (connection == null) { - mTitleTextView.setText("Opening device failed"); - return; - } - - try { - sPort.open(connection); - sPort.setParameters(115200, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); - - showStatus(mDumpTextView, "CD - Carrier Detect", sPort.getCD()); - showStatus(mDumpTextView, "CTS - Clear To Send", sPort.getCTS()); - showStatus(mDumpTextView, "DSR - Data Set Ready", sPort.getDSR()); - showStatus(mDumpTextView, "DTR - Data Terminal Ready", sPort.getDTR()); - showStatus(mDumpTextView, "DSR - Data Set Ready", sPort.getDSR()); - showStatus(mDumpTextView, "RI - Ring Indicator", sPort.getRI()); - showStatus(mDumpTextView, "RTS - Request To Send", sPort.getRTS()); - - } catch (IOException e) { - Log.e(TAG, "Error setting up device: " + e.getMessage(), e); - mTitleTextView.setText("Error opening device: " + e.getMessage()); - try { - sPort.close(); - } catch (IOException e2) { - // Ignore. - } - sPort = null; - return; - } - mTitleTextView.setText("Serial device: " + sPort.getClass().getSimpleName()); - } - onDeviceStateChange(); - } - - private void stopIoManager() { - if (mSerialIoManager != null) { - Log.i(TAG, "Stopping io manager .."); - mSerialIoManager.stop(); - mSerialIoManager = null; - } - } - - private void startIoManager() { - if (sPort != null) { - Log.i(TAG, "Starting io manager .."); - mSerialIoManager = new SerialInputOutputManager(sPort, mListener); - mExecutor.submit(mSerialIoManager); - } - } - - private void onDeviceStateChange() { - stopIoManager(); - startIoManager(); - } - - private void updateReceivedData(byte[] data) { - final String message = "Read " + data.length + " bytes: \n" - + HexDump.dumpHexString(data) + "\n\n"; - mDumpTextView.append(message); - mScrollView.smoothScrollTo(0, mDumpTextView.getBottom()); - } - - /** - * Starts the activity, using the supplied driver instance. - * - * @param context - * @param driver - */ - static void show(Context context, UsbSerialPort port) { - sPort = port; - final Intent intent = new Intent(context, SerialConsoleActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NO_HISTORY); - context.startActivity(intent); - } - -} diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/TerminalFragment.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/TerminalFragment.java new file mode 100644 index 0000000..e4b18f7 --- /dev/null +++ b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/examples/TerminalFragment.java @@ -0,0 +1,315 @@ +package com.hoho.android.usbserial.examples; + +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.hardware.usb.UsbDevice; +import android.hardware.usb.UsbDeviceConnection; +import android.hardware.usb.UsbManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.method.ScrollingMovementMethod; +import android.text.style.ForegroundColorSpan; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; +import android.widget.ToggleButton; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.hoho.android.usbserial.driver.UsbSerialDriver; +import com.hoho.android.usbserial.driver.UsbSerialPort; +import com.hoho.android.usbserial.driver.UsbSerialProber; +import com.hoho.android.usbserial.util.HexDump; +import com.hoho.android.usbserial.util.SerialInputOutputManager; + +import java.io.IOException; +import java.util.concurrent.Executors; + +public class TerminalFragment extends Fragment implements SerialInputOutputManager.Listener { + + private enum UsbPermission { Unknown, Requested, Granted, Denied }; + + private static final String INTENT_ACTION_GRANT_USB = BuildConfig.APPLICATION_ID + ".GRANT_USB"; + private static final int WRITE_WAIT_MILLIS = 2000; + + private int deviceId, portNum, baudRate; + + private BroadcastReceiver broadcastReceiver; + private Handler mainLooper; + private TextView receiveText; + private ControlLines controlLines; + + private SerialInputOutputManager usbIoManager; + private UsbSerialPort usbSerialPort; + private UsbPermission usbPermission = UsbPermission.Unknown; + private boolean connected = false; + + public TerminalFragment() { + broadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if(intent.getAction().equals(INTENT_ACTION_GRANT_USB)) { + usbPermission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false) + ? UsbPermission.Granted : UsbPermission.Denied; + connect(); + } + } + }; + mainLooper = new Handler(Looper.getMainLooper()); + } + + /* + * Lifecycle + */ + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + setRetainInstance(true); + deviceId = getArguments().getInt("device"); + portNum = getArguments().getInt("port"); + baudRate = getArguments().getInt("baud"); + } + + @Override + public void onResume() { + super.onResume(); + getActivity().registerReceiver(broadcastReceiver, new IntentFilter(INTENT_ACTION_GRANT_USB)); + + if(usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted) + mainLooper.post(this::connect); + } + + @Override + public void onPause() { + if(connected) { + status("disconnected"); + disconnect(); + } + getActivity().unregisterReceiver(broadcastReceiver); + super.onPause(); + } + + /* + * UI + */ + @Override + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_terminal, container, false); + receiveText = view.findViewById(R.id.receive_text); // TextView performance decreases with number of spans + receiveText.setTextColor(getResources().getColor(R.color.colorRecieveText)); // set as default color to reduce number of spans + receiveText.setMovementMethod(ScrollingMovementMethod.getInstance()); + TextView sendText = view.findViewById(R.id.send_text); + View sendBtn = view.findViewById(R.id.send_btn); + sendBtn.setOnClickListener(v -> send(sendText.getText().toString())); + controlLines = new ControlLines(view); + return view; + } + + @Override + public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) { + inflater.inflate(R.menu.menu_terminal, menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.clear) { + receiveText.setText(""); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + /* + * Serial + */ + @Override + public void onNewData(byte[] data) { + mainLooper.post(() -> { + receive(data); + }); + } + + @Override + public void onRunError(Exception e) { + mainLooper.post(() -> { + status("connection lost: " + e.getMessage()); + disconnect(); + }); + } + + /* + * Serial + UI + */ + private void connect() { + UsbDevice device = null; + UsbManager usbManager = (UsbManager) getActivity().getSystemService(Context.USB_SERVICE); + for(UsbDevice v : usbManager.getDeviceList().values()) + if(v.getDeviceId() == deviceId) + device = v; + if(device == null) { + status("connection failed: device not found"); + return; + } + UsbSerialDriver driver = UsbSerialProber.getDefaultProber().probeDevice(device); + if(driver == null) { + driver = CustomProber.getCustomProber().probeDevice(device); + } + if(driver == null) { + status("connection failed: no driver for device"); + return; + } + if(driver.getPorts().size() < portNum) { + status("connection failed: not enough ports at device"); + return; + } + usbSerialPort = driver.getPorts().get(portNum); + UsbDeviceConnection usbConnection = usbManager.openDevice(driver.getDevice()); + if(usbConnection == null && usbPermission == UsbPermission.Unknown && !usbManager.hasPermission(driver.getDevice())) { + usbPermission = UsbPermission.Requested; + PendingIntent usbPermissionIntent = PendingIntent.getBroadcast(getActivity(), 0, new Intent(INTENT_ACTION_GRANT_USB), 0); + usbManager.requestPermission(driver.getDevice(), usbPermissionIntent); + return; + } + if(usbConnection == null) { + if (!usbManager.hasPermission(driver.getDevice())) + status("connection failed: permission denied"); + else + status("connection failed: open failed"); + return; + } + + try { + usbSerialPort.open(usbConnection); + usbSerialPort.setParameters(baudRate, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE); + usbIoManager = new SerialInputOutputManager(usbSerialPort, this); + Executors.newSingleThreadExecutor().submit(usbIoManager); + status("connected"); + connected = true; + controlLines.start(); + } catch (Exception e) { + status("connection failed: " + e.getMessage()); + disconnect(); + } + } + + private void disconnect() { + connected = false; + controlLines.stop(); + if(usbIoManager != null) + usbIoManager.stop(); + usbIoManager = null; + try { + usbSerialPort.close(); + } catch (IOException ignored) {} + usbSerialPort = null; + } + + private void send(String str) { + if(!connected) { + Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show(); + return; + } + try { + byte[] data = (str + '\n').getBytes(); + SpannableStringBuilder spn = new SpannableStringBuilder(); + spn.append("send " + data.length + " bytes:\n"); + spn.append(HexDump.dumpHexString(data)+"\n"); + spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorSendText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + receiveText.append(spn); + usbSerialPort.write(data, WRITE_WAIT_MILLIS); + } catch (Exception e) { + onRunError(e); + } + } + + private void receive(byte[] data) { + String str = new String(data); + SpannableStringBuilder spn = new SpannableStringBuilder(); + spn.append("receive " + data.length + " bytes:\n"); + spn.append(HexDump.dumpHexString(data)+"\n"); + receiveText.append(spn); + } + + void status(String str) { + SpannableStringBuilder spn = new SpannableStringBuilder(str+'\n'); + spn.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.colorStatusText)), 0, spn.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + receiveText.append(spn); + } + + class ControlLines { + private static final int refreshInterval = 200; // msec + + private Runnable runnable; + private ToggleButton rtsBtn, ctsBtn, dtrBtn, dsrBtn, cdBtn, riBtn; + + ControlLines(View view) { + runnable = this::start; // w/o explicit Runnable, a new lambda would be created on each postDelayed, which would not be found again by removeCallbacks + + rtsBtn = view.findViewById(R.id.controlLineRts); + ctsBtn = view.findViewById(R.id.controlLineCts); + dtrBtn = view.findViewById(R.id.controlLineDtr); + dsrBtn = view.findViewById(R.id.controlLineDsr); + cdBtn = view.findViewById(R.id.controlLineCd); + riBtn = view.findViewById(R.id.controlLineRi); + rtsBtn.setOnClickListener(this::toggle); + dtrBtn.setOnClickListener(this::toggle); + } + + private void toggle(View v) { + ToggleButton btn = (ToggleButton) v; + if (!connected) { + btn.setChecked(!btn.isChecked()); + Toast.makeText(getActivity(), "not connected", Toast.LENGTH_SHORT).show(); + return; + } + String ctrl = ""; + try { + if (btn.equals(rtsBtn)) { ctrl = "RTS"; usbSerialPort.setRTS(btn.isChecked()); } + if (btn.equals(dtrBtn)) { ctrl = "DTR"; usbSerialPort.setDTR(btn.isChecked()); } + } catch (IOException e) { + status("set" + ctrl + " failed: " + e.getMessage()); + } + } + + private boolean refresh() { + String ctrl = ""; + try { + ctrl = "RTS"; rtsBtn.setChecked(usbSerialPort.getRTS()); + ctrl = "CTS"; ctsBtn.setChecked(usbSerialPort.getCTS()); + ctrl = "DTR"; dtrBtn.setChecked(usbSerialPort.getDTR()); + ctrl = "DSR"; dsrBtn.setChecked(usbSerialPort.getDSR()); + ctrl = "CD"; cdBtn.setChecked(usbSerialPort.getCD()); + ctrl = "RI"; riBtn.setChecked(usbSerialPort.getRI()); + } catch (IOException e) { + status("get" + ctrl + " failed: " + e.getMessage() + " -> stopped control line refresh"); + return false; + } + return true; + } + + void start() { + if (connected && refresh()) + mainLooper.postDelayed(runnable, refreshInterval); + } + + void stop() { + mainLooper.removeCallbacks(runnable); + } + } +} diff --git a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/util/HexDump.java b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/util/HexDump.java index 54f6e42..e05d71c 100644 --- a/usbSerialExamples/src/main/java/com/hoho/android/usbserial/util/HexDump.java +++ b/usbSerialExamples/src/main/java/com/hoho/android/usbserial/util/HexDump.java @@ -16,6 +16,8 @@ package com.hoho.android.usbserial.util; +import java.security.InvalidParameterException; + /** * Clone of Android's HexDump class, for use in debugging. Cosmetic changes * only. @@ -32,17 +34,12 @@ public class HexDump { public static String dumpHexString(byte[] array, int offset, int length) { StringBuilder result = new StringBuilder(); - byte[] line = new byte[16]; + byte[] line = new byte[8]; int lineIndex = 0; - result.append("\n0x"); - result.append(toHexString(offset)); - for (int i = offset; i < offset + length; i++) { - if (lineIndex == 16) { - result.append(" "); - - for (int j = 0; j < 16; j++) { + if (lineIndex == line.length) { + for (int j = 0; j < line.length; j++) { if (line[j] > ' ' && line[j] < '~') { result.append(new String(line, j, 1)); } else { @@ -50,32 +47,26 @@ public class HexDump { } } - result.append("\n0x"); - result.append(toHexString(i)); + result.append("\n"); lineIndex = 0; } byte b = array[i]; - result.append(" "); result.append(HEX_DIGITS[(b >>> 4) & 0x0F]); result.append(HEX_DIGITS[b & 0x0F]); + result.append(" "); line[lineIndex++] = b; } - if (lineIndex != 16) { - int count = (16 - lineIndex) * 3; - count++; - for (int i = 0; i < count; i++) { - result.append(" "); - } - - for (int i = 0; i < lineIndex; i++) { - if (line[i] > ' ' && line[i] < '~') { - result.append(new String(line, i, 1)); - } else { - result.append("."); - } + for (int i = 0; i < (line.length - lineIndex); i++) { + result.append(" "); + } + for (int i = 0; i < lineIndex; i++) { + if (line[i] > ' ' && line[i] < '~') { + result.append(new String(line, i, 1)); + } else { + result.append("."); } } @@ -145,7 +136,7 @@ public class HexDump { if (c >= 'a' && c <= 'f') return (c - 'a' + 10); - throw new RuntimeException("Invalid hex char '" + c + "'"); + throw new InvalidParameterException("Invalid hex char '" + c + "'"); } public static byte[] hexStringToByteArray(String hexString) { diff --git a/usbSerialExamples/src/main/res/drawable-hdpi/ic_launcher.png b/usbSerialExamples/src/main/res/drawable-hdpi/ic_launcher.png deleted file mode 100644 index 8074c4c..0000000 Binary files a/usbSerialExamples/src/main/res/drawable-hdpi/ic_launcher.png and /dev/null differ diff --git a/usbSerialExamples/src/main/res/drawable-ldpi/ic_launcher.png b/usbSerialExamples/src/main/res/drawable-ldpi/ic_launcher.png deleted file mode 100644 index 1095584..0000000 Binary files a/usbSerialExamples/src/main/res/drawable-ldpi/ic_launcher.png and /dev/null differ diff --git a/usbSerialExamples/src/main/res/drawable-mdpi/ic_launcher.png b/usbSerialExamples/src/main/res/drawable-mdpi/ic_launcher.png deleted file mode 100644 index a07c69f..0000000 Binary files a/usbSerialExamples/src/main/res/drawable-mdpi/ic_launcher.png and /dev/null differ diff --git a/usbSerialExamples/src/main/res/drawable/ic_delete_white_24dp.xml b/usbSerialExamples/src/main/res/drawable/ic_delete_white_24dp.xml new file mode 100644 index 0000000..f9213d2 --- /dev/null +++ b/usbSerialExamples/src/main/res/drawable/ic_delete_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/usbSerialExamples/src/main/res/drawable/ic_send_white_24dp.xml b/usbSerialExamples/src/main/res/drawable/ic_send_white_24dp.xml new file mode 100644 index 0000000..791e93b --- /dev/null +++ b/usbSerialExamples/src/main/res/drawable/ic_send_white_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/usbSerialExamples/src/main/res/layout/activity_main.xml b/usbSerialExamples/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..8cde2fb --- /dev/null +++ b/usbSerialExamples/src/main/res/layout/activity_main.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + diff --git a/usbSerialExamples/src/main/res/layout/device_list_header.xml b/usbSerialExamples/src/main/res/layout/device_list_header.xml new file mode 100644 index 0000000..25214f4 --- /dev/null +++ b/usbSerialExamples/src/main/res/layout/device_list_header.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/usbSerialExamples/src/main/res/layout/device_list_item.xml b/usbSerialExamples/src/main/res/layout/device_list_item.xml new file mode 100644 index 0000000..5a8c577 --- /dev/null +++ b/usbSerialExamples/src/main/res/layout/device_list_item.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/usbSerialExamples/src/main/res/layout/fragment_terminal.xml b/usbSerialExamples/src/main/res/layout/fragment_terminal.xml new file mode 100644 index 0000000..18c9c7f --- /dev/null +++ b/usbSerialExamples/src/main/res/layout/fragment_terminal.xml @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/usbSerialExamples/src/main/res/layout/main.xml b/usbSerialExamples/src/main/res/layout/main.xml deleted file mode 100644 index 2d24c70..0000000 --- a/usbSerialExamples/src/main/res/layout/main.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/usbSerialExamples/src/main/res/layout/serial_console.xml b/usbSerialExamples/src/main/res/layout/serial_console.xml deleted file mode 100644 index 8e25ee9..0000000 --- a/usbSerialExamples/src/main/res/layout/serial_console.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - diff --git a/usbSerialExamples/src/main/res/menu/menu_devices.xml b/usbSerialExamples/src/main/res/menu/menu_devices.xml new file mode 100644 index 0000000..26860b4 --- /dev/null +++ b/usbSerialExamples/src/main/res/menu/menu_devices.xml @@ -0,0 +1,9 @@ + +

+ + + diff --git a/usbSerialExamples/src/main/res/menu/menu_terminal.xml b/usbSerialExamples/src/main/res/menu/menu_terminal.xml new file mode 100644 index 0000000..77ba443 --- /dev/null +++ b/usbSerialExamples/src/main/res/menu/menu_terminal.xml @@ -0,0 +1,9 @@ + + + + diff --git a/usbSerialExamples/src/main/res/mipmap-hdpi/ic_launcher.png b/usbSerialExamples/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..7b9ba5d Binary files /dev/null and b/usbSerialExamples/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/usbSerialExamples/src/main/res/mipmap-mdpi/ic_launcher.png b/usbSerialExamples/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..ef22ce2 Binary files /dev/null and b/usbSerialExamples/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/usbSerialExamples/src/main/res/mipmap-xhdpi/ic_launcher.png b/usbSerialExamples/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..9470400 Binary files /dev/null and b/usbSerialExamples/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/usbSerialExamples/src/main/res/mipmap-xxhdpi/ic_launcher.png b/usbSerialExamples/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b71377c Binary files /dev/null and b/usbSerialExamples/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/usbSerialExamples/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/usbSerialExamples/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f1132c6 Binary files /dev/null and b/usbSerialExamples/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/usbSerialExamples/src/main/res/values/arrays.xml b/usbSerialExamples/src/main/res/values/arrays.xml new file mode 100644 index 0000000..0cc6f82 --- /dev/null +++ b/usbSerialExamples/src/main/res/values/arrays.xml @@ -0,0 +1,10 @@ + + + + 2400 + 9600 + 19200 + 57600 + 115200 + + diff --git a/usbSerialExamples/src/main/res/values/colors.xml b/usbSerialExamples/src/main/res/values/colors.xml new file mode 100644 index 0000000..2f18fb4 --- /dev/null +++ b/usbSerialExamples/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #949C29 + #61671B + #D8E33B + + #00FF00 + #82CAFF + #FFDB58 + diff --git a/usbSerialExamples/src/main/res/values/strings.xml b/usbSerialExamples/src/main/res/values/strings.xml index c818764..f7e0a8e 100644 --- a/usbSerialExamples/src/main/res/values/strings.xml +++ b/usbSerialExamples/src/main/res/values/strings.xml @@ -1,10 +1,6 @@ - - USB Serial Example - Serial Example - Refreshing... - RTS - Request To Send - DTR - Data Terminal Ready - + USB Serial For Android Example + USB Serial Example + USB Devices diff --git a/usbSerialExamples/src/main/res/values/styles.xml b/usbSerialExamples/src/main/res/values/styles.xml new file mode 100644 index 0000000..5f4a5d7 --- /dev/null +++ b/usbSerialExamples/src/main/res/values/styles.xml @@ -0,0 +1,7 @@ + + +