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