Loading packages/MtpDocumentsProvider/AndroidManifest.xml +1 −5 Original line number Diff line number Diff line Loading @@ -30,16 +30,12 @@ </activity> <receiver android:name=".UsbIntentReceiver" android:exported="true"> <!-- TODO: Remove an intent-filter for USB_DEVICE_ATTACHED after the provider becomes to open devices on demand. --> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> </intent-filter> </receiver> </application> </manifest> packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +32 −4 Original line number Diff line number Diff line Loading @@ -70,6 +70,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private Resources mResources; private MtpDatabase mDatabase; private AppFuse mAppFuse; private ServiceIntentSender mIntentSender; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -88,6 +89,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = new ServiceIntentSender(getContext()); // TODO: Mount AppFuse on demands. try { mAppFuse.mount(getContext().getSystemService(StorageManager.class)); Loading @@ -105,7 +107,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { MtpManager mtpManager, ContentResolver resolver, MtpDatabase database, StorageManager storageManager) { StorageManager storageManager, ServiceIntentSender intentSender) { mResources = resources; mMtpManager = mtpManager; mResolver = resolver; Loading @@ -113,6 +116,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDatabase = database; mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = intentSender; // TODO: Mount AppFuse on demands. try { mAppFuse.mount(storageManager); Loading Loading @@ -152,6 +156,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { } Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); try { openDevice(parentIdentifier.mDeviceId); if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { final Identifier singleStorageIdentifier = mDatabase.getSingleStorageIdentifier(parentDocumentId); Loading @@ -176,11 +181,12 @@ public class MtpDocumentsProvider extends DocumentsProvider { throws FileNotFoundException { final Identifier identifier = mDatabase.createIdentifier(documentId); try { openDevice(identifier.mDeviceId); switch (mode) { case "r": final long fileSize = getFileSize(documentId); // MTP getPartialObject operation does not support files that are larger than 4GB. // Fallback to non-seekable file descriptor. // MTP getPartialObject operation does not support files that are larger than // 4GB. Fallback to non-seekable file descriptor. // TODO: Use getPartialObject64 for MTP devices that support Android vendor // extension. if (fileSize <= 0xffffffffl) { Loading Loading @@ -213,6 +219,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { CancellationSignal signal) throws FileNotFoundException { final Identifier identifier = mDatabase.createIdentifier(documentId); try { openDevice(identifier.mDeviceId); return new AssetFileDescriptor( getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 0, // Start offset. Loading @@ -227,6 +234,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { public void deleteDocument(String documentId) throws FileNotFoundException { try { final Identifier identifier = mDatabase.createIdentifier(documentId); openDevice(identifier.mDeviceId); final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); mDatabase.deleteDocument(documentId); Loading Loading @@ -259,6 +267,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { throws FileNotFoundException { try { final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); openDevice(parentId.mDeviceId); final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); pipe[0].close(); // 0 bytes for a new document. final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? Loading Loading @@ -286,11 +295,19 @@ public class MtpDocumentsProvider extends DocumentsProvider { void openDevice(int deviceId) throws IOException { synchronized (mDeviceListLock) { if (mDeviceToolkits.containsKey(deviceId)) { return; } mMtpManager.openDevice(deviceId); mDeviceToolkits.put( deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); mIntentSender.sendUpdateNotificationIntent(); try { mRootScanner.resume().await(); } catch (InterruptedException error) { Log.e(TAG, "openDevice", error); } } mRootScanner.resume(); } void closeDevice(int deviceId) throws IOException, InterruptedException { Loading @@ -298,6 +315,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { closeDeviceInternal(deviceId); } mRootScanner.resume(); mIntentSender.sendUpdateNotificationIntent(); } int[] getOpenedDeviceIds() { Loading @@ -317,6 +335,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } /** * Resumes root scanner to handle the update of device list. */ void resumeRootScanner() { mRootScanner.resume(); } /** * Finalize the content provider for unit tests. */ Loading Loading @@ -356,6 +381,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { // TODO: Flush the device before closing (if not closed externally). if (!mDeviceToolkits.containsKey(deviceId)) { return; } getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); mDeviceToolkits.remove(deviceId); mMtpManager.closeDevice(deviceId); Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +7 −28 Original line number Diff line number Diff line Loading @@ -19,13 +19,12 @@ package com.android.mtp; import android.app.Notification; import android.app.Service; import android.app.NotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.usb.UsbDevice; import android.os.IBinder; import android.util.Log; import com.android.internal.util.Preconditions; import java.io.IOException; /** Loading @@ -36,6 +35,7 @@ import java.io.IOException; public class MtpDocumentsService extends Service { static final String ACTION_OPEN_DEVICE = "com.android.mtp.OPEN_DEVICE"; static final String ACTION_CLOSE_DEVICE = "com.android.mtp.CLOSE_DEVICE"; static final String ACTION_UPDATE_NOTIFICATION = "com.android.mtp.UPDATE_NOTIFICATION"; static final String EXTRA_DEVICE = "device"; NotificationManager mNotificationManager; Loading @@ -55,33 +55,11 @@ public class MtpDocumentsService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { // If intent is null, the service was restarted. if (intent != null) { final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE); try { Preconditions.checkNotNull(device); switch (intent.getAction()) { case ACTION_OPEN_DEVICE: provider.openDevice(device.getDeviceId()); break; case ACTION_CLOSE_DEVICE: mNotificationManager.cancel(device.getDeviceId()); provider.closeDevice(device.getDeviceId()); break; default: throw new IllegalArgumentException("Received unknown intent action."); } } catch (IOException | InterruptedException | IllegalArgumentException error) { logErrorMessage(error); } } else { // TODO: Fetch devices again. } if (intent == null || ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) { return updateForegroundState() ? START_STICKY : START_NOT_STICKY; } return START_NOT_STICKY; } /** * Updates the foreground state of the service. Loading @@ -92,6 +70,7 @@ public class MtpDocumentsService extends Service { final int[] deviceIds = provider.getOpenedDeviceIds(); int notificationId = 0; Notification notification = null; // TODO: Hide notification if the device has already been removed. for (final int deviceId : deviceIds) { try { final String title = getResources().getString( Loading packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +24 −3 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.ContentResolver; Loading @@ -7,6 +23,7 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; Loading Loading @@ -64,9 +81,8 @@ final class RootScanner { /** * Starts to check new changes right away. * If the background thread has already gone, it restarts another background thread. */ synchronized void resume() { synchronized CountDownLatch resume() { if (mExecutor == null) { // Only single thread updates the database. mExecutor = Executors.newSingleThreadExecutor(); Loading @@ -75,8 +91,10 @@ final class RootScanner { // Cancel previous task. mCurrentTask.cancel(true); } mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null); final UpdateRootsRunnable runnable = new UpdateRootsRunnable(); mCurrentTask = new FutureTask<Void>(runnable, null); mExecutor.submit(mCurrentTask); return runnable.mFirstScanCompleted; } /** Loading @@ -98,6 +116,8 @@ final class RootScanner { * Runnable to scan roots and update the database information. */ private final class UpdateRootsRunnable implements Runnable { final CountDownLatch mFirstScanCompleted = new CountDownLatch(1); @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Loading Loading @@ -136,6 +156,7 @@ final class RootScanner { if (changed) { notifyChange(); } mFirstScanCompleted.countDown(); pollingCount++; try { // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is Loading packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.ComponentName; import android.content.Context; import android.content.Intent; /** * Sends intent to MtpDocumentsService. */ class ServiceIntentSender { private Context mContext; ServiceIntentSender(Context context) { mContext = context; } void sendUpdateNotificationIntent() { final Intent intent = new Intent(MtpDocumentsService.ACTION_UPDATE_NOTIFICATION); intent.setComponent(new ComponentName(mContext, MtpDocumentsService.class)); mContext.startService(intent); } } Loading
packages/MtpDocumentsProvider/AndroidManifest.xml +1 −5 Original line number Diff line number Diff line Loading @@ -30,16 +30,12 @@ </activity> <receiver android:name=".UsbIntentReceiver" android:exported="true"> <!-- TODO: Remove an intent-filter for USB_DEVICE_ATTACHED after the provider becomes to open devices on demand. --> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> </intent-filter> <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter" /> <intent-filter> <action android:name="android.hardware.usb.action.USB_DEVICE_DETACHED" /> </intent-filter> </receiver> </application> </manifest>
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +32 −4 Original line number Diff line number Diff line Loading @@ -70,6 +70,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { private Resources mResources; private MtpDatabase mDatabase; private AppFuse mAppFuse; private ServiceIntentSender mIntentSender; /** * Provides singleton instance to MtpDocumentsService. Loading @@ -88,6 +89,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDatabase = new MtpDatabase(getContext(), MtpDatabaseConstants.FLAG_DATABASE_IN_FILE); mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = new ServiceIntentSender(getContext()); // TODO: Mount AppFuse on demands. try { mAppFuse.mount(getContext().getSystemService(StorageManager.class)); Loading @@ -105,7 +107,8 @@ public class MtpDocumentsProvider extends DocumentsProvider { MtpManager mtpManager, ContentResolver resolver, MtpDatabase database, StorageManager storageManager) { StorageManager storageManager, ServiceIntentSender intentSender) { mResources = resources; mMtpManager = mtpManager; mResolver = resolver; Loading @@ -113,6 +116,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { mDatabase = database; mRootScanner = new RootScanner(mResolver, mResources, mMtpManager, mDatabase); mAppFuse = new AppFuse(TAG, new AppFuseCallback()); mIntentSender = intentSender; // TODO: Mount AppFuse on demands. try { mAppFuse.mount(storageManager); Loading Loading @@ -152,6 +156,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { } Identifier parentIdentifier = mDatabase.createIdentifier(parentDocumentId); try { openDevice(parentIdentifier.mDeviceId); if (parentIdentifier.mDocumentType == MtpDatabaseConstants.DOCUMENT_TYPE_DEVICE) { final Identifier singleStorageIdentifier = mDatabase.getSingleStorageIdentifier(parentDocumentId); Loading @@ -176,11 +181,12 @@ public class MtpDocumentsProvider extends DocumentsProvider { throws FileNotFoundException { final Identifier identifier = mDatabase.createIdentifier(documentId); try { openDevice(identifier.mDeviceId); switch (mode) { case "r": final long fileSize = getFileSize(documentId); // MTP getPartialObject operation does not support files that are larger than 4GB. // Fallback to non-seekable file descriptor. // MTP getPartialObject operation does not support files that are larger than // 4GB. Fallback to non-seekable file descriptor. // TODO: Use getPartialObject64 for MTP devices that support Android vendor // extension. if (fileSize <= 0xffffffffl) { Loading Loading @@ -213,6 +219,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { CancellationSignal signal) throws FileNotFoundException { final Identifier identifier = mDatabase.createIdentifier(documentId); try { openDevice(identifier.mDeviceId); return new AssetFileDescriptor( getPipeManager(identifier).readThumbnail(mMtpManager, identifier), 0, // Start offset. Loading @@ -227,6 +234,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { public void deleteDocument(String documentId) throws FileNotFoundException { try { final Identifier identifier = mDatabase.createIdentifier(documentId); openDevice(identifier.mDeviceId); final Identifier parentIdentifier = mDatabase.getParentIdentifier(documentId); mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle); mDatabase.deleteDocument(documentId); Loading Loading @@ -259,6 +267,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { throws FileNotFoundException { try { final Identifier parentId = mDatabase.createIdentifier(parentDocumentId); openDevice(parentId.mDeviceId); final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe(); pipe[0].close(); // 0 bytes for a new document. final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ? Loading Loading @@ -286,11 +295,19 @@ public class MtpDocumentsProvider extends DocumentsProvider { void openDevice(int deviceId) throws IOException { synchronized (mDeviceListLock) { if (mDeviceToolkits.containsKey(deviceId)) { return; } mMtpManager.openDevice(deviceId); mDeviceToolkits.put( deviceId, new DeviceToolkit(mMtpManager, mResolver, mDatabase)); mIntentSender.sendUpdateNotificationIntent(); try { mRootScanner.resume().await(); } catch (InterruptedException error) { Log.e(TAG, "openDevice", error); } } mRootScanner.resume(); } void closeDevice(int deviceId) throws IOException, InterruptedException { Loading @@ -298,6 +315,7 @@ public class MtpDocumentsProvider extends DocumentsProvider { closeDeviceInternal(deviceId); } mRootScanner.resume(); mIntentSender.sendUpdateNotificationIntent(); } int[] getOpenedDeviceIds() { Loading @@ -317,6 +335,13 @@ public class MtpDocumentsProvider extends DocumentsProvider { } } /** * Resumes root scanner to handle the update of device list. */ void resumeRootScanner() { mRootScanner.resume(); } /** * Finalize the content provider for unit tests. */ Loading Loading @@ -356,6 +381,9 @@ public class MtpDocumentsProvider extends DocumentsProvider { private void closeDeviceInternal(int deviceId) throws IOException, InterruptedException { // TODO: Flush the device before closing (if not closed externally). if (!mDeviceToolkits.containsKey(deviceId)) { return; } getDeviceToolkit(deviceId).mDocumentLoader.clearTasks(); mDeviceToolkits.remove(deviceId); mMtpManager.closeDevice(deviceId); Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsService.java +7 −28 Original line number Diff line number Diff line Loading @@ -19,13 +19,12 @@ package com.android.mtp; import android.app.Notification; import android.app.Service; import android.app.NotificationManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.hardware.usb.UsbDevice; import android.os.IBinder; import android.util.Log; import com.android.internal.util.Preconditions; import java.io.IOException; /** Loading @@ -36,6 +35,7 @@ import java.io.IOException; public class MtpDocumentsService extends Service { static final String ACTION_OPEN_DEVICE = "com.android.mtp.OPEN_DEVICE"; static final String ACTION_CLOSE_DEVICE = "com.android.mtp.CLOSE_DEVICE"; static final String ACTION_UPDATE_NOTIFICATION = "com.android.mtp.UPDATE_NOTIFICATION"; static final String EXTRA_DEVICE = "device"; NotificationManager mNotificationManager; Loading @@ -55,33 +55,11 @@ public class MtpDocumentsService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { // If intent is null, the service was restarted. if (intent != null) { final MtpDocumentsProvider provider = MtpDocumentsProvider.getInstance(); final UsbDevice device = intent.<UsbDevice>getParcelableExtra(EXTRA_DEVICE); try { Preconditions.checkNotNull(device); switch (intent.getAction()) { case ACTION_OPEN_DEVICE: provider.openDevice(device.getDeviceId()); break; case ACTION_CLOSE_DEVICE: mNotificationManager.cancel(device.getDeviceId()); provider.closeDevice(device.getDeviceId()); break; default: throw new IllegalArgumentException("Received unknown intent action."); } } catch (IOException | InterruptedException | IllegalArgumentException error) { logErrorMessage(error); } } else { // TODO: Fetch devices again. } if (intent == null || ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) { return updateForegroundState() ? START_STICKY : START_NOT_STICKY; } return START_NOT_STICKY; } /** * Updates the foreground state of the service. Loading @@ -92,6 +70,7 @@ public class MtpDocumentsService extends Service { final int[] deviceIds = provider.getOpenedDeviceIds(); int notificationId = 0; Notification notification = null; // TODO: Hide notification if the device has already been removed. for (final int deviceId : deviceIds) { try { final String title = getResources().getString( Loading
packages/MtpDocumentsProvider/src/com/android/mtp/RootScanner.java +24 −3 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.ContentResolver; Loading @@ -7,6 +23,7 @@ import android.os.Process; import android.provider.DocumentsContract; import android.util.Log; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.FutureTask; Loading Loading @@ -64,9 +81,8 @@ final class RootScanner { /** * Starts to check new changes right away. * If the background thread has already gone, it restarts another background thread. */ synchronized void resume() { synchronized CountDownLatch resume() { if (mExecutor == null) { // Only single thread updates the database. mExecutor = Executors.newSingleThreadExecutor(); Loading @@ -75,8 +91,10 @@ final class RootScanner { // Cancel previous task. mCurrentTask.cancel(true); } mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null); final UpdateRootsRunnable runnable = new UpdateRootsRunnable(); mCurrentTask = new FutureTask<Void>(runnable, null); mExecutor.submit(mCurrentTask); return runnable.mFirstScanCompleted; } /** Loading @@ -98,6 +116,8 @@ final class RootScanner { * Runnable to scan roots and update the database information. */ private final class UpdateRootsRunnable implements Runnable { final CountDownLatch mFirstScanCompleted = new CountDownLatch(1); @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); Loading Loading @@ -136,6 +156,7 @@ final class RootScanner { if (changed) { notifyChange(); } mFirstScanCompleted.countDown(); pollingCount++; try { // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is Loading
packages/MtpDocumentsProvider/src/com/android/mtp/ServiceIntentSender.java 0 → 100644 +38 −0 Original line number Diff line number Diff line /* * Copyright (C) 2016 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.ComponentName; import android.content.Context; import android.content.Intent; /** * Sends intent to MtpDocumentsService. */ class ServiceIntentSender { private Context mContext; ServiceIntentSender(Context context) { mContext = context; } void sendUpdateNotificationIntent() { final Intent intent = new Intent(MtpDocumentsService.ACTION_UPDATE_NOTIFICATION); intent.setComponent(new ComponentName(mContext, MtpDocumentsService.class)); mContext.startService(intent); } }