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 } 170 171 protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) { 172 List<MtpObjectInfo> importHandles = new ArrayList<MtpObjectInfo>(); 173 for (int i = 0; i < selected.size(); i++) { 174 if (selected.valueAt(i)) { 175 Object item = adapter.getItem(selected.keyAt(i)); 176 if (item instanceof MtpObjectInfo) { 177 importHandles.add(((MtpObjectInfo) item)); 178 } 179 } 180 } 181 ImportTask task = new ImportTask(mDevice, importHandles, BucketNames.IMPORTED, this); 182 task.setListener(this); 183 mNotificationBuilder.setProgress(0, 0, true) 184 .setContentText(getResources().getText(R.string.ingest_importing)); 185 startForeground(NotificationIds.INGEST_NOTIFICATION_IMPORTING, 186 mNotificationBuilder.build()); 187 new Thread(task).start(); 188 } 189 190 @Override 191 public void deviceAdded(MtpDevice device) { 192 if (mDevice == null) { 193 setDevice(device); 194 UsageStatistics.onEvent(UsageStatistics.COMPONENT_IMPORTER, 195 "DeviceConnected", null); 196 } 197 } 198 199 @Override 200 public void deviceRemoved(MtpDevice device) { 201 if (device == mDevice) { 202 setDevice(null); 203 mNeedRelaunchNotification = false; 204 mNotificationManager.cancel(NotificationIds.INGEST_NOTIFICATION_SCANNING); 205 } 206 } 207 208 @Override 209 public void onImportProgress(int visitedCount, int totalCount, 210 String pathIfSuccessful) { 211 if (pathIfSuccessful != null) { 212 mScannerClient.scanPath(pathIfSuccessful); 213 } 214 mNeedRelaunchNotification = false; 215 if (mClientActivity != null) { 216 mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful); 217 } 218 mNotificationBuilder.setProgress(totalCount, visitedCount, false) 219 .setContentText(getResources().getText(R.string.ingest_importing)); 220 mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING, 221 mNotificationBuilder.build()); 222 } 223 224 @Override 225 public void onImportFinish(Collection<MtpObjectInfo> objectsNotImported, 226 int visitedCount) { 227 stopForeground(true); 228 mNeedRelaunchNotification = true; 229 if (mClientActivity != null) { 230 mClientActivity.onImportFinish(objectsNotImported, visitedCount); 231 } else { 232 mRedeliverImportFinish = true; 233 mRedeliverObjectsNotImported = objectsNotImported; 234 mRedeliverImportFinishCount = visitedCount; 235 mNotificationBuilder.setProgress(0, 0, false) 236 .setContentText(getResources().getText(R.string.import_complete)); 237 mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_IMPORTING, 238 mNotificationBuilder.build()); 239 } 240 UsageStatistics.onEvent(UsageStatistics.COMPONENT_IMPORTER, 241 "ImportFinished", null, visitedCount); 242 } 243 244 @Override 245 public void onObjectIndexed(MtpObjectInfo object, int numVisited) { 246 mNeedRelaunchNotification = false; 247 if (mClientActivity != null) { 248 mClientActivity.onObjectIndexed(object, numVisited); 249 } else { 250 // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds 251 long currentTime = SystemClock.uptimeMillis(); 252 if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) { 253 mLastProgressIndexTime = currentTime; 254 mNotificationBuilder.setProgress(0, numVisited, true) 255 .setContentText(getResources().getText(R.string.ingest_scanning)); 256 mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING, 257 mNotificationBuilder.build()); 258 } 259 } 260 } 261 262 @Override 263 public void onSorting() { 264 if (mClientActivity != null) mClientActivity.onSorting(); 265 } 266 267 @Override 268 public void onIndexFinish() { 269 mNeedRelaunchNotification = true; 270 if (mClientActivity != null) { 271 mClientActivity.onIndexFinish(); 272 } else { 273 mNotificationBuilder.setProgress(0, 0, false) 274 .setContentText(getResources().getText(R.string.ingest_scanning_done)); 275 mNotificationManager.notify(NotificationIds.INGEST_NOTIFICATION_SCANNING, 276 mNotificationBuilder.build()); 277 mRedeliverIndexFinish = true; 278 } 279 } 280 281 // Copied from old Gallery3d code 282 private static final class ScannerClient implements MediaScannerConnectionClient { 283 ArrayList<String> mPaths = new ArrayList<String>(); 284 MediaScannerConnection mScannerConnection; 285 boolean mConnected; 286 Object mLock = new Object(); 287 288 public ScannerClient(Context context) { 289 mScannerConnection = new MediaScannerConnection(context, this); 290 } 291 292 public void scanPath(String path) { 293 synchronized (mLock) { 294 if (mConnected) { 295 mScannerConnection.scanFile(path, null); 296 } else { 297 mPaths.add(path); 298 mScannerConnection.connect(); 299 } 300 } 301 } 302 303 @Override 304 public void onMediaScannerConnected() { 305 synchronized (mLock) { 306 mConnected = true; 307 if (!mPaths.isEmpty()) { 308 for (String path : mPaths) { 309 mScannerConnection.scanFile(path, null); 310 } 311 mPaths.clear(); 312 } 313 } 314 } 315 316 @Override 317 public void onScanCompleted(String path, Uri uri) { 318 } 319 } 320} 321