RootScanner.java revision dc47344660035b27a564ab6d9c9a9b58ec340347
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; 11 12final class RootScanner { 13 /** 14 * Polling interval in milliseconds used for first SHORT_POLLING_TIMES because it is more 15 * likely to add new root just after the device is added. 16 */ 17 private final static long SHORT_POLLING_INTERVAL = 2000; 18 19 /** 20 * Polling interval in milliseconds for low priority polling, when changes are not expected. 21 */ 22 private final static long LONG_POLLING_INTERVAL = 30 * 1000; 23 24 /** 25 * @see #SHORT_POLLING_INTERVAL 26 */ 27 private final static long SHORT_POLLING_TIMES = 10; 28 29 final ContentResolver mResolver; 30 final Resources mResources; 31 final MtpManager mManager; 32 final MtpDatabase mDatabase; 33 boolean mClosed = false; 34 int mPollingCount; 35 Thread mBackgroundThread; 36 37 RootScanner( 38 ContentResolver resolver, 39 Resources resources, 40 MtpManager manager, 41 MtpDatabase database) { 42 mResolver = resolver; 43 mResources = resources; 44 mManager = manager; 45 mDatabase = database; 46 } 47 48 void notifyChange() { 49 final Uri uri = 50 DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY); 51 mResolver.notifyChange(uri, null, false); 52 } 53 54 /** 55 * Starts to check new changes right away. 56 * If the background thread has already gone, it restarts another background thread. 57 */ 58 synchronized void scanNow() { 59 if (mClosed) { 60 return; 61 } 62 mPollingCount = 0; 63 if (mBackgroundThread == null) { 64 mBackgroundThread = new BackgroundLoaderThread(); 65 mBackgroundThread.start(); 66 } else { 67 notify(); 68 } 69 } 70 71 void close() throws InterruptedException { 72 Thread thread; 73 synchronized (this) { 74 mClosed = true; 75 thread = mBackgroundThread; 76 if (mBackgroundThread == null) { 77 return; 78 } 79 notify(); 80 } 81 thread.join(); 82 } 83 84 private final class BackgroundLoaderThread extends Thread { 85 @Override 86 public void run() { 87 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); 88 synchronized (RootScanner.this) { 89 while (!mClosed) { 90 final int[] deviceIds = mManager.getOpenedDeviceIds(); 91 if (deviceIds.length == 0) { 92 break; 93 } 94 boolean changed = false; 95 for (int deviceId : deviceIds) { 96 try { 97 mDatabase.startAddingRootDocuments(deviceId); 98 changed = mDatabase.putRootDocuments( 99 deviceId, mResources, mManager.getRoots(deviceId)) || changed; 100 changed = mDatabase.stopAddingRootDocuments(deviceId) || changed; 101 } catch (IOException exp) { 102 // The error may happen on the device. We would like to continue getting 103 // roots for other devices. 104 Log.e(MtpDocumentsProvider.TAG, exp.getMessage()); 105 continue; 106 } 107 } 108 if (changed) { 109 notifyChange(); 110 } 111 mPollingCount++; 112 try { 113 // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is 114 // more likely to add new root just after the device is added. 115 // TODO: Use short interval only for a device that is just added. 116 RootScanner.this.wait( 117 mPollingCount > SHORT_POLLING_TIMES ? 118 LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL); 119 } catch (InterruptedException exception) { 120 break; 121 } 122 } 123 124 mBackgroundThread = null; 125 } 126 } 127 } 128} 129