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