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 17 package com.android.gallery3d.ingest; 18 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; 24 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; 43 44 import java.util.ArrayList; 45 import java.util.Collection; 46 import java.util.List; 47 48 /** 49 * Service for MTP importing tasks. 50 */ 51 @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) 52 public class IngestService extends Service implements ImportTask.Listener, 53 MtpDeviceIndex.ProgressListener, MtpClient.Listener { 54 55 /** 56 * Convenience class to allow easy access to the service instance. 57 */ 58 public class LocalBinder extends Binder { getService()59 IngestService getService() { 60 return IngestService.this; 61 } 62 } 63 64 private static final int PROGRESS_UPDATE_INTERVAL_MS = 180; 65 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; 82 83 @Override onCreate()84 public void onCreate() { 85 super.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); 95 96 mClient = new MtpClient(getApplicationContext()); 97 List<MtpDevice> devices = mClient.getDeviceList(); 98 if (!devices.isEmpty()) { 99 setDevice(devices.get(0)); 100 } 101 mClient.addListener(this); 102 } 103 104 @Override onDestroy()105 public void onDestroy() { 106 mClient.close(); 107 mIndex.unsetProgressListener(this); 108 super.onDestroy(); 109 } 110 111 @Override onBind(Intent intent)112 public IBinder onBind(Intent intent) { 113 return mBinder; 114 } 115 setDevice(MtpDevice device)116 private void setDevice(MtpDevice device) { 117 if (mDevice == device) { 118 return; 119 } 120 mRedeliverImportFinish = false; 121 mRedeliverObjectsNotImported = null; 122 mRedeliverNotifyIndexChanged = false; 123 mRedeliverIndexFinish = false; 124 mDevice = device; 125 mIndex.setDevice(mDevice); 126 if (mDevice != null) { 127 MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo(); 128 if (deviceInfo == null) { 129 setDevice(null); 130 return; 131 } else { 132 mDevicePrettyName = deviceInfo.getModel(); 133 mNotificationBuilder.setContentTitle(mDevicePrettyName); 134 new Thread(mIndex.getIndexRunnable()).start(); 135 } 136 } else { 137 mDevicePrettyName = null; 138 } 139 if (mClientActivity != null) { 140 mClientActivity.notifyIndexChanged(); 141 } else { 142 mRedeliverNotifyIndexChanged = true; 143 } 144 } 145 getIndex()146 protected MtpDeviceIndex getIndex() { 147 return mIndex; 148 } 149 setClientActivity(IngestActivity activity)150 protected void setClientActivity(IngestActivity activity) { 151 if (mClientActivity == activity) { 152 return; 153 } 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()); 161 } 162 return; 163 } 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; 171 } 172 if (mRedeliverNotifyIndexChanged) { 173 mClientActivity.notifyIndexChanged(); 174 mRedeliverNotifyIndexChanged = false; 175 } 176 if (mRedeliverIndexFinish) { 177 mClientActivity.onIndexingFinished(); 178 mRedeliverIndexFinish = false; 179 } 180 if (mDevice != null) { 181 mNeedRelaunchNotification = true; 182 } 183 } 184 importSelectedItems(SparseBooleanArray selected, Adapter adapter)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)); 192 } 193 } 194 } 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(); 202 } 203 204 @Override deviceAdded(MtpDevice device)205 public void deviceAdded(MtpDevice device) { 206 if (mDevice == null) { 207 setDevice(device); 208 } 209 } 210 211 @Override deviceRemoved(MtpDevice device)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); 216 setDevice(null); 217 mNeedRelaunchNotification = false; 218 219 } 220 } 221 222 @Override onImportProgress(int visitedCount, int totalCount, String pathIfSuccessful)223 public void onImportProgress(int visitedCount, int totalCount, 224 String pathIfSuccessful) { 225 if (pathIfSuccessful != null) { 226 mScannerClient.scanPath(pathIfSuccessful); 227 } 228 mNeedRelaunchNotification = false; 229 if (mClientActivity != null) { 230 mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful); 231 } 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()); 236 } 237 238 @Override onImportFinish(Collection<IngestObjectInfo> objectsNotImported, int visitedCount)239 public void onImportFinish(Collection<IngestObjectInfo> objectsNotImported, 240 int visitedCount) { 241 stopForeground(true); 242 mNeedRelaunchNotification = true; 243 if (mClientActivity != null) { 244 mClientActivity.onImportFinish(objectsNotImported, visitedCount); 245 } else { 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()); 253 } 254 } 255 256 @Override onObjectIndexed(IngestObjectInfo object, int numVisited)257 public void onObjectIndexed(IngestObjectInfo object, int numVisited) { 258 mNeedRelaunchNotification = false; 259 if (mClientActivity != null) { 260 mClientActivity.onObjectIndexed(object, numVisited); 261 } else { 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()); 270 } 271 } 272 } 273 274 @Override onSortingStarted()275 public void onSortingStarted() { 276 if (mClientActivity != null) { 277 mClientActivity.onSortingStarted(); 278 } 279 } 280 281 @Override onIndexingFinished()282 public void onIndexingFinished() { 283 mNeedRelaunchNotification = true; 284 if (mClientActivity != null) { 285 mClientActivity.onIndexingFinished(); 286 } else { 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; 292 } 293 } 294 295 // Copied from old Gallery3d code 296 private static final class ScannerClient implements MediaScannerConnectionClient { 297 ArrayList<String> mPaths = new ArrayList<String>(); 298 MediaScannerConnection mScannerConnection; 299 boolean mConnected; 300 Object mLock = new Object(); 301 ScannerClient(Context context)302 public ScannerClient(Context context) { 303 mScannerConnection = new MediaScannerConnection(context, this); 304 } 305 scanPath(String path)306 public void scanPath(String path) { 307 synchronized (mLock) { 308 if (mConnected) { 309 mScannerConnection.scanFile(path, null); 310 } else { 311 mPaths.add(path); 312 mScannerConnection.connect(); 313 } 314 } 315 } 316 317 @Override onMediaScannerConnected()318 public void onMediaScannerConnected() { 319 synchronized (mLock) { 320 mConnected = true; 321 if (!mPaths.isEmpty()) { 322 for (String path : mPaths) { 323 mScannerConnection.scanFile(path, null); 324 } 325 mPaths.clear(); 326 } 327 } 328 } 329 330 @Override onScanCompleted(String path, Uri uri)331 public void onScanCompleted(String path, Uri uri) { 332 } 333 } 334 } 335