All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.mtp.MtpManager Maven / Gradle / Ivy

Go to download

A library jar that provides APIs for Applications written for the Google Android Platform.

There is a newer version: 15-robolectric-12650502
Show newest version
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.mtp;

import android.annotation.Nullable;
import android.content.Context;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.mtp.MtpConstants;
import android.mtp.MtpDevice;
import android.mtp.MtpDeviceInfo;
import android.mtp.MtpEvent;
import android.mtp.MtpObjectInfo;
import android.mtp.MtpStorageInfo;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.VisibleForTesting;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;

/**
 * The model wrapping android.mtp API.
 */
class MtpManager {
    final static int OBJECT_HANDLE_ROOT_CHILDREN = -1;

    /**
     * Subclass for PTP.
     */
    private static final int SUBCLASS_STILL_IMAGE_CAPTURE = 1;

    /**
     * Subclass for Android style MTP.
     */
    private static final int SUBCLASS_MTP = 0xff;

    /**
     * Protocol for Picture Transfer Protocol (PIMA 15470).
     */
    private static final int PROTOCOL_PICTURE_TRANSFER = 1;

    /**
     * Protocol for Android style MTP.
     */
    private static final int PROTOCOL_MTP = 0;

    private final UsbManager mManager;
    private final SparseArray mDevices = new SparseArray<>();

    MtpManager(Context context) {
        mManager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
    }

    synchronized MtpDeviceRecord openDevice(int deviceId) throws IOException {
        UsbDevice rawDevice = null;
        for (final UsbDevice candidate : mManager.getDeviceList().values()) {
            if (candidate.getDeviceId() == deviceId) {
                rawDevice = candidate;
                break;
            }
        }

        ensureNotNull(rawDevice, "Not found USB device: " + deviceId);

        if (!mManager.hasPermission(rawDevice)) {
            mManager.grantPermission(rawDevice);
            if (!mManager.hasPermission(rawDevice)) {
                throw new IOException("Failed to grant a device permission.");
            }
        }

        final MtpDevice device = new MtpDevice(rawDevice);

        final UsbDeviceConnection connection = ensureNotNull(
                mManager.openDevice(rawDevice),
                "Failed to open a USB connection.");

        if (!device.open(connection)) {
            // We cannot open connection when another application use the device.
            throw new BusyDeviceException();
        }

        // Handle devices that fail to obtain storages just after opening a MTP session.
        final int[] storageIds = ensureNotNull(
                device.getStorageIds(),
                "Not found MTP storages in the device.");

        mDevices.put(deviceId, device);
        return createDeviceRecord(rawDevice);
    }

    synchronized void closeDevice(int deviceId) throws IOException {
        getDevice(deviceId).close();
        mDevices.remove(deviceId);
    }

    synchronized MtpDeviceRecord[] getDevices() {
        final ArrayList devices = new ArrayList<>();
        for (UsbDevice device : mManager.getDeviceList().values()) {
            if (!isMtpDevice(device)) {
                continue;
            }
            devices.add(createDeviceRecord(device));
        }
        return devices.toArray(new MtpDeviceRecord[devices.size()]);
    }

    MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return ensureNotNull(
                    device.getObjectInfo(objectHandle),
                    "Failed to get object info: " + objectHandle);
        }
    }

    int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle)
            throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return ensureNotNull(
                    device.getObjectHandles(storageId, 0 /* all format */, parentObjectHandle),
                    "Failed to fetch object handles.");
        }
    }

    byte[] getObject(int deviceId, int objectHandle, int expectedSize)
            throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return ensureNotNull(
                    device.getObject(objectHandle, expectedSize),
                    "Failed to fetch object bytes");
        }
    }

    long getPartialObject(int deviceId, int objectHandle, long offset, long size, byte[] buffer)
            throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return device.getPartialObject(objectHandle, offset, size, buffer);
        }
    }

    long getPartialObject64(int deviceId, int objectHandle, long offset, long size, byte[] buffer)
            throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return device.getPartialObject64(objectHandle, offset, size, buffer);
        }
    }

    byte[] getThumbnail(int deviceId, int objectHandle) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            return ensureNotNull(
                    device.getThumbnail(objectHandle),
                    "Failed to obtain thumbnail bytes");
        }
    }

    void deleteDocument(int deviceId, int objectHandle) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            if (!device.deleteObject(objectHandle)) {
                throw new IOException("Failed to delete document");
            }
        }
    }

    int createDocument(int deviceId, MtpObjectInfo objectInfo,
            ParcelFileDescriptor source) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            final MtpObjectInfo sendObjectInfoResult = device.sendObjectInfo(objectInfo);
            if (sendObjectInfoResult == null) {
                throw new SendObjectInfoFailure();
            }
            if (objectInfo.getFormat() != MtpConstants.FORMAT_ASSOCIATION) {
                if (!device.sendObject(sendObjectInfoResult.getObjectHandle(),
                        sendObjectInfoResult.getCompressedSizeLong(), source)) {
                    throw new IOException("Failed to send contents of a document");
                }
            }
            return sendObjectInfoResult.getObjectHandle();
        }
    }

    int getParent(int deviceId, int objectHandle) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            final int result = (int) device.getParent(objectHandle);
            if (result == 0xffffffff) {
                throw new FileNotFoundException("Not found parent object");
            }
            return result;
        }
    }

    void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target)
            throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            if (!device.importFile(objectHandle, target)) {
                throw new IOException("Failed to import file to FD");
            }
        }
    }

    @VisibleForTesting
    MtpEvent readEvent(int deviceId, CancellationSignal signal) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        return device.readEvent(signal);
    }

    long getObjectSizeLong(int deviceId, int objectHandle, int format) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        return device.getObjectSizeLong(objectHandle, format);
    }

    private synchronized MtpDevice getDevice(int deviceId) throws IOException {
        return ensureNotNull(
                mDevices.get(deviceId),
                "USB device " + deviceId + " is not opened.");
    }

    private MtpRoot[] getRoots(int deviceId) throws IOException {
        final MtpDevice device = getDevice(deviceId);
        synchronized (device) {
            final int[] storageIds =
                    ensureNotNull(device.getStorageIds(), "Failed to obtain storage IDs.");
            final ArrayList roots = new ArrayList<>();
            for (int i = 0; i < storageIds.length; i++) {
                final MtpStorageInfo info = device.getStorageInfo(storageIds[i]);
                if (info == null) {
                    continue;
                }
                roots.add(new MtpRoot(device.getDeviceId(), info));
            }
            return roots.toArray(new MtpRoot[roots.size()]);
        }
    }

    private MtpDeviceRecord createDeviceRecord(UsbDevice device) {
        final MtpDevice mtpDevice = mDevices.get(device.getDeviceId());
        final boolean opened = mtpDevice != null;
        final String name = device.getProductName();
        MtpRoot[] roots;
        int[] operationsSupported = null;
        int[] eventsSupported = null;
        if (opened) {
            try {
                roots = getRoots(device.getDeviceId());
            } catch (IOException exp) {
                Log.e(MtpDocumentsProvider.TAG, "Failed to open device", exp);
                // If we failed to fetch roots for the device, we still returns device model
                // with an empty set of roots so that the device is shown DocumentsUI as long as
                // the device is physically connected.
                roots = new MtpRoot[0];
            }
            final MtpDeviceInfo info = mtpDevice.getDeviceInfo();
            if (info != null) {
                operationsSupported = info.getOperationsSupported();
                eventsSupported = info.getEventsSupported();
            }
        } else {
            roots = new MtpRoot[0];
        }
        return new MtpDeviceRecord(
                device.getDeviceId(), name, device.getSerialNumber(), opened, roots,
                operationsSupported, eventsSupported);
    }

    static boolean isMtpDevice(UsbDevice device) {
        for (int i = 0; i < device.getInterfaceCount(); i++) {
            final UsbInterface usbInterface = device.getInterface(i);
            if ((usbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_STILL_IMAGE &&
                    usbInterface.getInterfaceSubclass() == SUBCLASS_STILL_IMAGE_CAPTURE &&
                    usbInterface.getInterfaceProtocol() == PROTOCOL_PICTURE_TRANSFER)) {
                return true;
            }
            if (usbInterface.getInterfaceClass() == UsbConstants.USB_SUBCLASS_VENDOR_SPEC &&
                    usbInterface.getInterfaceSubclass() == SUBCLASS_MTP &&
                    usbInterface.getInterfaceProtocol() == PROTOCOL_MTP &&
                    "MTP".equals(usbInterface.getName())) {
                return true;
            }
        }
        return false;
    }

    private static  T ensureNotNull(@Nullable T t, String errorMessage) throws IOException {
        if (t != null) {
            return t;
        } else {
            throw new IOException(errorMessage);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy