RootScanner.java revision 20754c5a112e418c11cc88176283db2c4bf2efd6
1package com.android.mtp; 2 3import android.content.ContentResolver; 4import android.content.res.Resources; 5import android.net.Uri; 6import android.os.Process; 7import android.provider.DocumentsContract; 8import android.util.Log; 9 10import java.io.IOException; 11import java.util.HashMap; 12import java.util.Map; 13import java.util.concurrent.ExecutorService; 14import java.util.concurrent.Executors; 15import java.util.concurrent.FutureTask; 16import java.util.concurrent.TimeUnit; 17 18final class RootScanner { 19 /** 20 * Polling interval in milliseconds used for first SHORT_POLLING_TIMES because it is more 21 * likely to add new root just after the device is added. 22 */ 23 private final static long SHORT_POLLING_INTERVAL = 2000; 24 25 /** 26 * Polling interval in milliseconds for low priority polling, when changes are not expected. 27 */ 28 private final static long LONG_POLLING_INTERVAL = 30 * 1000; 29 30 /** 31 * @see #SHORT_POLLING_INTERVAL 32 */ 33 private final static long SHORT_POLLING_TIMES = 10; 34 35 /** 36 * Milliseconds we wait for background thread when pausing. 37 */ 38 private final static long AWAIT_TERMINATION_TIMEOUT = 2000; 39 40 final ContentResolver mResolver; 41 final Resources mResources; 42 final MtpManager mManager; 43 final MtpDatabase mDatabase; 44 45 ExecutorService mExecutor; 46 FutureTask<Void> mCurrentTask; 47 48 RootScanner( 49 ContentResolver resolver, 50 Resources resources, 51 MtpManager manager, 52 MtpDatabase database) { 53 mResolver = resolver; 54 mResources = resources; 55 mManager = manager; 56 mDatabase = database; 57 } 58 59 /** 60 * Notifies a change of the roots list via ContentResolver. 61 */ 62 void notifyChange() { 63 final Uri uri = 64 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); 65 mResolver.notifyChange(uri, null, false); 66 } 67 68 /** 69 * Starts to check new changes right away. 70 * If the background thread has already gone, it restarts another background thread. 71 */ 72 synchronized void resume() { 73 if (mExecutor == null) { 74 // Only single thread updates the database. 75 mExecutor = Executors.newSingleThreadExecutor(); 76 } 77 if (mCurrentTask != null) { 78 // Cancel previous task. 79 mCurrentTask.cancel(true); 80 } 81 mCurrentTask = new FutureTask<Void>(new UpdateRootsRunnable(), null); 82 mExecutor.submit(mCurrentTask); 83 } 84 85 /** 86 * Stops background thread and wait for its termination. 87 * @throws InterruptedException 88 */ 89 synchronized void pause() throws InterruptedException { 90 if (mExecutor == null) { 91 return; 92 } 93 mExecutor.shutdownNow(); 94 if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) { 95 Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread."); 96 } 97 mExecutor = null; 98 } 99 100 /** 101 * Runnable to scan roots and update the database information. 102 */ 103 private final class UpdateRootsRunnable implements Runnable { 104 @Override 105 public void run() { 106 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 107 int pollingCount = 0; 108 while (!Thread.interrupted()) { 109 boolean changed = false; 110 111 // Update devices. 112 final MtpDeviceRecord[] devices = mManager.getDevices(); 113 mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */); 114 for (final MtpDeviceRecord device : devices) { 115 if (mDatabase.getMapper().putDeviceDocument(device)) { 116 changed = true; 117 } 118 } 119 if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) { 120 changed = true; 121 } 122 123 // Update roots. 124 for (final MtpDeviceRecord device : devices) { 125 final String documentId = mDatabase.getDocumentIdForDevice(device.deviceId); 126 if (documentId == null) { 127 continue; 128 } 129 mDatabase.getMapper().startAddingDocuments(documentId); 130 if (mDatabase.getMapper().putRootDocuments( 131 documentId, mResources, device.roots)) { 132 changed = true; 133 } 134 if (mDatabase.getMapper().stopAddingDocuments(documentId)) { 135 changed = true; 136 } 137 } 138 139 if (changed) { 140 notifyChange(); 141 } 142 pollingCount++; 143 try { 144 // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is 145 // more likely to add new root just after the device is added. 146 // TODO: Use short interval only for a device that is just added. 147 Thread.sleep(pollingCount > SHORT_POLLING_TIMES ? 148 LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL); 149 } catch (InterruptedException exp) { 150 // The while condition handles the interrupted flag. 151 continue; 152 } 153 } 154 } 155 } 156} 157