Home | History | Annotate | Download | only in companion
      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 libcore.io.IoUtils;
     33 
     34 import java.io.FileInputStream;
     35 import java.io.FileOutputStream;
     36 import java.io.IOException;
     37 import java.nio.ByteBuffer;
     38 import java.nio.ByteOrder;
     39 
     40 /** Activity for AOAP phone test app. */
     41 public class AoapPhoneCompanionActivity extends Activity {
     42     private static final String TAG = AoapPhoneCompanionActivity.class.getSimpleName();
     43     private static final boolean DBG = true;
     44     private static final ByteOrder ORDER = ByteOrder.BIG_ENDIAN;
     45 
     46     private static final String ACTION_USB_ACCESSORY_PERMISSION =
     47             "com.google.android.car.usb.aoap.companion.ACTION_USB_ACCESSORY_PERMISSION";
     48 
     49     private UsbManager mUsbManager;
     50     private AccessoryReceiver mReceiver;
     51     private ParcelFileDescriptor mFd;
     52     private ProcessorThread mProcessorThread;
     53     private UsbAccessory mAccessory;
     54 
     55     @Override
     56     protected void onCreate(Bundle savedInstanceState) {
     57         super.onCreate(savedInstanceState);
     58 
     59         setContentView(R.layout.device);
     60         Button exitButton = (Button) findViewById(R.id.exit);
     61         exitButton.setOnClickListener(new View.OnClickListener() {
     62                 @Override
     63                 public void onClick(View view) {
     64                     finish();
     65                 }
     66             });
     67 
     68         mUsbManager = (UsbManager) getSystemService(Context.USB_SERVICE);
     69         configureReceiver();
     70         handleIntent(getIntent());
     71     }
     72 
     73     private void handleIntent(Intent intent) {
     74         if (intent.getAction().equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
     75             UsbAccessory accessory =
     76                     (UsbAccessory) intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
     77             if (accessory != null) {
     78                 onAccessoryAttached(accessory);
     79             } else {
     80                 throw new RuntimeException("USB accessory is null.");
     81             }
     82         } else {
     83             finish();
     84         }
     85     }
     86 
     87     private void configureReceiver() {
     88         IntentFilter filter = new IntentFilter();
     89         filter.addAction(UsbManager.ACTION_USB_ACCESSORY_ATTACHED);
     90         filter.addAction(UsbManager.ACTION_USB_ACCESSORY_DETACHED);
     91         filter.addAction(ACTION_USB_ACCESSORY_PERMISSION);
     92         mReceiver = new AccessoryReceiver();
     93         registerReceiver(mReceiver, filter);
     94     }
     95 
     96     @Override
     97     protected void onDestroy() {
     98         super.onDestroy();
     99         unregisterReceiver(mReceiver);
    100         IoUtils.closeQuietly(mFd);
    101         if (mProcessorThread != null) {
    102             mProcessorThread.requestToQuit();
    103             try {
    104                 mProcessorThread.join(1000);
    105             } catch (InterruptedException e) {
    106             }
    107             if (mProcessorThread.isAlive()) { // reader thread stuck
    108                 Log.w(TAG, "ProcessorThread still alive");
    109             }
    110         }
    111     }
    112 
    113     private void onAccessoryAttached(UsbAccessory accessory) {
    114         Log.i(TAG, "Starting AOAP discovery protocol, accessory attached: " + accessory);
    115         // Check whether we have permission to access the accessory.
    116         if (!mUsbManager.hasPermission(accessory)) {
    117             Log.i(TAG, "Prompting the user for access to the accessory.");
    118             Intent intent = new Intent(ACTION_USB_ACCESSORY_PERMISSION);
    119             intent.setPackage(getPackageName());
    120             PendingIntent pendingIntent = PendingIntent.getBroadcast(
    121                     this, 0, intent, PendingIntent.FLAG_ONE_SHOT);
    122             mUsbManager.requestPermission(accessory, pendingIntent);
    123             return;
    124         }
    125         mFd = mUsbManager.openAccessory(accessory);
    126         if (mFd == null) {
    127             Log.e(TAG, "UsbManager.openAccessory returned null");
    128             finish();
    129             return;
    130         }
    131         mAccessory = accessory;
    132         mProcessorThread = new ProcessorThread(mFd);
    133         mProcessorThread.start();
    134     }
    135 
    136     private void onAccessoryDetached(UsbAccessory accessory) {
    137         Log.i(TAG, "Accessory detached: " + accessory);
    138         finish();
    139     }
    140 
    141     private class ProcessorThread extends Thread {
    142         private boolean mShouldQuit = false;
    143         private final FileInputStream mInputStream;
    144         private final FileOutputStream mOutputStream;
    145         private final byte[] mBuffer = new byte[16384];
    146 
    147         private ProcessorThread(ParcelFileDescriptor fd) {
    148             super("AOAP");
    149             mInputStream = new FileInputStream(fd.getFileDescriptor());
    150             mOutputStream = new FileOutputStream(fd.getFileDescriptor());
    151         }
    152 
    153         private synchronized void requestToQuit() {
    154             mShouldQuit = true;
    155         }
    156 
    157         private synchronized boolean shouldQuit() {
    158             return mShouldQuit;
    159         }
    160 
    161         protected int byteToInt(byte[] buffer) {
    162             return ByteBuffer.wrap(buffer).order(ORDER).getInt();
    163         }
    164 
    165         @Override
    166         public void run() {
    167             while (!shouldQuit()) {
    168                 int readBufferSize = 0;
    169                 while (!shouldQuit()) {
    170                     try {
    171                         int read = mInputStream.read(mBuffer);
    172                         if (read == 4 && readBufferSize == 0) {
    173                             readBufferSize = byteToInt(mBuffer);
    174                             continue;
    175                         }
    176                         Log.d(TAG, "Read " + read + " bytes");
    177                         if (read < readBufferSize) {
    178                             break;
    179                         }
    180                     } catch (IOException e) {
    181                         Log.i(TAG, "ProcessorThread IOException", e);
    182                         // AOAP App should release FD when IOException happens.
    183                         // If FD is kept, device will not behave nicely on reset and multiple reset
    184                         // can be required.
    185                         finish();
    186                         return;
    187                     }
    188                 }
    189                 if (!shouldQuit()) {
    190                     byte[] outBuffer = "DONE".getBytes();
    191                     try {
    192                         mOutputStream.write(outBuffer);
    193                     } catch (IOException e) {
    194                         Log.i(TAG, "ProcessorThread IOException", e);
    195                         finish();
    196                         return;
    197                     }
    198                 }
    199             }
    200         }
    201     }
    202 
    203     private class AccessoryReceiver extends BroadcastReceiver {
    204         @Override
    205         public void onReceive(Context context, Intent intent) {
    206             UsbAccessory accessory = intent.getParcelableExtra(UsbManager.EXTRA_ACCESSORY);
    207             if (accessory != null) {
    208                 String action = intent.getAction();
    209                 if (action.equals(UsbManager.ACTION_USB_ACCESSORY_ATTACHED)) {
    210                     onAccessoryAttached(accessory);
    211                 } else if (action.equals(UsbManager.ACTION_USB_ACCESSORY_DETACHED)) {
    212                     if (mAccessory != null && mAccessory.equals(accessory)) {
    213                         onAccessoryDetached(accessory);
    214                     }
    215                 } else if (action.equals(ACTION_USB_ACCESSORY_PERMISSION)) {
    216                     if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
    217                         Log.i(TAG, "Accessory permission granted: " + accessory);
    218                         onAccessoryAttached(accessory);
    219                     } else {
    220                         Log.e(TAG, "Accessory permission denied: " + accessory);
    221                         finish();
    222                     }
    223                 }
    224             }
    225         }
    226     }
    227 }
    228