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