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
onCreate(Bundle savedInstanceState)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 
handleIntent(Intent intent)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 
configureReceiver()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
onDestroy()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 
onAccessoryAttached(UsbAccessory accessory)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 
onAccessoryDetached(UsbAccessory accessory)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 
ProcessorThread(ParcelFileDescriptor fd)153         private ProcessorThread(ParcelFileDescriptor fd) {
154             super("AOAP");
155             mInputStream = new FileInputStream(fd.getFileDescriptor());
156             mOutputStream = new FileOutputStream(fd.getFileDescriptor());
157         }
158 
requestToQuit()159         private synchronized void requestToQuit() {
160             mShouldQuit = true;
161         }
162 
shouldQuit()163         private synchronized boolean shouldQuit() {
164             return mShouldQuit;
165         }
166 
byteToInt(byte[] buffer)167         protected int byteToInt(byte[] buffer) {
168             return ByteBuffer.wrap(buffer).order(ORDER).getInt();
169         }
170 
171         @Override
run()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
onReceive(Context context, Intent intent)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