diff --git a/packages/MtpDocumentsProvider/Android.mk b/packages/MtpDocumentsProvider/Android.mk index ec18463c71631..5f76a9a988fc1 100644 --- a/packages/MtpDocumentsProvider/Android.mk +++ b/packages/MtpDocumentsProvider/Android.mk @@ -5,6 +5,8 @@ LOCAL_MODULE_TAGS := optional LOCAL_SRC_FILES := $(call all-java-files-under, src) LOCAL_PACKAGE_NAME := MtpDocumentsProvider LOCAL_CERTIFICATE := media +LOCAL_PROGUARD_FLAGS := '-keepclassmembers class * {' \ + ' @com.android.internal.annotations.VisibleForTesting *; }' include $(BUILD_PACKAGE) include $(LOCAL_PATH)/tests/Android.mk diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index ba3067e17b3af..0cfa749d49359 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -16,14 +16,24 @@ package com.android.mtp; +import android.content.ContentResolver; import android.database.Cursor; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.provider.DocumentsProvider; -import java.io.FileNotFoundException; +import android.util.Log; +import com.android.internal.annotations.VisibleForTesting; + +import java.io.FileNotFoundException; +import java.io.IOException; + +/** + * DocumentsProvider for MTP devices. + */ public class MtpDocumentsProvider extends DocumentsProvider { private static final String TAG = "MtpDocumentsProvider"; public static final String AUTHORITY = "com.android.mtp.documents"; @@ -40,32 +50,89 @@ public class MtpDocumentsProvider extends DocumentsProvider { Document.COLUMN_FLAGS, Document.COLUMN_SIZE, }; + private MtpManager mDeviceModel; + private ContentResolver mResolver; + @Override public boolean onCreate() { + mDeviceModel = new MtpManager(getContext()); + mResolver = getContext().getContentResolver(); return true; } + @VisibleForTesting + void onCreateForTesting(MtpManager deviceModel, ContentResolver resolver) { + this.mDeviceModel = deviceModel; + this.mResolver = resolver; + } + @Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { - return null; + throw new FileNotFoundException(); } @Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { - return null; + throw new FileNotFoundException(); } @Override public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { - return null; + throw new FileNotFoundException(); } @Override public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { - return null; + throw new FileNotFoundException(); + } + + // TODO: Remove annotation when the method starts to be used. + @VisibleForTesting + void openDevice(int deviceId) { + try { + mDeviceModel.openDevice(deviceId); + notifyRootsUpdate(); + } catch (IOException error) { + Log.d(TAG, "Failed to open the MTP device: " + deviceId); + } + } + + // TODO: Remove annotation when the method starts to be used. + @VisibleForTesting + void closeDevice(int deviceId) { + try { + mDeviceModel.closeDevice(deviceId); + notifyRootsUpdate(); + } catch (IOException error) { + Log.d(TAG, "Failed to close the MTP device: " + deviceId); + } + } + + // TODO: Remove annotation when the method starts to be used. + @VisibleForTesting + void closeAllDevices() { + boolean closed = false; + for (int deviceId : mDeviceModel.getOpenedDeviceIds()) { + try { + mDeviceModel.closeDevice(deviceId); + closed = true; + } catch (IOException d) { + Log.d(TAG, "Failed to close the MTP device: " + deviceId); + } + } + if (closed) { + notifyRootsUpdate(); + } + } + + private void notifyRootsUpdate() { + mResolver.notifyChange( + DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY), + null, + false); } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java new file mode 100644 index 0000000000000..eea38b1247141 --- /dev/null +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -0,0 +1,42 @@ +/* + * 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.content.Context; + +import java.io.IOException; + +/** + * The model wrapping android.mtp API. + */ +class MtpManager { + MtpManager(Context context) { + } + + void openDevice(int deviceId) throws IOException { + // TODO: Implement this method. + } + + void closeDevice(int deviceId) throws IOException { + // TODO: Implement this method. + } + + int[] getOpenedDeviceIds() { + // TODO: Implement this method. + return null; + } +} diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 990d6ae63bb28..7e91cc765f29e 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -16,13 +16,92 @@ package com.android.mtp; +import android.content.Context; +import android.database.ContentObserver; +import android.net.Uri; import android.test.AndroidTestCase; +import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.SmallTest; +import java.io.IOException; + @SmallTest public class MtpDocumentsProviderTest extends AndroidTestCase { - public void testBasic() { + public void testOpenAndCloseDevice() throws Exception { + final ContentResolver resolver = new ContentResolver(); final MtpDocumentsProvider provider = new MtpDocumentsProvider(); - assertNotNull(provider); + provider.onCreateForTesting(new MtpManagerMock(getContext()), resolver); + + provider.openDevice(MtpManagerMock.SUCCESS_DEVICE_ID); + assertEquals(1, resolver.changeCount); + provider.closeDevice(MtpManagerMock.SUCCESS_DEVICE_ID); + assertEquals(2, resolver.changeCount); + + provider.openDevice(MtpManagerMock.FAILURE_DEVICE_ID); + assertEquals(2, resolver.changeCount); + provider.closeDevice(MtpManagerMock.FAILURE_DEVICE_ID); + assertEquals(2, resolver.changeCount); + } + + public void testCloseAllDevices() { + final ContentResolver resolver = new ContentResolver(); + final MtpDocumentsProvider provider = new MtpDocumentsProvider(); + provider.onCreateForTesting(new MtpManagerMock(getContext()), resolver); + + provider.closeAllDevices(); + assertEquals(0, resolver.changeCount); + + provider.openDevice(MtpManagerMock.SUCCESS_DEVICE_ID); + assertEquals(1, resolver.changeCount); + + provider.closeAllDevices(); + assertEquals(2, resolver.changeCount); + } + + private static class MtpManagerMock extends MtpManager { + final static int SUCCESS_DEVICE_ID = 1; + final static int FAILURE_DEVICE_ID = 2; + + private boolean opened = false; + + MtpManagerMock(Context context) { + super(context); + } + + @Override + void openDevice(int deviceId) throws IOException { + if (deviceId == SUCCESS_DEVICE_ID) { + opened = true; + } else { + throw new IOException(); + } + } + + @Override + void closeDevice(int deviceId) throws IOException { + if (opened && deviceId == SUCCESS_DEVICE_ID) { + opened = false; + } else { + throw new IOException(); + } + } + + @Override + int[] getOpenedDeviceIds() { + if (opened) { + return new int[] { SUCCESS_DEVICE_ID }; + } else { + return new int[0]; + } + } + } + + private static class ContentResolver extends MockContentResolver { + int changeCount = 0; + + @Override + public void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork) { + changeCount++; + } } }