1 /*
2  * Copyright (C) 2019 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.providers.media;
18 
19 import static com.android.providers.media.scan.MediaScanner.REASON_DEMAND;
20 import static com.android.providers.media.scan.MediaScanner.REASON_MOUNTED;
21 import static com.android.providers.media.util.Logging.TAG;
22 
23 import android.content.ContentProviderClient;
24 import android.content.ContentResolver;
25 import android.content.ContentValues;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.media.RingtoneManager;
29 import android.net.Uri;
30 import android.os.Trace;
31 import android.provider.MediaStore;
32 import android.util.Log;
33 
34 import androidx.core.app.JobIntentService;
35 
36 import com.android.providers.media.util.FileUtils;
37 
38 import java.io.File;
39 import java.io.IOException;
40 
41 public class MediaService extends JobIntentService {
42     private static final int JOB_ID = -300;
43 
enqueueWork(Context context, Intent work)44     public static void enqueueWork(Context context, Intent work) {
45         enqueueWork(context, MediaService.class, JOB_ID, work);
46     }
47 
48     @Override
onHandleWork(Intent intent)49     protected void onHandleWork(Intent intent) {
50         Trace.beginSection(intent.getAction());
51         if (Log.isLoggable(TAG, Log.INFO)) {
52             Log.i(TAG, "Begin " + intent);
53         }
54         try {
55             switch (intent.getAction()) {
56                 case Intent.ACTION_LOCALE_CHANGED: {
57                     onLocaleChanged();
58                     break;
59                 }
60                 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
61                 case Intent.ACTION_PACKAGE_DATA_CLEARED: {
62                     final String packageName = intent.getData().getSchemeSpecificPart();
63                     onPackageOrphaned(packageName);
64                     break;
65                 }
66                 case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: {
67                     onScanFile(this, intent.getData());
68                     break;
69                 }
70                 case Intent.ACTION_MEDIA_MOUNTED: {
71                     onScanVolume(this, intent.getData(), REASON_MOUNTED);
72                     break;
73                 }
74                 default: {
75                     Log.w(TAG, "Unknown intent " + intent);
76                     break;
77                 }
78             }
79         } catch (Exception e) {
80             Log.w(TAG, "Failed operation " + intent, e);
81         } finally {
82             if (Log.isLoggable(TAG, Log.INFO)) {
83                 Log.i(TAG, "End " + intent);
84             }
85             Trace.endSection();
86         }
87     }
88 
onLocaleChanged()89     private void onLocaleChanged() {
90         try (ContentProviderClient cpc = getContentResolver()
91                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
92             ((MediaProvider) cpc.getLocalContentProvider()).onLocaleChanged();
93         }
94     }
95 
onPackageOrphaned(String packageName)96     private void onPackageOrphaned(String packageName) {
97         try (ContentProviderClient cpc = getContentResolver()
98                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
99             ((MediaProvider) cpc.getLocalContentProvider()).onPackageOrphaned(packageName);
100         }
101     }
102 
onScanVolume(Context context, Uri uri, int reason)103     private static void onScanVolume(Context context, Uri uri, int reason)
104             throws IOException {
105         final File file = new File(uri.getPath()).getCanonicalFile();
106         final String volumeName = FileUtils.getVolumeName(context, file);
107 
108         onScanVolume(context, volumeName, reason);
109     }
110 
onScanVolume(Context context, String volumeName, int reason)111     public static void onScanVolume(Context context, String volumeName, int reason)
112             throws IOException {
113         // If we're about to scan any external storage, scan internal first
114         // to ensure that we have ringtones ready to roll before a possibly very
115         // long external storage scan
116         if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
117             onScanVolume(context, MediaStore.VOLUME_INTERNAL, reason);
118             RingtoneManager.ensureDefaultRingtones(context);
119         }
120 
121         // Resolve the Uri that we should use for all broadcast intents related
122         // to this volume; we do this once to ensure we can deliver all events
123         // in the situation where a volume is ejected mid-scan
124         final Uri broadcastUri;
125         if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
126             broadcastUri = Uri.fromFile(FileUtils.getVolumePath(context, volumeName));
127         } else {
128             broadcastUri = null;
129         }
130 
131         try (ContentProviderClient cpc = context.getContentResolver()
132                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
133             final MediaProvider provider = ((MediaProvider) cpc.getLocalContentProvider());
134             provider.attachVolume(volumeName, /* validate */ true);
135 
136             final ContentResolver resolver = ContentResolver.wrap(cpc.getLocalContentProvider());
137 
138             ContentValues values = new ContentValues();
139             values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
140             Uri scanUri = resolver.insert(MediaStore.getMediaScannerUri(), values);
141 
142             if (broadcastUri != null) {
143                 context.sendBroadcast(
144                         new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, broadcastUri));
145             }
146 
147             for (File dir : FileUtils.getVolumeScanPaths(context, volumeName)) {
148                 provider.scanDirectory(dir, reason);
149             }
150 
151             resolver.delete(scanUri, null, null);
152 
153         } finally {
154             if (broadcastUri != null) {
155                 context.sendBroadcast(
156                         new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, broadcastUri));
157             }
158         }
159     }
160 
onScanFile(Context context, Uri uri)161     private static Uri onScanFile(Context context, Uri uri) throws IOException {
162         final File file = new File(uri.getPath()).getCanonicalFile();
163         try (ContentProviderClient cpc = context.getContentResolver()
164                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
165             final MediaProvider provider = ((MediaProvider) cpc.getLocalContentProvider());
166             return provider.scanFile(file, REASON_DEMAND);
167         }
168     }
169 }
170