| /* |
| * Copyright (C) 2013 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.gallery3d.ingest; |
| |
| import com.android.gallery3d.R; |
| import com.android.gallery3d.ingest.data.ImportTask; |
| import com.android.gallery3d.ingest.data.IngestObjectInfo; |
| import com.android.gallery3d.ingest.data.MtpClient; |
| import com.android.gallery3d.ingest.data.MtpDeviceIndex; |
| |
| import android.annotation.TargetApi; |
| import android.app.NotificationManager; |
| import android.app.PendingIntent; |
| import android.app.Service; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.media.MediaScannerConnection; |
| import android.media.MediaScannerConnection.MediaScannerConnectionClient; |
| import android.mtp.MtpDevice; |
| import android.mtp.MtpDeviceInfo; |
| import android.net.Uri; |
| import android.os.Binder; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.os.SystemClock; |
| import androidx.core.app.NotificationCompat; |
| import android.util.SparseBooleanArray; |
| import android.widget.Adapter; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.List; |
| |
| /** |
| * Service for MTP importing tasks. |
| */ |
| @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1) |
| public class IngestService extends Service implements ImportTask.Listener, |
| MtpDeviceIndex.ProgressListener, MtpClient.Listener { |
| |
| /** |
| * Convenience class to allow easy access to the service instance. |
| */ |
| public class LocalBinder extends Binder { |
| IngestService getService() { |
| return IngestService.this; |
| } |
| } |
| |
| private static final int PROGRESS_UPDATE_INTERVAL_MS = 180; |
| |
| private MtpClient mClient; |
| private final IBinder mBinder = new LocalBinder(); |
| private ScannerClient mScannerClient; |
| private MtpDevice mDevice; |
| private String mDevicePrettyName; |
| private MtpDeviceIndex mIndex; |
| private IngestActivity mClientActivity; |
| private boolean mRedeliverImportFinish = false; |
| private int mRedeliverImportFinishCount = 0; |
| private Collection<IngestObjectInfo> mRedeliverObjectsNotImported; |
| private boolean mRedeliverNotifyIndexChanged = false; |
| private boolean mRedeliverIndexFinish = false; |
| private NotificationManager mNotificationManager; |
| private NotificationCompat.Builder mNotificationBuilder; |
| private long mLastProgressIndexTime = 0; |
| private boolean mNeedRelaunchNotification = false; |
| |
| @Override |
| public void onCreate() { |
| super.onCreate(); |
| mScannerClient = new ScannerClient(this); |
| mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); |
| mNotificationBuilder = new NotificationCompat.Builder(this); |
| // TODO(georgescu): Use a better drawable for the notificaton? |
| mNotificationBuilder.setSmallIcon(android.R.drawable.stat_notify_sync) |
| .setContentIntent(PendingIntent.getActivity(this, 0, |
| new Intent(this, IngestActivity.class), 0)); |
| mIndex = MtpDeviceIndex.getInstance(); |
| mIndex.setProgressListener(this); |
| |
| mClient = new MtpClient(getApplicationContext()); |
| List<MtpDevice> devices = mClient.getDeviceList(); |
| if (!devices.isEmpty()) { |
| setDevice(devices.get(0)); |
| } |
| mClient.addListener(this); |
| } |
| |
| @Override |
| public void onDestroy() { |
| mClient.close(); |
| mIndex.unsetProgressListener(this); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public IBinder onBind(Intent intent) { |
| return mBinder; |
| } |
| |
| private void setDevice(MtpDevice device) { |
| if (mDevice == device) { |
| return; |
| } |
| mRedeliverImportFinish = false; |
| mRedeliverObjectsNotImported = null; |
| mRedeliverNotifyIndexChanged = false; |
| mRedeliverIndexFinish = false; |
| mDevice = device; |
| mIndex.setDevice(mDevice); |
| if (mDevice != null) { |
| MtpDeviceInfo deviceInfo = mDevice.getDeviceInfo(); |
| if (deviceInfo == null) { |
| setDevice(null); |
| return; |
| } else { |
| mDevicePrettyName = deviceInfo.getModel(); |
| mNotificationBuilder.setContentTitle(mDevicePrettyName); |
| new Thread(mIndex.getIndexRunnable()).start(); |
| } |
| } else { |
| mDevicePrettyName = null; |
| } |
| if (mClientActivity != null) { |
| mClientActivity.notifyIndexChanged(); |
| } else { |
| mRedeliverNotifyIndexChanged = true; |
| } |
| } |
| |
| protected MtpDeviceIndex getIndex() { |
| return mIndex; |
| } |
| |
| protected void setClientActivity(IngestActivity activity) { |
| if (mClientActivity == activity) { |
| return; |
| } |
| mClientActivity = activity; |
| if (mClientActivity == null) { |
| if (mNeedRelaunchNotification) { |
| mNotificationBuilder.setProgress(0, 0, false) |
| .setContentText(getResources().getText(R.string.ingest_scanning_done)); |
| mNotificationManager.notify(R.id.ingest_notification_scanning, |
| mNotificationBuilder.build()); |
| } |
| return; |
| } |
| mNotificationManager.cancel(R.id.ingest_notification_importing); |
| mNotificationManager.cancel(R.id.ingest_notification_scanning); |
| if (mRedeliverImportFinish) { |
| mClientActivity.onImportFinish(mRedeliverObjectsNotImported, |
| mRedeliverImportFinishCount); |
| mRedeliverImportFinish = false; |
| mRedeliverObjectsNotImported = null; |
| } |
| if (mRedeliverNotifyIndexChanged) { |
| mClientActivity.notifyIndexChanged(); |
| mRedeliverNotifyIndexChanged = false; |
| } |
| if (mRedeliverIndexFinish) { |
| mClientActivity.onIndexingFinished(); |
| mRedeliverIndexFinish = false; |
| } |
| if (mDevice != null) { |
| mNeedRelaunchNotification = true; |
| } |
| } |
| |
| protected void importSelectedItems(SparseBooleanArray selected, Adapter adapter) { |
| List<IngestObjectInfo> importHandles = new ArrayList<IngestObjectInfo>(); |
| for (int i = 0; i < selected.size(); i++) { |
| if (selected.valueAt(i)) { |
| Object item = adapter.getItem(selected.keyAt(i)); |
| if (item instanceof IngestObjectInfo) { |
| importHandles.add(((IngestObjectInfo) item)); |
| } |
| } |
| } |
| ImportTask task = new ImportTask(mDevice, importHandles, mDevicePrettyName, this); |
| task.setListener(this); |
| mNotificationBuilder.setProgress(0, 0, true) |
| .setContentText(getResources().getText(R.string.ingest_importing)); |
| startForeground(R.id.ingest_notification_importing, |
| mNotificationBuilder.build()); |
| new Thread(task).start(); |
| } |
| |
| @Override |
| public void deviceAdded(MtpDevice device) { |
| if (mDevice == null) { |
| setDevice(device); |
| } |
| } |
| |
| @Override |
| public void deviceRemoved(MtpDevice device) { |
| if (device == mDevice) { |
| mNotificationManager.cancel(R.id.ingest_notification_scanning); |
| mNotificationManager.cancel(R.id.ingest_notification_importing); |
| setDevice(null); |
| mNeedRelaunchNotification = false; |
| |
| } |
| } |
| |
| @Override |
| public void onImportProgress(int visitedCount, int totalCount, |
| String pathIfSuccessful) { |
| if (pathIfSuccessful != null) { |
| mScannerClient.scanPath(pathIfSuccessful); |
| } |
| mNeedRelaunchNotification = false; |
| if (mClientActivity != null) { |
| mClientActivity.onImportProgress(visitedCount, totalCount, pathIfSuccessful); |
| } |
| mNotificationBuilder.setProgress(totalCount, visitedCount, false) |
| .setContentText(getResources().getText(R.string.ingest_importing)); |
| mNotificationManager.notify(R.id.ingest_notification_importing, |
| mNotificationBuilder.build()); |
| } |
| |
| @Override |
| public void onImportFinish(Collection<IngestObjectInfo> objectsNotImported, |
| int visitedCount) { |
| stopForeground(true); |
| mNeedRelaunchNotification = true; |
| if (mClientActivity != null) { |
| mClientActivity.onImportFinish(objectsNotImported, visitedCount); |
| } else { |
| mRedeliverImportFinish = true; |
| mRedeliverObjectsNotImported = objectsNotImported; |
| mRedeliverImportFinishCount = visitedCount; |
| mNotificationBuilder.setProgress(0, 0, false) |
| .setContentText(getResources().getText(R.string.ingest_import_complete)); |
| mNotificationManager.notify(R.id.ingest_notification_importing, |
| mNotificationBuilder.build()); |
| } |
| } |
| |
| @Override |
| public void onObjectIndexed(IngestObjectInfo object, int numVisited) { |
| mNeedRelaunchNotification = false; |
| if (mClientActivity != null) { |
| mClientActivity.onObjectIndexed(object, numVisited); |
| } else { |
| // Throttle the updates to one every PROGRESS_UPDATE_INTERVAL_MS milliseconds |
| long currentTime = SystemClock.uptimeMillis(); |
| if (currentTime > mLastProgressIndexTime + PROGRESS_UPDATE_INTERVAL_MS) { |
| mLastProgressIndexTime = currentTime; |
| mNotificationBuilder.setProgress(0, numVisited, true) |
| .setContentText(getResources().getText(R.string.ingest_scanning)); |
| mNotificationManager.notify(R.id.ingest_notification_scanning, |
| mNotificationBuilder.build()); |
| } |
| } |
| } |
| |
| @Override |
| public void onSortingStarted() { |
| if (mClientActivity != null) { |
| mClientActivity.onSortingStarted(); |
| } |
| } |
| |
| @Override |
| public void onIndexingFinished() { |
| mNeedRelaunchNotification = true; |
| if (mClientActivity != null) { |
| mClientActivity.onIndexingFinished(); |
| } else { |
| mNotificationBuilder.setProgress(0, 0, false) |
| .setContentText(getResources().getText(R.string.ingest_scanning_done)); |
| mNotificationManager.notify(R.id.ingest_notification_scanning, |
| mNotificationBuilder.build()); |
| mRedeliverIndexFinish = true; |
| } |
| } |
| |
| // Copied from old Gallery3d code |
| private static final class ScannerClient implements MediaScannerConnectionClient { |
| ArrayList<String> mPaths = new ArrayList<String>(); |
| MediaScannerConnection mScannerConnection; |
| boolean mConnected; |
| Object mLock = new Object(); |
| |
| public ScannerClient(Context context) { |
| mScannerConnection = new MediaScannerConnection(context, this); |
| } |
| |
| public void scanPath(String path) { |
| synchronized (mLock) { |
| if (mConnected) { |
| mScannerConnection.scanFile(path, null); |
| } else { |
| mPaths.add(path); |
| mScannerConnection.connect(); |
| } |
| } |
| } |
| |
| @Override |
| public void onMediaScannerConnected() { |
| synchronized (mLock) { |
| mConnected = true; |
| if (!mPaths.isEmpty()) { |
| for (String path : mPaths) { |
| mScannerConnection.scanFile(path, null); |
| } |
| mPaths.clear(); |
| } |
| } |
| } |
| |
| @Override |
| public void onScanCompleted(String path, Uri uri) { |
| } |
| } |
| } |