Home | History | Annotate | Download | only in ingest
      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 {
     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
     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
    105   public void onDestroy() {
    106     mClient.close();
    107     mIndex.unsetProgressListener(this);
    108     super.onDestroy();
    109   }
    110 
    111   @Override
    112   public IBinder onBind(Intent intent) {
    113     return mBinder;
    114   }
    115 
    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 
    146   protected MtpDeviceIndex getIndex() {
    147     return mIndex;
    148   }
    149 
    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 
    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
    205   public void deviceAdded(MtpDevice device) {
    206     if (mDevice == null) {
    207       setDevice(device);
    208     }
    209   }
    210 
    211   @Override
    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
    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
    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
    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
    275   public void onSortingStarted() {
    276     if (mClientActivity != null) {
    277       mClientActivity.onSortingStarted();
    278     }
    279   }
    280 
    281   @Override
    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 
    302     public ScannerClient(Context context) {
    303       mScannerConnection = new MediaScannerConnection(context, this);
    304     }
    305 
    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
    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
    331     public void onScanCompleted(String path, Uri uri) {
    332     }
    333   }
    334 }
    335