IngestService.java revision 568a8cf9ec253a635a36866efc0e3acebbba2a2e
1/*
2 * Copyright (C) 2013 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
17package com.android.gallery3d.ingest;
18
19import android.app.NotificationManager;
20import android.app.PendingIntent;
21import android.app.Service;
22import android.content.Context;
23import android.content.Intent;
24import android.media.MediaScannerConnection;
25import android.media.MediaScannerConnection.MediaScannerConnectionClient;
26import android.mtp.MtpDevice;
27import android.mtp.MtpDeviceInfo;
28import android.mtp.MtpObjectInfo;
29import android.net.Uri;
30import android.os.Binder;
31import android.os.IBinder;
32import android.os.SystemClock;
33import android.support.v4.app.NotificationCompat;
34import android.util.SparseBooleanArray;
35import android.widget.Adapter;
36
37import com.android.gallery3d.R;
38import com.android.gallery3d.app.NotificationIds;
39import com.android.gallery3d.data.MtpClient;
40import com.android.gallery3d.util.BucketNames;
41
42import java.util.ArrayList;
43import java.util.Collection;
44import java.util.List;
45
46public class IngestService extends Service implements ImportTask.Listener,
47        MtpDeviceIndex.ProgressListener, MtpClient.Listener {
48
49    public class LocalBinder extends Binder {
50        IngestService getService() {
51            return IngestService.this;
52        }
53    }
54
55    private static final int PROGRESS_UPDATE_INTERVAL_MS = 180;
56
57    private static MtpClient sClient;
58
59    private final IBinder mBinder = new LocalBinder();
60    private ScannerClient mScannerClient;
61    private MtpDevice mDevice;
62    private String mDevicePrettyName;
63    private MtpDeviceIndex mIndex;
64    private IngestActivity mClientActivity;
65    private boolean mRedeliverImportFinish = false;
66    private Collection<MtpObjectInfo> mRedeliverObjectsNotImported;
67    private boolean mRedeliverNotifyIndexChanged = false;
68    private boolean mRedeliverIndexFinish = false;
69    private NotificationManager mNotificationManager;
70    private NotificationCompat.Builder mNotificationBuilder;
71    private long mLastProgressIndexTime = 0;
72    private boolean mNeedRelaunchNotification = false;
73
74    @Override
75    public void onCreate() {
76        super.onCreate();
77        mScannerClient = new ScannerClient(this);
78        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
79        mNotificationBuilder = new NotificationCompat.Builder(this);
80        mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_sync) // TODO drawable
81                .setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, IngestActivity.class), 0));
82        mIndex = MtpDeviceIndex.getInstance();
83        mIndex.setProgressListener(this);
84
85        if (sClient == null) {
86            sClient = new MtpClient(getApplicationContext());
87        }
88        List<MtpDevice> devices = sClient.getDeviceList();
89        if (devices.size() > 0) {
90            setDevice(devices.get(0));
91        }
92        sClient.addListener(this);
93    }
94
95    @Override
96    public void onDestroy() {
97        sClient.removeListener(this);
98        mIndex.unsetProgressListener(this);
99        super.onDestroy();
100    }
101
102    @Override
103    public IBinder onBind(Intent intent) {
104        return mBinder;
105    }
106
107    private void setDevice(MtpDevice device) {
108        if (mDevice == device) return;
109        mRedeliverImportFinish = false;
110        mRedeliverObjectsNotImported = null;
111        mRedeliverNotifyIndexChanged = false;
112        mRedeliverIndexFinish = false;
113        mDevice = device;
114        mIndex.setDevice(mDevice);
115        if (mDevice != null) {
116            MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo();
117            if (deviceInfo == null) {
118                setDevice(null);
119                return;
120            } else {
121                mDevicePrettyName = deviceInfo.getModel();
122                mNotificationBuilder.setContentTitle(mDevicePrettyName);
123                new Thread(mIndex.getIndexRunnable()).start();
124            }
125        } else {
126            mDevicePrettyName = null;
127        }
128        if (mClientActivity != null) {
129            mClientActivity.notifyIndexChanged();
130        } else {
131            mRedeliverNotifyIndexChanged = true;
132        }
133    }
134
135    protected MtpDeviceIndex getIndex() {
136        return mIndex;
137    }
138
139    protected void setClientActivity(IngestActivity activity) {
140        if (mClientActivity == activity) return;
141        mClientActivity = activity;
142        if (mClientActivity == null) {
143            if (mNeedRelaunchNotification) {
144                mNotificationBuilder.setProgress(0, 0, false)
145                    .setContentText(getResources().getText(R.string.ingest_scanning_done));
146                mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
147                    mNotificationBuilder.build());
148            }
149            return;
150        }
151        mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_IMPORTING);
152        mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING);
153        if (mRedeliverImportFinish) {
154            mClientActivity.onImportFinish(mRedeliverObjectsNotImported);
155            mRedeliverImportFinish = false;
156            mRedeliverObjectsNotImported = null;
157        }
158        if (mRedeliverNotifyIndexChanged) {
159            mClientActivity.notifyIndexChanged();
160            mRedeliverNotifyIndexChanged = false;
161        }
162        if (mRedeliverIndexFinish) {
163            mClientActivity.onIndexFinish();
164            mRedeliverIndexFinish = false;
165        }
166    }
167
168    protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) {
169        List<MtpObjectInfo> importHandles = new ArrayList<MtpObjectInfo>();
170        for (int i = 0; i < selected.size(); i++) {
171            if (selected.valueAt(i)) {
172                Object item = adapter.getItem(selected.keyAt(i));
173                if (item instanceof MtpObjectInfo) {
174                    importHandles.add(((MtpObjectInfo) item));
175                }
176            }
177        }
178        ImportTask task = new ImportTask(mDevice, importHandles, BucketNames.IMPORTED, this);
179        task.setListener(this);
180        mNotificationBuilder.setProgress(0, 0, true)
181            .setContentText(getResources().getText(R.string.ingest_importing));
182        startForeground(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
183                    mNotificationBuilder.build());
184        new Thread(task).start();
185    }
186
187    @Override
188    public void deviceAdded(MtpDevice device) {
189        if (mDevice == null) {
190            setDevice(device);
191        }
192    }
193
194    @Override
195    public void deviceRemoved(MtpDevice device) {
196        if (device == mDevice) {
197            setDevice(null);
198            mNeedRelaunchNotification = false;
199            mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING);
200        }
201    }
202
203    @Override
204    public void onImportProgress(int visitedCount, int totalCount,
205            String pathIfSuccessful) {
206        if (pathIfSuccessful != null) {
207            mScannerClient.scanPath(pathIfSuccessful);
208        }
209        mNeedRelaunchNotification = false;
210        if (mClientActivity != null) {
211            mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful);
212        }
213        mNotificationBuilder.setProgress(totalCount, visitedCount, false)
214            .setContentText(getResources().getText(R.string.ingest_importing));
215        mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
216                mNotificationBuilder.build());
217    }
218
219    @Override
220    public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported) {
221        stopForeground(true);
222        mNeedRelaunchNotification = true;
223        if (mClientActivity != null) {
224            mClientActivity.onImportFinish(objectsNotImported);
225        } else {
226            mRedeliverImportFinish = true;
227            mRedeliverObjectsNotImported = objectsNotImported;
228            mNotificationBuilder.setProgress(0, 0, false)
229                .setContentText(getResources().getText(R.string.import_complete));
230            mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING,
231                    mNotificationBuilder.build());
232        }
233    }
234
235    @Override
236    public void onObjectIndexed(MtpObjectInfo object, int numVisited) {
237        mNeedRelaunchNotification = false;
238        if (mClientActivity != null) {
239            mClientActivity.onObjectIndexed(object, numVisited);
240        } else {
241            // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds
242            long currentTime = SystemClock.uptimeMillis();
243            if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) {
244                mLastProgressIndexTime = currentTime;
245                mNotificationBuilder.setProgress(0, numVisited, true)
246                        .setContentText(getResources().getText(R.string.ingest_scanning));
247                mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
248                        mNotificationBuilder.build());
249            }
250        }
251    }
252
253    @Override
254    public void onSorting() {
255        if (mClientActivity != null) mClientActivity.onSorting();
256    }
257
258    @Override
259    public void onIndexFinish() {
260        mNeedRelaunchNotification = true;
261        if (mClientActivity != null) {
262            mClientActivity.onIndexFinish();
263        } else {
264            mNotificationBuilder.setProgress(0, 0, false)
265                .setContentText(getResources().getText(R.string.ingest_scanning_done));
266            mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING,
267                    mNotificationBuilder.build());
268            mRedeliverIndexFinish = true;
269        }
270    }
271
272    // Copied from old Gallery3d code
273    private static final class ScannerClient implements MediaScannerConnectionClient {
274        ArrayList<String> mPaths = new ArrayList<String>();
275        MediaScannerConnection mScannerConnection;
276        boolean mConnected;
277        Object mLock = new Object();
278
279        public ScannerClient(Context context) {
280            mScannerConnection = new MediaScannerConnection(context, this);
281        }
282
283        public void scanPath(String path) {
284            synchronized (mLock) {
285                if (mConnected) {
286                    mScannerConnection.scanFile(path, null);
287                } else {
288                    mPaths.add(path);
289                    mScannerConnection.connect();
290                }
291            }
292        }
293
294        @Override
295        public void onMediaScannerConnected() {
296            synchronized (mLock) {
297                mConnected = true;
298                if (!mPaths.isEmpty()) {
299                    for (String path : mPaths) {
300                        mScannerConnection.scanFile(path, null);
301                    }
302                    mPaths.clear();
303                }
304            }
305        }
306
307        @Override
308        public void onScanCompleted(String path, Uri uri) {
309        }
310    }
311}
312