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