2 * Copyright (C) 2013 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.gallery3d.ingest;
19 import com.android.gallery3d.R;
20 import com.android.gallery3d.ingest.data.ImportTask;
21 import com.android.gallery3d.ingest.data.IngestObjectInfo;
22 import com.android.gallery3d.ingest.data.MtpClient;
23 import com.android.gallery3d.ingest.data.MtpDeviceIndex;
25 import android.annotation.TargetApi;
26 import android.app.NotificationManager;
27 import android.app.PendingIntent;
28 import android.app.Service;
29 import android.content.Context;
30 import android.content.Intent;
31 import android.media.MediaScannerConnection;
32 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
33 import android.mtp.MtpDevice;
34 import android.mtp.MtpDeviceInfo;
35 import android.net.Uri;
36 import android.os.Binder;
37 import android.os.Build;
38 import android.os.IBinder;
39 import android.os.SystemClock;
40 import androidx.core.app.NotificationCompat;
41 import android.util.SparseBooleanArray;
42 import android.widget.Adapter;
44 import java.util.ArrayList;
45 import java.util.Collection;
46 import java.util.List;
49 * Service for MTP importing tasks.
51 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
52 public class IngestService extends Service implements ImportTask.Listener,
53 MtpDeviceIndex.ProgressListener, MtpClient.Listener {
56 * Convenience class to allow easy access to the service instance.
58 public class LocalBinder extends Binder {
59 IngestService getService() {
60 return IngestService.this;
64 private static final int PROGRESS_UPDATE_INTERVAL_MS = 180;
66 private MtpClient mClient;
67 private final IBinder mBinder = new LocalBinder();
68 private ScannerClient mScannerClient;
69 private MtpDevice mDevice;
70 private String mDevicePrettyName;
71 private MtpDeviceIndex mIndex;
72 private IngestActivity mClientActivity;
73 private boolean mRedeliverImportFinish = false;
74 private int mRedeliverImportFinishCount = 0;
75 private Collection<IngestObjectInfo> mRedeliverObjectsNotImported;
76 private boolean mRedeliverNotifyIndexChanged = false;
77 private boolean mRedeliverIndexFinish = false;
78 private NotificationManager mNotificationManager;
79 private NotificationCompat.Builder mNotificationBuilder;
80 private long mLastProgressIndexTime = 0;
81 private boolean mNeedRelaunchNotification = false;
84 public void onCreate() {
86 mScannerClient = new ScannerClient(this);
87 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
88 mNotificationBuilder = new NotificationCompat.Builder(this);
89 // TODO(georgescu): Use a better drawable for the notificaton?
90 mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_sync)
91 .setContentIntent(PendingIntent.getActivity(this, 0,
92 new Intent(this, IngestActivity.class), 0));
93 mIndex = MtpDeviceIndex.getInstance();
94 mIndex.setProgressListener(this);
96 mClient = new MtpClient(getApplicationContext());
97 List<MtpDevice> devices = mClient.getDeviceList();
98 if (!devices.isEmpty()) {
99 setDevice(devices.get(0));
101 mClient.addListener(this);
105 public void onDestroy() {
107 mIndex.unsetProgressListener(this);
112 public IBinder onBind(Intent intent) {
116 private void setDevice(MtpDevice device) {
117 if (mDevice == device) {
120 mRedeliverImportFinish = false;
121 mRedeliverObjectsNotImported = null;
122 mRedeliverNotifyIndexChanged = false;
123 mRedeliverIndexFinish = false;
125 mIndex.setDevice(mDevice);
126 if (mDevice != null) {
127 MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo();
128 if (deviceInfo == null) {
132 mDevicePrettyName = deviceInfo.getModel();
133 mNotificationBuilder.setContentTitle(mDevicePrettyName);
134 new Thread(mIndex.getIndexRunnable()).start();
137 mDevicePrettyName = null;
139 if (mClientActivity != null) {
140 mClientActivity.notifyIndexChanged();
142 mRedeliverNotifyIndexChanged = true;
146 protected MtpDeviceIndex getIndex() {
150 protected void setClientActivity(IngestActivity activity) {
151 if (mClientActivity == activity) {
154 mClientActivity = activity;
155 if (mClientActivity == null) {
156 if (mNeedRelaunchNotification) {
157 mNotificationBuilder.setProgress(0, 0, false)
158 .setContentText(getResources().getText(R.string.ingest_scanning_done));
159 mNotificationManager.notify(R.id.ingest_notification_scanning,
160 mNotificationBuilder.build());
164 mNotificationManager.cancel(R.id.ingest_notification_importing);
165 mNotificationManager.cancel(R.id.ingest_notification_scanning);
166 if (mRedeliverImportFinish) {
167 mClientActivity.onImportFinish(mRedeliverObjectsNotImported,
168 mRedeliverImportFinishCount);
169 mRedeliverImportFinish = false;
170 mRedeliverObjectsNotImported = null;
172 if (mRedeliverNotifyIndexChanged) {
173 mClientActivity.notifyIndexChanged();
174 mRedeliverNotifyIndexChanged = false;
176 if (mRedeliverIndexFinish) {
177 mClientActivity.onIndexingFinished();
178 mRedeliverIndexFinish = false;
180 if (mDevice != null) {
181 mNeedRelaunchNotification = true;
185 protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) {
186 List<IngestObjectInfo> importHandles = new ArrayList<IngestObjectInfo>();
187 for (int i = 0; i < selected.size(); i++) {
188 if (selected.valueAt(i)) {
189 Object item = adapter.getItem(selected.keyAt(i));
190 if (item instanceof IngestObjectInfo) {
191 importHandles.add(((IngestObjectInfo) item));
195 ImportTask task = new ImportTask(mDevice, importHandles, mDevicePrettyName, this);
196 task.setListener(this);
197 mNotificationBuilder.setProgress(0, 0, true)
198 .setContentText(getResources().getText(R.string.ingest_importing));
199 startForeground(R.id.ingest_notification_importing,
200 mNotificationBuilder.build());
201 new Thread(task).start();
205 public void deviceAdded(MtpDevice device) {
206 if (mDevice == null) {
212 public void deviceRemoved(MtpDevice device) {
213 if (device == mDevice) {
214 mNotificationManager.cancel(R.id.ingest_notification_scanning);
215 mNotificationManager.cancel(R.id.ingest_notification_importing);
217 mNeedRelaunchNotification = false;
223 public void onImportProgress(int visitedCount, int totalCount,
224 String pathIfSuccessful) {
225 if (pathIfSuccessful != null) {
226 mScannerClient.scanPath(pathIfSuccessful);
228 mNeedRelaunchNotification = false;
229 if (mClientActivity != null) {
230 mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful);
232 mNotificationBuilder.setProgress(totalCount, visitedCount, false)
233 .setContentText(getResources().getText(R.string.ingest_importing));
234 mNotificationManager.notify(R.id.ingest_notification_importing,
235 mNotificationBuilder.build());
239 public void onImportFinish(Collection<IngestObjectInfo> objectsNotImported,
241 stopForeground(true);
242 mNeedRelaunchNotification = true;
243 if (mClientActivity != null) {
244 mClientActivity.onImportFinish(objectsNotImported, visitedCount);
246 mRedeliverImportFinish = true;
247 mRedeliverObjectsNotImported = objectsNotImported;
248 mRedeliverImportFinishCount = visitedCount;
249 mNotificationBuilder.setProgress(0, 0, false)
250 .setContentText(getResources().getText(R.string.ingest_import_complete));
251 mNotificationManager.notify(R.id.ingest_notification_importing,
252 mNotificationBuilder.build());
257 public void onObjectIndexed(IngestObjectInfo object, int numVisited) {
258 mNeedRelaunchNotification = false;
259 if (mClientActivity != null) {
260 mClientActivity.onObjectIndexed(object, numVisited);
262 // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds
263 long currentTime = SystemClock.uptimeMillis();
264 if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) {
265 mLastProgressIndexTime = currentTime;
266 mNotificationBuilder.setProgress(0, numVisited, true)
267 .setContentText(getResources().getText(R.string.ingest_scanning));
268 mNotificationManager.notify(R.id.ingest_notification_scanning,
269 mNotificationBuilder.build());
275 public void onSortingStarted() {
276 if (mClientActivity != null) {
277 mClientActivity.onSortingStarted();
282 public void onIndexingFinished() {
283 mNeedRelaunchNotification = true;
284 if (mClientActivity != null) {
285 mClientActivity.onIndexingFinished();
287 mNotificationBuilder.setProgress(0, 0, false)
288 .setContentText(getResources().getText(R.string.ingest_scanning_done));
289 mNotificationManager.notify(R.id.ingest_notification_scanning,
290 mNotificationBuilder.build());
291 mRedeliverIndexFinish = true;
295 // Copied from old Gallery3d code
296 private static final class ScannerClient implements MediaScannerConnectionClient {
297 ArrayList<String> mPaths = new ArrayList<String>();
298 MediaScannerConnection mScannerConnection;
300 Object mLock = new Object();
302 public ScannerClient(Context context) {
303 mScannerConnection = new MediaScannerConnection(context, this);
306 public void scanPath(String path) {
307 synchronized (mLock) {
309 mScannerConnection.scanFile(path, null);
312 mScannerConnection.connect();
318 public void onMediaScannerConnected() {
319 synchronized (mLock) {
321 if (!mPaths.isEmpty()) {
322 for (String path : mPaths) {
323 mScannerConnection.scanFile(path, null);
331 public void onScanCompleted(String path, Uri uri) {