14b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani/*
24b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * Copyright (C) 2010 The Android Open Source Project
34b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani *
44b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * Licensed under the Apache License, Version 2.0 (the "License");
54b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * you may not use this file except in compliance with the License.
64b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * You may obtain a copy of the License at
74b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani *
84b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani *      http://www.apache.org/licenses/LICENSE-2.0
94b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani *
104b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * Unless required by applicable law or agreed to in writing, software
114b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * distributed under the License is distributed on an "AS IS" BASIS,
124b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * See the License for the specific language governing permissions and
144b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * limitations under the License.
154b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani */
164b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani
174b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasanipackage com.android.camerabrowser;
184b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani
19ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkeyimport android.app.PendingIntent;
204b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasaniimport android.content.BroadcastReceiver;
21db6a14cc85cede0769735fdac4da70766989a3ceAmith Yamasaniimport android.content.Context;
222a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasaniimport android.content.Intent;
2380a4af2bbc6af42ae605e454bf89558e564f5244Dianne Hackbornimport android.content.IntentFilter;
2480a4af2bbc6af42ae605e454bf89558e564f5244Dianne Hackbornimport android.hardware.usb.UsbConstants;
25db6a14cc85cede0769735fdac4da70766989a3ceAmith Yamasaniimport android.hardware.usb.UsbDevice;
26258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasaniimport android.hardware.usb.UsbDeviceConnection;
27258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasaniimport android.hardware.usb.UsbInterface;
280b285499db739ba50f2f839d633e763c70e67f96Amith Yamasaniimport android.hardware.usb.UsbManager;
294b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasaniimport android.mtp.MtpDevice;
30e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasaniimport android.mtp.MtpDeviceInfo;
31e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasaniimport android.mtp.MtpObjectInfo;
32258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasaniimport android.mtp.MtpStorageInfo;
334b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasaniimport android.os.ParcelFileDescriptor;
344b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasaniimport android.util.Log;
35ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey
36258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasaniimport java.io.IOException;
37258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasaniimport java.util.ArrayList;
3880a4af2bbc6af42ae605e454bf89558e564f5244Dianne Hackbornimport java.util.HashMap;
39f02b60aa4f367516f40cf3d60fffae0c6fe3e1b8Dianne Hackbornimport java.util.List;
4027bd34d9d9fe99f11b80aa0bbdb402fb47ef4158Jeff Sharkey
412a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani/**
424b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * This class helps an application manage a list of connected MTP or PTP devices.
434b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani * It listens for MTP devices being attached and removed from the USB host bus
44ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey * and notifies the application when the MTP device list changes.
45920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani */
464b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasanipublic class MtpClient {
474b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani
48ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    private static final String TAG = "MtpClient";
49ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey
50ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    private static final String ACTION_USB_PERMISSION =
51ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey            "android.mtp.MtpClient.action.USB_PERMISSION";
52ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey
53ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    private final Context mContext;
54ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    private final UsbManager mUsbManager;
554b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    private final ArrayList<Listener> mListeners = new ArrayList<Listener>();
564b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    // mDevices contains all MtpDevices that have been seen by our client,
57920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani    // so we can inform when the device has been detached.
584b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    // mDevices is also used for synchronization in this class.
59b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani    private final HashMap<String, MtpDevice> mDevices = new HashMap<String, MtpDevice>();
604b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani
614b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    private final PendingIntent mPermissionIntent;
62920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani
634b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
644b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani        @Override
654b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani        public void onReceive(Context context, Intent intent) {
66258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            String action = intent.getAction();
67b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani            UsbDevice usbDevice = (UsbDevice)intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
682a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani            String deviceName = usbDevice.getDeviceName();
69b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani
701638931166b7b5571886a8fe6d413ea90d4194b5Amith Yamasani            synchronized (mDevices) {
711638931166b7b5571886a8fe6d413ea90d4194b5Amith Yamasani                MtpDevice mtpDevice = mDevices.get(deviceName);
724b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani
734b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) {
74b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani                    if (mtpDevice == null) {
754b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                        mtpDevice = openDeviceLocked(usbDevice);
76920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani                    }
77920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani                    if (mtpDevice != null) {
782a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani                        for (Listener listener : mListeners) {
792a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani                            listener.deviceAdded(mtpDevice);
80d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                        }
816f34b411144a8202c96d05ff79e8040d3885643aAmith Yamasani                    }
824b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                } else if (UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) {
834b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                    if (mtpDevice != null) {
844b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                        mDevices.remove(deviceName);
850b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani                        for (Listener listener : mListeners) {
864b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                            listener.deviceRemoved(mtpDevice);
87b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani                        }
884b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                    }
89634cf31345ae843392215237e741d76271a5cfedAmith Yamasani                } else if (ACTION_USB_PERMISSION.equals(action)) {
90634cf31345ae843392215237e741d76271a5cfedAmith Yamasani                    boolean permission = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED,
91bc9625059bccc8f0d70540d0affd73320620c3c0Amith Yamasani                            false);
926f34b411144a8202c96d05ff79e8040d3885643aAmith Yamasani                    Log.d(TAG, "ACTION_USB_PERMISSION: " + permission);
93920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani                    if (permission) {
94920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani                        if (mtpDevice == null) {
954428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                            mtpDevice = openDeviceLocked(usbDevice);
964428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                        }
974428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                        if (mtpDevice != null) {
984428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                            for (Listener listener : mListeners) {
994b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                                listener.deviceAdded(mtpDevice);
100ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey                            }
101ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey                        }
1024b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                    }
1034b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani                }
1044428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn            }
1054428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        }
106ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    };
107ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey
108ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    /**
109ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey     * An interface for being notified when MTP or PTP devices are attached
110ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey     * or removed.  In the current implementation, only PTP devices are supported.
111ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey     */
112ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey    public interface Listener {
113ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey        /**
1144428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn         * Called when a new device has been added
1150b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani         *
116258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani         * @param device the new device that was added
1172a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani         */
1186f34b411144a8202c96d05ff79e8040d3885643aAmith Yamasani        public void deviceAdded(MtpDevice device);
1190b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani
120258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        /**
121258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani         * Called when a new device has been removed
1224428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn         *
1234428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn         * @param device the device that was removed
1244428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn         */
125258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        public void deviceRemoved(MtpDevice device);
126258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    }
1274b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani
1284b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    /**
1294b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani     * Tests to see if a {@link android.hardware.usb.UsbDevice}
1304b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani     * supports the PTP protocol (typically used by digital cameras)
131258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     *
1324428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     * @param device the device to test
1334b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani     * @return true if the device is a PTP device.
1344b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani     */
1354428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn    static public boolean isCamera(UsbDevice device) {
1364428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        int count = device.getInterfaceCount();
1374428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        for (int i = 0; i < count; i++) {
1384428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn            UsbInterface intf = device.getInterface(i);
1394428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn            if (intf.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
1404428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                    intf.getInterfaceSubclass() == 1 &&
1414428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                    intf.getInterfaceProtocol() == 1) {
1424428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn                return true;
1434428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn            }
1444428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        }
145258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        return false;
146258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    }
1474428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn
1484428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn    /**
1494428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     * MtpClient constructor
1504428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     *
1514428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     * @param context the {@link android.content.Context} to use for the MtpClient
1524428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     */
153d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    public MtpClient(Context context) {
154d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        mContext = context;
155d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        mUsbManager = (UsbManager)context.getSystemService(Context.USB_SERVICE);
156d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        mPermissionIntent = PendingIntent.getBroadcast(mContext, 0, new Intent(ACTION_USB_PERMISSION), 0);
157ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey        IntentFilter filter = new IntentFilter();
158d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
159d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
160d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        filter.addAction(ACTION_USB_PERMISSION);
161d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        context.registerReceiver(mUsbReceiver, filter);
162d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    }
163d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn
164d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    /**
165d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * Opens the {@link android.hardware.usb.UsbDevice} for an MTP or PTP
166d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * device and return an {@link android.mtp.MtpDevice} for it.
167d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     *
168d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @param device the device to open
169d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @return an MtpDevice for the device.
170d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     */
171d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    private MtpDevice openDeviceLocked(UsbDevice usbDevice) {
172756901d82b41f50610a63b7cf4c7747a70f1f724Amith Yamasani        if (isCamera(usbDevice)) {
173d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            if (!mUsbManager.hasPermission(usbDevice)) {
174d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                mUsbManager.requestPermission(usbDevice, mPermissionIntent);
175d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            } else {
176d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                UsbDeviceConnection connection = mUsbManager.openDevice(usbDevice);
177d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                if (connection != null) {
178d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                    MtpDevice mtpDevice = new MtpDevice(usbDevice);
179d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                    if (mtpDevice.open(connection)) {
180d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                        mDevices.put(usbDevice.getDeviceName(), mtpDevice);
181d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                        return mtpDevice;
182d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                    }
183d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                }
184d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            }
185d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        }
186d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        return null;
187d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    }
1884428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn
1894b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani    /**
1904b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani     * Closes all resources related to the MtpClient object
191258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     */
192920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani    public void close() {
1932a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani        mContext.unregisterReceiver(mUsbReceiver);
1944428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn    }
195135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani
196135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    /**
197d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * Registers a {@link android.mtp.MtpClient.Listener} interface to receive
198d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * notifications when MTP or PTP devices are added or removed.
199d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     *
200d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @param listener the listener to register
201ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey     */
202d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    public void addListener(Listener listener) {
203920ace0bbc2d4133dbec991d2636c99a57d6245eAmith Yamasani        synchronized (mDevices) {
204135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani            if (!mListeners.contains(listener)) {
205135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani                mListeners.add(listener);
206135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani            }
207135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani        }
208135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    }
209258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani
210258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    /**
2112a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani     * Unregisters a {@link android.mtp.MtpClient.Listener} interface.
2124428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     *
2131952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani     * @param listener the listener to unregister
214135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani     */
215135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    public void removeListener(Listener listener) {
216135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani        synchronized (mDevices) {
2171952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani            mListeners.remove(listener);
2181952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani        }
2191952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani    }
2201952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani
221d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    /**
2221638931166b7b5571886a8fe6d413ea90d4194b5Amith Yamasani     * Retrieves an {@link android.mtp.MtpDevice} object for the USB device
223ffe0cb49d14d9c21b5609de009f7e7434e5b0753Jeff Sharkey     * with the given name.
224d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     *
225d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @param deviceName the name of the USB device
226d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @return the MtpDevice, or null if it does not exist
227d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     */
2281952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani    public MtpDevice getDevice(String deviceName) {
2291952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani        synchronized (mDevices) {
230135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani            return mDevices.get(deviceName);
2314428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        }
232135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    }
233135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani
234135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    /**
235135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani     * Retrieves an {@link android.mtp.MtpDevice} object for the USB device
236258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * with the given ID.
237b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani     *
2382a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani     * @param id the ID of the USB device
239d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @return the MtpDevice, or null if it does not exist
2404428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     */
241135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    public MtpDevice getDevice(int id) {
242d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        synchronized (mDevices) {
243d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            return mDevices.get(UsbDevice.getDeviceName(id));
244d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        }
245d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    }
246135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani
247135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    /**
248135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani     * Retrieves a list of all currently connected {@link android.mtp.MtpDevice}.
249d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     *
250135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani     * @return the list of MtpDevices
2514b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani     */
252d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    public List<MtpDevice> getDeviceList() {
253d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        synchronized (mDevices) {
254d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            // Query the USB manager since devices might have attached
2554b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani            // before we added our listener.
2564b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani            for (UsbDevice usbDevice : mUsbManager.getDeviceList().values()) {
257258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani                if (mDevices.get(usbDevice.getDeviceName()) == null) {
258e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani                    openDeviceLocked(usbDevice);
2592a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani                }
2604428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn            }
261b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani
262d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            return new ArrayList<MtpDevice>(mDevices.values());
263d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        }
264d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn    }
265d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn
266e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani    /**
267e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * Retrieves a list of all {@link android.mtp.MtpStorageInfo}
268b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani     * for the MTP or PTP device with the given USB device name
269e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     *
270e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * @param deviceName the name of the USB device
271e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * @return the list of MtpStorageInfo
272e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     */
273e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani    public List<MtpStorageInfo> getStorageList(String deviceName) {
274e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani        MtpDevice device = getDevice(deviceName);
275e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani        if (device == null) {
276e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani            return null;
277b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        }
278b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        int[] storageIds = device.getStorageIds();
279258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        if (storageIds == null) {
280e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani            return null;
2813b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani        }
2823b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani
2833b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani        int length = storageIds.length;
284d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        ArrayList<MtpStorageInfo> storageList = new ArrayList<MtpStorageInfo>(length);
285d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn        for (int i = 0; i < length; i++) {
286d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            MtpStorageInfo info = device.getStorageInfo(storageIds[i]);
287d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            if (info == null) {
288d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                Log.w(TAG, "getStorageInfo failed");
289d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn            } else {
290d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn                storageList.add(info);
291e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani            }
2923b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani        }
2933b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani        return storageList;
2943b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani    }
2953b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani
296258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    /**
2972a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani     * Retrieves the {@link android.mtp.MtpObjectInfo} for an object on
2984428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     * the MTP or PTP device with the given USB device name with the given
299258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * object handle
300258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     *
301258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @param deviceName the name of the USB device
302258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @param objectHandle handle of the object to query
303258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @return the MtpObjectInfo
304d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     */
305258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    public MtpObjectInfo getObjectInfo(String deviceName, int objectHandle) {
306258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        MtpDevice device = getDevice(deviceName);
307258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        if (device == null) {
308258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            return null;
309258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        }
310258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        return device.getObjectInfo(objectHandle);
311258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    }
312258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani
313258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    /**
314258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * Deletes an object on the MTP or PTP device with the given USB device name.
315258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     *
316258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @param deviceName the name of the USB device
317258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @param objectHandle handle of the object to delete
318258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @return true if the deletion succeeds
319258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     */
320258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    public boolean deleteObject(String deviceName, int objectHandle) {
3214428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        MtpDevice device = getDevice(deviceName);
322258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        if (device == null) {
323258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            return false;
324258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        }
325258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        return device.deleteObject(objectHandle);
326258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani    }
327258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani
3282a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani    /**
329258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * Retrieves a list of {@link android.mtp.MtpObjectInfo} for all objects
330258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * on the MTP or PTP device with the given USB device name and given storage ID
331258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * and/or object handle.
3325dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn     * If the object handle is zero, then all objects in the root of the storage unit
3335dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn     * will be returned. Otherwise, all immediate children of the object will be returned.
3345dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn     * If the storage ID is also zero, then all objects on all storage units will be returned.
3355dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn     *
336d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @param deviceName the name of the USB device
337d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @param storageId the ID of the storage unit to query, or zero for all
338d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @param objectHandle the handle of the parent object to query, or zero for the storage root
339d4ac8d7b3de27a9f0e4c6af2496ca71d794e42d1Dianne Hackborn     * @return the list of MtpObjectInfo
3405dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn     */
3415dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn    public List<MtpObjectInfo> getObjectList(String deviceName, int storageId, int objectHandle) {
3425dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn        MtpDevice device = getDevice(deviceName);
3435dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn        if (device == null) {
3445dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn            return null;
3455dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn        }
346258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani        if (objectHandle == 0) {
347faea76ff8bc9a350765873af81334afe4d14afbdAmith Yamasani            // all objects in root of storage
348faea76ff8bc9a350765873af81334afe4d14afbdAmith Yamasani            objectHandle = 0xFFFFFFFF;
3495dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn        }
3505dc5a00e7ebadc085ded7e29feacd17e53698486Dianne Hackborn        int[] handles = device.getObjectHandles(storageId, 0, objectHandle);
35127bd34d9d9fe99f11b80aa0bbdb402fb47ef4158Jeff Sharkey        if (handles == null) {
352faea76ff8bc9a350765873af81334afe4d14afbdAmith Yamasani            return null;
353faea76ff8bc9a350765873af81334afe4d14afbdAmith Yamasani        }
354faea76ff8bc9a350765873af81334afe4d14afbdAmith Yamasani
3551952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani        int length = handles.length;
3561952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani        ArrayList<MtpObjectInfo> objectList = new ArrayList<MtpObjectInfo>(length);
3571952637425eece18aa1ce3d80d4b49086ef3bcf7Amith Yamasani        for (int i = 0; i < length; i++) {
358258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            MtpObjectInfo info = device.getObjectInfo(handles[i]);
359258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            if (info == null) {
360258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani                Log.w(TAG, "getObjectInfo failed");
361258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            } else {
3622a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani                objectList.add(info);
363258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani            }
3642a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani        }
3652a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani        return objectList;
3662a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani    }
3672a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani
3682a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani    /**
369258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * Returns the data for an object as a byte array.
370258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     *
371258848d2ae04f447ff1c18023fa76b139fcc0862Amith Yamasani     * @param deviceName the name of the USB device containing the object
372e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * @param objectHandle handle of the object to read
373b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani     * @param objectSize the size of the object (this should match
374b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani     *      {@link android.mtp.MtpObjectInfo#getCompressedSize}
375b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani     * @return the object's data, or null if reading fails
376b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani     */
377b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani    public byte[] getObject(String deviceName, int objectHandle, int objectSize) {
378b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        MtpDevice device = getDevice(deviceName);
379b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        if (device == null) {
380b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani            return null;
381b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        }
382b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        return device.getObject(objectHandle, objectSize);
383e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani    }
384e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani
3853b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani    /**
3863b49f07a452e0a77c1d22db2065255689a461d31Amith Yamasani     * Returns the thumbnail data for an object as a byte array.
387e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     *
388e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * @param deviceName the name of the USB device containing the object
389e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * @param objectHandle handle of the object to read
390e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     * @return the object's thumbnail, or null if reading fails
391e928d7d95dbb64627e6ff3a0572190c555b59d96Amith Yamasani     */
392b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani    public byte[] getThumbnail(String deviceName, int objectHandle) {
3932a00329c6d55c6cd9166e01963d7410e95d80d21Amith Yamasani        MtpDevice device = getDevice(deviceName);
394b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        if (device == null) {
395b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani            return null;
396b8151ecd6ef4faa5c16d0a4c3abb45ec84d1f97aAmith Yamasani        }
3970b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani        return device.getThumbnail(objectHandle);
3980b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani    }
3990b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani
4000b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani    /**
4010b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani     * Copies the data for an object to a file in external storage.
4021676c856d61b97c871dc2be0cb1f1fb1e12e24e9Dianne Hackborn     *
4034428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     * @param deviceName the name of the USB device containing the object
4047767eac3232ba2fb9828766813cdb481d6a97584Dianne Hackborn     * @param objectHandle handle of the object to read
4057767eac3232ba2fb9828766813cdb481d6a97584Dianne Hackborn     * @param destPath path to destination for the file transfer.
4060b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani     *      This path should be in the external storage as defined by
4070b285499db739ba50f2f839d633e763c70e67f96Amith Yamasani     *      {@link android.os.Environment#getExternalStorageDirectory}
4084428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     * @return true if the file transfer succeeds
4094428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn     */
4104428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn    public boolean importFile(String deviceName, int objectHandle, String destPath) {
4114428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn        MtpDevice device = getDevice(deviceName);
4124b2e934928a2eb65927bd39197198c28c49efb94Amith Yamasani        if (device == null) {
4134428e17c5e05c0dad76da8f1c28ccba62a66cd91Dianne Hackborn            return false;
414135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani        }
415135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani        return device.importFile(objectHandle, destPath);
416135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani    }
417135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani}
418135936072b24b090fb63940aea41b408d855a4f3Amith Yamasani