1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.car.usb.aoap.companion; 17 18 import android.app.Activity; 19 import android.app.PendingIntent; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.hardware.usb.UsbAccessory; 25 import android.hardware.usb.UsbManager; 26 import android.os.Bundle; 27 import android.os.ParcelFileDescriptor; 28 import android.util.Log; 29 import android.view.View; 30 import android.widget.Button; 31 32 import java.io.FileInputStream; 33 import java.io.FileOutputStream; 34 import java.io.IOException; 35 import java.nio.ByteBuffer; 36 import java.nio.ByteOrder; 37 import java.util.Objects; 38 39 import javax.annotation.concurrent.GuardedBy; 40 41 /** Activity for AOAP phone test app. */ 42 public class AoapPhoneCompanionActivity extends Activity { 43 private static final String TAG = AoapPhoneCompanionActivity.class.getSimpleName(); 44 private static final boolean DBG = true; 45 private static final ByteOrder ORDER = ByteOrder.BIG_ENDIAN; 46 47 private static final String ACTION_USB_ACCESSORY_PERMISSION = 48 "com.google.android.car.usb.aoap.companion.ACTION_USB_ACCESSORY_PERMISSION"; 49 50 private UsbManager mUsbManager; 51 private AccessoryReceiver mReceiver; 52 private ParcelFileDescriptor mFd; 53 private ProcessorThread mProcessorThread; 54 private UsbAccessory mAccessory; 55 56 @Override onCreate(Bundle savedInstanceState)57 protected void onCreate(Bundle savedInstanceState) { 58 super.onCreate(savedInstanceState); 59 60 setContentView(R.layout.device); 61 Button exitButton = (Button) findViewById(R.id.exit); 62 exitButton.setOnClickListener(new View.OnClickListener() { 63 @Override 64 public void onClick(View view) { 65 finish(); 66 } 67 }); 68 69 mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE); 70 configureReceiver(); 71 handleIntent(getIntent()); 72 } 73 handleIntent(Intent intent)74 private void handleIntent(Intent intent) { 75 if (intent.getAction().equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { 76 UsbAccessory accessory = 77 (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 78 if (accessory != null) { 79 onAccessoryAttached(accessory); 80 } else { 81 throw new RuntimeException("USB accessory is null."); 82 } 83 } else { 84 finish(); 85 } 86 } 87 configureReceiver()88 private void configureReceiver() { 89 IntentFilter filter = new IntentFilter(); 90 filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED); 91 filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED); 92 filter.addAction(ACTION_USB_ACCESSORY_PERMISSION); 93 mReceiver = new AccessoryReceiver(); 94 registerReceiver(mReceiver, filter, Context.RECEIVER_NOT_EXPORTED); 95 } 96 97 @Override onDestroy()98 protected void onDestroy() { 99 super.onDestroy(); 100 unregisterReceiver(mReceiver); 101 // close quietly 102 if (mFd != null) { 103 try { 104 mFd.close(); 105 } catch (RuntimeException e) { 106 throw e; 107 } catch (Exception e) { 108 } 109 } 110 if (mProcessorThread != null) { 111 mProcessorThread.requestToQuit(); 112 try { 113 mProcessorThread.join(1000); 114 } catch (InterruptedException e) { 115 } 116 if (mProcessorThread.isAlive()) { // reader thread stuck 117 Log.w(TAG, "ProcessorThread still alive"); 118 } 119 } 120 } 121 onAccessoryAttached(UsbAccessory accessory)122 private void onAccessoryAttached(UsbAccessory accessory) { 123 Log.i(TAG, "Starting AOAP discovery protocol, accessory attached: " + accessory); 124 // Check whether we have permission to access the accessory. 125 if (!mUsbManager.hasPermission(accessory)) { 126 Log.i(TAG, "Prompting the user for access to the accessory."); 127 Intent intent = new Intent(ACTION_USB_ACCESSORY_PERMISSION); 128 intent.setPackage(getPackageName()); 129 PendingIntent pendingIntent = PendingIntent.getBroadcast( 130 this, 0, intent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_ONE_SHOT); 131 mUsbManager.requestPermission(accessory, pendingIntent); 132 return; 133 } 134 mFd = mUsbManager.openAccessory(accessory); 135 if (mFd == null) { 136 Log.e(TAG, "UsbManager.openAccessory returned null"); 137 finish(); 138 return; 139 } 140 mAccessory = accessory; 141 mProcessorThread = new ProcessorThread(mFd); 142 mProcessorThread.start(); 143 } 144 onAccessoryDetached(UsbAccessory accessory)145 private void onAccessoryDetached(UsbAccessory accessory) { 146 Log.i(TAG, "Accessory detached: " + accessory); 147 finish(); 148 } 149 150 private class ProcessorThread extends Thread { 151 152 private final Object mLock = new Object(); 153 154 @GuardedBy("mLock") 155 private boolean mShouldQuit = false; 156 157 private final FileInputStream mInputStream; 158 private final FileOutputStream mOutputStream; 159 private final byte[] mBuffer = new byte[16384]; 160 ProcessorThread(ParcelFileDescriptor fd)161 private ProcessorThread(ParcelFileDescriptor fd) { 162 super("AOAP"); 163 mInputStream = new FileInputStream(fd.getFileDescriptor()); 164 mOutputStream = new FileOutputStream(fd.getFileDescriptor()); 165 } 166 requestToQuit()167 private void requestToQuit() { 168 synchronized (mLock) { 169 mShouldQuit = true; 170 } 171 } 172 shouldQuit()173 private boolean shouldQuit() { 174 synchronized (mLock) { 175 return mShouldQuit; 176 } 177 } 178 byteToInt(byte[] buffer)179 protected int byteToInt(byte[] buffer) { 180 return ByteBuffer.wrap(buffer).order(ORDER).getInt(); 181 } 182 183 @Override run()184 public void run() { 185 while (!shouldQuit()) { 186 int readBufferSize = 0; 187 while (!shouldQuit()) { 188 try { 189 int read = mInputStream.read(mBuffer); 190 if (read == 4 && readBufferSize == 0) { 191 readBufferSize = byteToInt(mBuffer); 192 continue; 193 } 194 Log.d(TAG, "Read " + read + " bytes"); 195 if (read < readBufferSize) { 196 break; 197 } 198 } catch (IOException e) { 199 Log.i(TAG, "ProcessorThread IOException", e); 200 // AOAP App should release FD when IOException happens. 201 // If FD is kept, device will not behave nicely on reset and multiple reset 202 // can be required. 203 finish(); 204 return; 205 } 206 } 207 if (!shouldQuit()) { 208 byte[] outBuffer = "DONE".getBytes(); 209 try { 210 mOutputStream.write(outBuffer); 211 } catch (IOException e) { 212 Log.i(TAG, "ProcessorThread IOException", e); 213 finish(); 214 return; 215 } 216 } 217 } 218 } 219 } 220 221 private class AccessoryReceiver extends BroadcastReceiver { 222 @Override onReceive(Context context, Intent intent)223 public void onReceive(Context context, Intent intent) { 224 UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY); 225 if (accessory != null) { 226 String action = intent.getAction(); 227 if (action.equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) { 228 onAccessoryAttached(accessory); 229 } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) { 230 if (mAccessory != null && mAccessory.equals(accessory)) { 231 onAccessoryDetached(accessory); 232 } 233 } else if (Objects.equals(action, ACTION_USB_ACCESSORY_PERMISSION)) { 234 if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) { 235 Log.i(TAG, "Accessory permission granted: " + accessory); 236 onAccessoryAttached(accessory); 237 } else { 238 Log.e(TAG, "Accessory permission denied: " + accessory); 239 finish(); 240 } 241 } 242 } 243 } 244 } 245 } 246