blob: 2f66c5cf17d70eb21ae79719ba459be2cc3c19c9 [file] [log] [blame]
Daichi Hironofda74742016-02-01 13:00:31 +09001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
Daichi Hirono8b9024f2015-08-12 12:59:09 +090017package com.android.mtp;
18
19import android.content.ContentResolver;
20import android.net.Uri;
21import android.os.Process;
22import android.provider.DocumentsContract;
23import android.util.Log;
24
Daichi Hirono619afda2016-02-07 14:23:43 +090025import java.io.FileNotFoundException;
Daichi Hironofda74742016-02-01 13:00:31 +090026import java.util.concurrent.CountDownLatch;
Daichi Hironoe1d57712015-11-17 10:55:45 +090027import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
Daichi Hironoe1d57712015-11-17 10:55:45 +090029import java.util.concurrent.TimeUnit;
Daichi Hirono8b9024f2015-08-12 12:59:09 +090030
31final class RootScanner {
32 /**
33 * Polling interval in milliseconds used for first SHORT_POLLING_TIMES because it is more
34 * likely to add new root just after the device is added.
35 */
36 private final static long SHORT_POLLING_INTERVAL = 2000;
37
38 /**
39 * Polling interval in milliseconds for low priority polling, when changes are not expected.
40 */
41 private final static long LONG_POLLING_INTERVAL = 30 * 1000;
42
43 /**
44 * @see #SHORT_POLLING_INTERVAL
45 */
46 private final static long SHORT_POLLING_TIMES = 10;
47
Daichi Hironoe1d57712015-11-17 10:55:45 +090048 /**
49 * Milliseconds we wait for background thread when pausing.
50 */
51 private final static long AWAIT_TERMINATION_TIMEOUT = 2000;
52
Daichi Hirono8b9024f2015-08-12 12:59:09 +090053 final ContentResolver mResolver;
54 final MtpManager mManager;
Daichi Hironodc473442015-11-13 15:42:28 +090055 final MtpDatabase mDatabase;
Daichi Hironoe1d57712015-11-17 10:55:45 +090056
57 ExecutorService mExecutor;
Daichi Hirono2e9a57b2016-02-26 17:41:45 +090058 private UpdateRootsRunnable mCurrentTask;
Daichi Hirono8b9024f2015-08-12 12:59:09 +090059
Daichi Hironodc473442015-11-13 15:42:28 +090060 RootScanner(
61 ContentResolver resolver,
Daichi Hironodc473442015-11-13 15:42:28 +090062 MtpManager manager,
63 MtpDatabase database) {
Daichi Hirono8b9024f2015-08-12 12:59:09 +090064 mResolver = resolver;
65 mManager = manager;
Daichi Hironodc473442015-11-13 15:42:28 +090066 mDatabase = database;
Daichi Hirono8b9024f2015-08-12 12:59:09 +090067 }
68
Daichi Hironoe1d57712015-11-17 10:55:45 +090069 /**
70 * Notifies a change of the roots list via ContentResolver.
71 */
Daichi Hironodc473442015-11-13 15:42:28 +090072 void notifyChange() {
Daichi Hironoebd24052016-02-06 21:05:57 +090073 final Uri uri = DocumentsContract.buildRootsUri(MtpDocumentsProvider.AUTHORITY);
Daichi Hironodc473442015-11-13 15:42:28 +090074 mResolver.notifyChange(uri, null, false);
Daichi Hirono8b9024f2015-08-12 12:59:09 +090075 }
76
77 /**
78 * Starts to check new changes right away.
Daichi Hirono8b9024f2015-08-12 12:59:09 +090079 */
Daichi Hironofda74742016-02-01 13:00:31 +090080 synchronized CountDownLatch resume() {
Daichi Hironoe1d57712015-11-17 10:55:45 +090081 if (mExecutor == null) {
82 // Only single thread updates the database.
83 mExecutor = Executors.newSingleThreadExecutor();
84 }
85 if (mCurrentTask != null) {
Daichi Hirono2e9a57b2016-02-26 17:41:45 +090086 // Stop previous task.
87 mCurrentTask.stop();
Daichi Hironoe1d57712015-11-17 10:55:45 +090088 }
Daichi Hirono2e9a57b2016-02-26 17:41:45 +090089 mCurrentTask = new UpdateRootsRunnable();
90 mExecutor.execute(mCurrentTask);
91 return mCurrentTask.mFirstScanCompleted;
Daichi Hironoe1d57712015-11-17 10:55:45 +090092 }
93
94 /**
95 * Stops background thread and wait for its termination.
96 * @throws InterruptedException
97 */
98 synchronized void pause() throws InterruptedException {
99 if (mExecutor == null) {
Daichi Hironodc473442015-11-13 15:42:28 +0900100 return;
101 }
Daichi Hironoe1d57712015-11-17 10:55:45 +0900102 mExecutor.shutdownNow();
103 if (!mExecutor.awaitTermination(AWAIT_TERMINATION_TIMEOUT, TimeUnit.MILLISECONDS)) {
104 Log.e(MtpDocumentsProvider.TAG, "Failed to terminate RootScanner's background thread.");
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900105 }
Daichi Hironoe1d57712015-11-17 10:55:45 +0900106 mExecutor = null;
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900107 }
108
Daichi Hironoe1d57712015-11-17 10:55:45 +0900109 /**
110 * Runnable to scan roots and update the database information.
111 */
112 private final class UpdateRootsRunnable implements Runnable {
Daichi Hirono2e9a57b2016-02-26 17:41:45 +0900113 /**
114 * Count down latch that specifies the runnable is stopped.
115 */
116 final CountDownLatch mStopped = new CountDownLatch(1);
117
118 /**
119 * Count down latch that specifies the first scan is completed.
120 */
Daichi Hironofda74742016-02-01 13:00:31 +0900121 final CountDownLatch mFirstScanCompleted = new CountDownLatch(1);
122
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900123 @Override
124 public void run() {
125 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
Daichi Hironoe1d57712015-11-17 10:55:45 +0900126 int pollingCount = 0;
Daichi Hirono2e9a57b2016-02-26 17:41:45 +0900127 while (mStopped.getCount() > 0) {
Daichi Hironoe1d57712015-11-17 10:55:45 +0900128 boolean changed = false;
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000129
130 // Update devices.
Daichi Hirono20754c52015-12-15 18:52:26 +0900131 final MtpDeviceRecord[] devices = mManager.getDevices();
Daichi Hirono619afda2016-02-07 14:23:43 +0900132 try {
133 mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
134 for (final MtpDeviceRecord device : devices) {
135 if (mDatabase.getMapper().putDeviceDocument(device)) {
136 changed = true;
137 }
138 }
139 if (mDatabase.getMapper().stopAddingDocuments(
140 null /* parentDocumentId */)) {
Daichi Hirono20754c52015-12-15 18:52:26 +0900141 changed = true;
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900142 }
Daichi Hirono619afda2016-02-07 14:23:43 +0900143 } catch (FileNotFoundException exception) {
144 // The top root (ID is null) must exist always.
145 // FileNotFoundException is unexpected.
146 Log.e(MtpDocumentsProvider.TAG, "Unexpected FileNotFoundException", exception);
147 throw new AssertionError("Unexpected exception for the top parent", exception);
Daichi Hirono20754c52015-12-15 18:52:26 +0900148 }
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000149
150 // Update roots.
Daichi Hirono20754c52015-12-15 18:52:26 +0900151 for (final MtpDeviceRecord device : devices) {
152 final String documentId = mDatabase.getDocumentIdForDevice(device.deviceId);
153 if (documentId == null) {
154 continue;
155 }
Daichi Hirono619afda2016-02-07 14:23:43 +0900156 try {
157 mDatabase.getMapper().startAddingDocuments(documentId);
Daichi Hirono0f325372016-02-21 15:50:30 +0900158 if (mDatabase.getMapper().putStorageDocuments(
159 documentId, device.eventsSupported, device.roots)) {
Daichi Hirono619afda2016-02-07 14:23:43 +0900160 changed = true;
161 }
162 if (mDatabase.getMapper().stopAddingDocuments(documentId)) {
163 changed = true;
164 }
165 } catch (FileNotFoundException exception) {
166 Log.e(MtpDocumentsProvider.TAG, "Parent document is gone.", exception);
167 continue;
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000168 }
Daichi Hirono7a375c42015-12-14 17:14:29 +0900169 }
Daichi Hironob3fe72b2015-12-15 07:45:06 +0000170
Daichi Hironoe1d57712015-11-17 10:55:45 +0900171 if (changed) {
172 notifyChange();
173 }
Daichi Hironofda74742016-02-01 13:00:31 +0900174 mFirstScanCompleted.countDown();
Daichi Hironoe1d57712015-11-17 10:55:45 +0900175 pollingCount++;
176 try {
177 // Use SHORT_POLLING_PERIOD for the first SHORT_POLLING_TIMES because it is
178 // more likely to add new root just after the device is added.
179 // TODO: Use short interval only for a device that is just added.
Daichi Hirono2e9a57b2016-02-26 17:41:45 +0900180 mStopped.await(pollingCount > SHORT_POLLING_TIMES ?
181 LONG_POLLING_INTERVAL : SHORT_POLLING_INTERVAL, TimeUnit.MILLISECONDS);
Daichi Hironoe1d57712015-11-17 10:55:45 +0900182 } catch (InterruptedException exp) {
Daichi Hirono2bdb3882015-12-22 12:57:47 +0900183 break;
Daichi Hironoe1d57712015-11-17 10:55:45 +0900184 }
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900185 }
186 }
Daichi Hirono2e9a57b2016-02-26 17:41:45 +0900187
188 void stop() {
189 mStopped.countDown();
190 }
Daichi Hirono8b9024f2015-08-12 12:59:09 +0900191 }
192}