Home | History | Annotate | Download | only in mtp
      1 /*
      2  * Copyright (C) 2015 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.mtp;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.app.Notification;
     22 import android.app.Service;
     23 import android.app.NotificationManager;
     24 import android.content.Intent;
     25 import android.os.IBinder;
     26 import android.os.Parcelable;
     27 import android.service.notification.StatusBarNotification;
     28 import android.util.Log;
     29 import com.android.internal.util.Preconditions;
     30 import java.util.HashSet;
     31 import java.util.Set;
     32 
     33 /**
     34  * Service to manage lifetime of DocumentsProvider's process.
     35  * The service prevents the system from killing the process that holds USB connections. The service
     36  * starts to run when the first MTP device is opened, and stops when the last MTP device is closed.
     37  */
     38 public class MtpDocumentsService extends Service {
     39     static final String ACTION_UPDATE_NOTIFICATION = "com.android.mtp.UPDATE_NOTIFICATION";
     40     static final String EXTRA_DEVICE_IDS = "deviceIds";
     41     static final String EXTRA_DEVICE_NOTIFICATIONS = "deviceNotifications";
     42 
     43     private NotificationManager mNotificationManager;
     44 
     45     @Override
     46     public IBinder onBind(Intent intent) {
     47         // The service is used via intents.
     48         return null;
     49     }
     50 
     51     @Override
     52     public void onCreate() {
     53         super.onCreate();
     54         mNotificationManager = getSystemService(NotificationManager.class);
     55     }
     56 
     57     @Override
     58     public int onStartCommand(Intent intent, int flags, int startId) {
     59         // If intent is null, the service was restarted.
     60         if (intent == null || ACTION_UPDATE_NOTIFICATION.equals(intent.getAction())) {
     61             final int[] ids = intent.hasExtra(EXTRA_DEVICE_IDS) ?
     62                     intent.getExtras().getIntArray(EXTRA_DEVICE_IDS) : null;
     63             final Notification[] notifications = intent.hasExtra(EXTRA_DEVICE_NOTIFICATIONS) ?
     64                     castToNotifications(intent.getExtras().getParcelableArray(
     65                             EXTRA_DEVICE_NOTIFICATIONS)) : null;
     66             return updateForegroundState(ids, notifications) ? START_STICKY : START_NOT_STICKY;
     67         }
     68         return START_NOT_STICKY;
     69     }
     70 
     71     /**
     72      * Updates the foreground state of the service.
     73      * @return Whether the service is foreground or not.
     74      */
     75     private boolean updateForegroundState(
     76             @Nullable int[] ids, @Nullable Notification[] notifications) {
     77         final Set<Integer> openedNotification = new HashSet<>();
     78         final int size = ids != null ? ids.length : 0;
     79         if (size != 0) {
     80             Preconditions.checkArgument(ids != null);
     81             Preconditions.checkArgument(notifications != null);
     82             Preconditions.checkArgument(ids.length == notifications.length);
     83         }
     84 
     85         for (int i = 0; i < size; i++) {
     86             if (i == 0) {
     87                 // Mark this service as foreground with the notification so that the process is
     88                 // not killed by the system while a MTP device is opened.
     89                 startForeground(ids[i], notifications[i]);
     90             } else {
     91                 // Only one notification can be shown as a foreground notification. We need to
     92                 // show the rest as normal notification.
     93                 mNotificationManager.notify(ids[i], notifications[i]);
     94             }
     95             openedNotification.add(ids[i]);
     96         }
     97 
     98         final StatusBarNotification[] activeNotifications =
     99                 mNotificationManager.getActiveNotifications();
    100         for (final StatusBarNotification notification : activeNotifications) {
    101             if (!openedNotification.contains(notification.getId())) {
    102                 mNotificationManager.cancel(notification.getId());
    103             }
    104         }
    105 
    106         if (size == 0) {
    107             // There is no opened device.
    108             stopForeground(true /* removeNotification */);
    109             stopSelf();
    110             return false;
    111         }
    112 
    113         return true;
    114     }
    115 
    116     private static @NonNull Notification[] castToNotifications(@NonNull Parcelable[] src) {
    117         Preconditions.checkNotNull(src);
    118         final Notification[] notifications = new Notification[src.length];
    119         for (int i = 0; i < src.length; i++) {
    120             notifications[i] = (Notification) src[i];
    121         }
    122         return notifications;
    123     }
    124 }
    125