• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.cts.verifier.usb.mtp;
18 
19 import static android.content.Context.RECEIVER_EXPORTED;
20 import static junit.framework.Assert.assertEquals;
21 import static junit.framework.Assert.assertNotNull;
22 import static junit.framework.Assert.assertTrue;
23 import static junit.framework.Assert.fail;
24 
25 import android.app.PendingIntent;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.hardware.usb.UsbConstants;
31 import android.hardware.usb.UsbDevice;
32 import android.hardware.usb.UsbDeviceConnection;
33 import android.hardware.usb.UsbInterface;
34 import android.hardware.usb.UsbManager;
35 import android.mtp.MtpConstants;
36 import android.mtp.MtpDevice;
37 import android.mtp.MtpDeviceInfo;
38 import android.mtp.MtpEvent;
39 import android.mtp.MtpObjectInfo;
40 import android.os.Bundle;
41 import android.os.Handler;
42 import android.os.Message;
43 import android.os.ParcelFileDescriptor;
44 import android.os.SystemClock;
45 import android.provider.Settings;
46 import android.util.MutableInt;
47 import android.view.LayoutInflater;
48 import android.view.View;
49 import android.view.View.OnClickListener;
50 import android.widget.Button;
51 import android.widget.ImageView;
52 import android.widget.LinearLayout;
53 import android.widget.TextView;
54 
55 import com.android.cts.verifier.PassFailButtons;
56 import com.android.cts.verifier.R;
57 
58 import junit.framework.AssertionFailedError;
59 
60 import java.io.IOException;
61 import java.io.PrintWriter;
62 import java.io.StringWriter;
63 import java.nio.charset.StandardCharsets;
64 import java.util.ArrayList;
65 import java.util.concurrent.CountDownLatch;
66 import java.util.concurrent.ExecutorService;
67 import java.util.concurrent.Executors;
68 
69 public class MtpHostTestActivity extends PassFailButtons.Activity implements Handler.Callback {
70     private static final int MESSAGE_PASS = 0;
71     private static final int MESSAGE_FAIL = 1;
72     private static final int MESSAGE_RUN = 2;
73 
74     private static final int ITEM_STATE_PASS = 0;
75     private static final int ITEM_STATE_FAIL = 1;
76     private static final int ITEM_STATE_INDETERMINATE = 2;
77 
78     /**
79      * Subclass for PTP.
80      */
81     private static final int SUBCLASS_STILL_IMAGE_CAPTURE = 1;
82 
83     /**
84      * Subclass for Android style MTP.
85      */
86     private static final int SUBCLASS_MTP = 0xff;
87 
88     /**
89      * Protocol for Picture Transfer Protocol (PIMA 15470).
90      */
91     private static final int PROTOCOL_PICTURE_TRANSFER = 1;
92 
93     /**
94      * Protocol for Android style MTP.
95      */
96     private static final int PROTOCOL_MTP = 0;
97 
98     private static final int RETRY_DELAY_MS = 1000;
99 
100     private static final String ACTION_PERMISSION_GRANTED =
101             "com.android.cts.verifier.usb.ACTION_PERMISSION_GRANTED";
102 
103     private static final String TEST_FILE_NAME = "CtsVerifierTest_testfile.txt";
104     private static final byte[] TEST_FILE_CONTENTS =
105             "This is a test file created by CTS verifier test.".getBytes(StandardCharsets.US_ASCII);
106 
107     private final Handler mHandler = new Handler(this);
108     private int mStep;
109     private final ArrayList<TestItem> mItems = new ArrayList<>();
110 
111     private UsbManager mUsbManager;
112     private BroadcastReceiver mReceiver;
113     private UsbDevice mUsbDevice;
114     private MtpDevice mMtpDevice;
115     private ExecutorService mExecutor;
116     private TextView mErrorText;
117 
118     @Override
onCreate(Bundle savedInstanceState)119     protected void onCreate(Bundle savedInstanceState) {
120         super.onCreate(savedInstanceState);
121         setContentView(R.layout.mtp_host_activity);
122         setInfoResources(R.string.mtp_host_test, R.string.mtp_host_test_info, -1);
123         setPassFailButtonClickListeners();
124 
125         final LayoutInflater inflater = getLayoutInflater();
126         final LinearLayout itemsView = (LinearLayout) findViewById(R.id.mtp_host_list);
127 
128         mErrorText = (TextView) findViewById(R.id.error_text);
129 
130         // Don't allow a test pass until all steps are passed.
131         getPassButton().setEnabled(false);
132 
133         // Build test items.
134         mItems.add(new TestItem(
135                 inflater,
136                 R.string.mtp_host_device_lookup_message,
137                 new int[] { R.id.next_item_button }));
138         mItems.add(new TestItem(
139                 inflater,
140                 R.string.mtp_host_test_file_browse_message,
141                 new int[] { R.id.settings_button, R.id.pass_item_button, R.id.fail_item_button }));
142         mItems.add(new TestItem(
143                 inflater,
144                 R.string.mtp_host_grant_permission_message,
145                 null));
146         mItems.add(new TestItem(
147                 inflater,
148                 R.string.mtp_host_test_read_event_message,
149                 null));
150         mItems.add(new TestItem(
151                 inflater,
152                 R.string.mtp_host_test_send_object_message,
153                 null));
154         for (final TestItem item : mItems) {
155             itemsView.addView(item.view);
156         }
157 
158         mExecutor = Executors.newSingleThreadExecutor();
159         mUsbManager = getSystemService(UsbManager.class);
160 
161         mStep = 0;
162         mHandler.sendEmptyMessage(MESSAGE_RUN);
163     }
164 
165     @Override
onDestroy()166     protected void onDestroy() {
167         super.onDestroy();
168         if (mReceiver != null) {
169             unregisterReceiver(mReceiver);
170             mReceiver = null;
171         }
172     }
173 
174     @Override
handleMessage(Message msg)175     public boolean handleMessage(Message msg) {
176         final TestItem item = mStep < mItems.size() ? mItems.get(mStep) : null;
177 
178         switch (msg.what) {
179             case MESSAGE_RUN:
180                 if (item == null) {
181                     getPassButton().setEnabled(true);
182                     return true;
183                 }
184                 item.setEnabled(true);
185                 mExecutor.execute(new Runnable() {
186                     private final int mCurrentStep = mStep;
187 
188                     @Override
189                     public void run() {
190                         try {
191                             switch (mCurrentStep) {
192                                 case 0:
193                                     stepFindMtpDevice();
194                                     break;
195                                 case 1:
196                                     stepTestFileBrowse();
197                                     break;
198                                 case 2:
199                                     stepGrantPermission();
200                                     break;
201                                 case 3:
202                                     stepTestReadEvent();
203                                     break;
204                                 case 4:
205                                     stepTestSendObject();
206                                     break;
207                             }
208                             mHandler.sendEmptyMessage(MESSAGE_PASS);
209                         } catch (Exception | AssertionFailedError exception) {
210                             mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_FAIL, exception));
211                         }
212                     }
213                 });
214                 break;
215 
216             case MESSAGE_PASS:
217                 item.setState(ITEM_STATE_PASS);
218                 item.setEnabled(false);
219                 mStep++;
220                 mHandler.sendEmptyMessage(MESSAGE_RUN);
221                 break;
222 
223             case MESSAGE_FAIL:
224                 item.setState(ITEM_STATE_FAIL);
225                 item.setEnabled(false);
226                 final StringWriter writer = new StringWriter();
227                 final Throwable throwable = (Throwable) msg.obj;
228                 throwable.printStackTrace(new PrintWriter(writer));
229                 mErrorText.setText(writer.toString());
230                 break;
231         }
232 
233         return true;
234     }
235 
236     private void stepFindMtpDevice() throws InterruptedException {
237         assertEquals(R.id.next_item_button, waitForButtonClick());
238 
239         UsbDevice device = null;
240         for (final UsbDevice candidate : mUsbManager.getDeviceList().values()) {
241             if (isMtpDevice(candidate)) {
242                 device = candidate;
243                 break;
244             }
245         }
246         assertNotNull(device);
247         mUsbDevice = device;
248     }
249 
250     private void stepGrantPermission() throws InterruptedException {
251         if (!mUsbManager.hasPermission(mUsbDevice)) {
252             final CountDownLatch latch = new CountDownLatch(1);
253             mReceiver = new BroadcastReceiver() {
254                 @Override
255                 public void onReceive(Context context, Intent intent) {
256                     unregisterReceiver(this);
257                     mReceiver = null;
258                     latch.countDown();
259                 }
260             };
261             registerReceiver(mReceiver, new IntentFilter(ACTION_PERMISSION_GRANTED),RECEIVER_EXPORTED);
262             mUsbManager.requestPermission(
263                     mUsbDevice,
264                     PendingIntent.getBroadcast(
265                             MtpHostTestActivity.this, 0,
266                             new Intent(ACTION_PERMISSION_GRANTED)
267                                     .setPackage(MtpHostTestActivity.this.getPackageName()),
268                             PendingIntent.FLAG_MUTABLE));
269 
270             latch.await();
271             assertTrue(mUsbManager.hasPermission(mUsbDevice));
272         }
273 
274         final UsbDeviceConnection connection = mUsbManager.openDevice(mUsbDevice);
275         assertNotNull(connection);
276 
277         // Try to rob device ownership from other applications.
278         for (int i = 0; i < mUsbDevice.getInterfaceCount(); i++) {
279             connection.claimInterface(mUsbDevice.getInterface(i), true);
280             connection.releaseInterface(mUsbDevice.getInterface(i));
281         }
282         mMtpDevice = new MtpDevice(mUsbDevice);
283         assertTrue(mMtpDevice.open(connection));
284         assertTrue(mMtpDevice.getStorageIds().length > 0);
285     }
286 
287     private void stepTestReadEvent() {
288         assertNotNull(mMtpDevice.getDeviceInfo().getEventsSupported());
289         assertTrue(mMtpDevice.getDeviceInfo().isEventSupported(MtpEvent.EVENT_OBJECT_ADDED));
290 
291         mMtpDevice.getObjectHandles(0xFFFFFFFF, 0x0, 0x0);
292         while (true) {
293             MtpEvent event;
294             try {
295                 event = mMtpDevice.readEvent(null);
296             } catch (IOException e) {
297                 fail();
298                 return;
299             }
300             if (event.getEventCode() == MtpEvent.EVENT_OBJECT_ADDED) {
301                 break;
302             }
303             SystemClock.sleep(RETRY_DELAY_MS);
304         }
305     }
306 
307     private void stepTestSendObject() throws IOException {
308         final MtpDeviceInfo deviceInfo = mMtpDevice.getDeviceInfo();
309         assertNotNull(deviceInfo.getOperationsSupported());
310         assertTrue(deviceInfo.isOperationSupported(MtpConstants.OPERATION_SEND_OBJECT_INFO));
311         assertTrue(deviceInfo.isOperationSupported(MtpConstants.OPERATION_SEND_OBJECT));
312 
313         // Delete an existing test file that may be created by the test previously.
314         final int storageId = mMtpDevice.getStorageIds()[0];
315         for (final int objectHandle : mMtpDevice.getObjectHandles(
316                 storageId, /* all format */ 0, /* Just under the root */ -1)) {
317             final MtpObjectInfo info = mMtpDevice.getObjectInfo(objectHandle);
318             if (TEST_FILE_NAME.equals(info.getName())) {
319                 assertTrue(mMtpDevice.deleteObject(objectHandle));
320             }
321         }
322 
323         final MtpObjectInfo info = new MtpObjectInfo.Builder()
324                 .setStorageId(storageId)
325                 .setName(TEST_FILE_NAME)
326                 .setCompressedSize(TEST_FILE_CONTENTS.length)
327                 .setFormat(MtpConstants.FORMAT_TEXT)
328                 .setParent(-1)
329                 .build();
330         final MtpObjectInfo newInfo = mMtpDevice.sendObjectInfo(info);
331         assertNotNull(newInfo);
332         assertTrue(newInfo.getObjectHandle() != -1);
333 
334         final ParcelFileDescriptor[] pipes = ParcelFileDescriptor.createPipe();
335         try {
336             try (final ParcelFileDescriptor.AutoCloseOutputStream stream =
337                     new ParcelFileDescriptor.AutoCloseOutputStream(pipes[1])) {
338                 stream.write(TEST_FILE_CONTENTS);
339             }
340             assertTrue(mMtpDevice.sendObject(
341                     newInfo.getObjectHandle(),
342                     newInfo.getCompressedSizeLong(),
343                     pipes[0]));
344         } finally {
345             pipes[0].close();
346         }
347     }
348 
349     private void stepTestFileBrowse() throws InterruptedException {
350         while (true) {
351             final int id = waitForButtonClick();
352             if (id == R.id.settings_button) {
353                 startActivity(new Intent(Settings.ACTION_APPLICATION_SETTINGS));
354                 continue;
355             }
356             assertEquals(R.id.pass_item_button, waitForButtonClick());
357             break;
358         }
359     }
360 
361     private int waitForButtonClick() throws InterruptedException {
362         final CountDownLatch latch = new CountDownLatch(1);
363         final MutableInt result = new MutableInt(-1);
364         mItems.get(mStep).setOnClickListener(new OnClickListener() {
365             @Override
366             public void onClick(View v) {
367                 result.value = v.getId();
368                 latch.countDown();
369             }
370         });
371         latch.await();
372         return result.value;
373     }
374 
375     private static boolean isMtpDevice(UsbDevice device) {
376         for (int i = 0; i < device.getInterfaceCount(); i++) {
377             final UsbInterface usbInterface = device.getInterface(i);
378             if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
379                     usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE &&
380                     usbInterface.getInterfaceProtocol() == PROTOCOL_PICTURE_TRANSFER)) {
381                 return true;
382             }
383             if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC &&
384                     usbInterface.getInterfaceSubclass() == SUBCLASS_MTP &&
385                     usbInterface.getInterfaceProtocol() == PROTOCOL_MTP &&
386                     "MTP".equals(usbInterface.getName())) {
387                 return true;
388             }
389         }
390         return false;
391     }
392 
393     private static class TestItem {
394         private final View view;
395         private final int[] buttons;
396 
397         TestItem(LayoutInflater inflater,
398                  int messageText,
399                  int[] buttons) {
400             this.view = inflater.inflate(R.layout.mtp_host_item, null, false);
401 
402             final TextView textView = (TextView) view.findViewById(R.id.instructions);
403             textView.setText(messageText);
404 
405             this.buttons = buttons != null ? buttons : new int[0];
406             for (final int id : this.buttons) {
407                 final Button button = (Button) view.findViewById(id);
408                 button.setVisibility(View.VISIBLE);
409                 button.setEnabled(false);
410             }
411         }
412 
413         void setOnClickListener(OnClickListener listener) {
414             for (final int id : buttons) {
415                 final Button button = (Button) view.findViewById(id);
416                 button.setOnClickListener(listener);
417             }
418         }
419 
420         void setEnabled(boolean value) {
421             for (final int id : buttons) {
422                 final Button button = (Button) view.findViewById(id);
423                 button.setEnabled(value);
424             }
425         }
426 
427         Button getButton(int id) {
428             return (Button) view.findViewById(id);
429         }
430 
431         void setState(int state) {
432             final ImageView imageView = (ImageView) view.findViewById(R.id.status);
433             switch (state) {
434                 case ITEM_STATE_PASS:
435                     imageView.setImageResource(R.drawable.fs_good);
436                     break;
437                 case ITEM_STATE_FAIL:
438                     imageView.setImageResource(R.drawable.fs_error);
439                     break;
440                 case ITEM_STATE_INDETERMINATE:
441                     imageView.setImageResource(R.drawable.fs_indeterminate);
442                     break;
443             }
444         }
445     }
446 }
447