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 android.media.RingtoneManager.TYPE_ALARM;
20 import static android.media.RingtoneManager.TYPE_NOTIFICATION;
21 import static android.media.RingtoneManager.TYPE_RINGTONE;
22 
23 import static com.android.providers.media.MediaProvider.TAG;
24 
25 import android.app.IntentService;
26 import android.content.ContentProviderClient;
27 import android.content.ContentResolver;
28 import android.content.ContentUris;
29 import android.content.ContentValues;
30 import android.content.Context;
31 import android.content.Intent;
32 import android.database.Cursor;
33 import android.media.RingtoneManager;
34 import android.net.Uri;
35 import android.os.Environment;
36 import android.os.PowerManager;
37 import android.os.SystemProperties;
38 import android.os.Trace;
39 import android.provider.MediaStore;
40 import android.provider.MediaStore.MediaColumns;
41 import android.provider.Settings;
42 import android.util.Log;
43 
44 import com.android.providers.media.scan.MediaScanner;
45 
46 import java.io.File;
47 import java.io.FileNotFoundException;
48 import java.io.IOException;
49 import java.util.Collection;
50 
51 public class MediaService extends IntentService {
MediaService()52     public MediaService() {
53         super(TAG);
54     }
55 
56     private PowerManager.WakeLock mWakeLock;
57 
58     @Override
onCreate()59     public void onCreate() {
60         super.onCreate();
61 
62         mWakeLock = getSystemService(PowerManager.class).newWakeLock(
63                 PowerManager.PARTIAL_WAKE_LOCK, TAG);
64     }
65 
66     @Override
onHandleIntent(Intent intent)67     protected void onHandleIntent(Intent intent) {
68         mWakeLock.acquire();
69         Trace.traceBegin(Trace.TRACE_TAG_DATABASE, intent.getAction());
70         if (Log.isLoggable(TAG, Log.INFO)) {
71             Log.i(TAG, "Begin " + intent);
72         }
73         try {
74             switch (intent.getAction()) {
75                 case Intent.ACTION_LOCALE_CHANGED: {
76                     onLocaleChanged();
77                     break;
78                 }
79                 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
80                 case Intent.ACTION_PACKAGE_DATA_CLEARED: {
81                     final String packageName = intent.getData().getSchemeSpecificPart();
82                     onPackageOrphaned(packageName);
83                     break;
84                 }
85                 case Intent.ACTION_MEDIA_MOUNTED: {
86                     onScanVolume(this, intent.getData());
87                     break;
88                 }
89                 case Intent.ACTION_MEDIA_SCANNER_SCAN_FILE: {
90                     onScanFile(this, intent.getData());
91                     break;
92                 }
93                 default: {
94                     Log.w(TAG, "Unknown intent " + intent);
95                     break;
96                 }
97             }
98         } catch (Exception e) {
99             Log.w(TAG, "Failed operation " + intent, e);
100         } finally {
101             if (Log.isLoggable(TAG, Log.INFO)) {
102                 Log.i(TAG, "End " + intent);
103             }
104             Trace.traceEnd(Trace.TRACE_TAG_DATABASE);
105             mWakeLock.release();
106         }
107     }
108 
onLocaleChanged()109     private void onLocaleChanged() {
110         try (ContentProviderClient cpc = getContentResolver()
111                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
112             ((MediaProvider) cpc.getLocalContentProvider()).onLocaleChanged();
113         }
114     }
115 
onPackageOrphaned(String packageName)116     private void onPackageOrphaned(String packageName) {
117         try (ContentProviderClient cpc = getContentResolver()
118                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
119             ((MediaProvider) cpc.getLocalContentProvider()).onPackageOrphaned(packageName);
120         }
121     }
122 
onScanVolume(Context context, Uri uri)123     public static void onScanVolume(Context context, Uri uri) throws IOException {
124         final File file = new File(uri.getPath()).getCanonicalFile();
125         final String volumeName = MediaStore.getVolumeName(file);
126 
127         // If we're about to scan primary external storage, scan internal first
128         // to ensure that we have ringtones ready to roll before a possibly very
129         // long external storage scan
130         if (MediaStore.VOLUME_EXTERNAL_PRIMARY.equals(volumeName)) {
131             onScanVolume(context, Uri.fromFile(Environment.getRootDirectory()));
132             ensureDefaultRingtones(context);
133         }
134 
135         try (ContentProviderClient cpc = context.getContentResolver()
136                 .acquireContentProviderClient(MediaStore.AUTHORITY)) {
137             ((MediaProvider) cpc.getLocalContentProvider()).attachVolume(volumeName);
138 
139             final ContentResolver resolver = ContentResolver.wrap(cpc.getLocalContentProvider());
140 
141             ContentValues values = new ContentValues();
142             values.put(MediaStore.MEDIA_SCANNER_VOLUME, volumeName);
143             Uri scanUri = resolver.insert(MediaStore.getMediaScannerUri(), values);
144 
145             if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
146                 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_STARTED, uri));
147             }
148 
149             for (File dir : resolveDirectories(volumeName)) {
150                 MediaScanner.instance(context).scanDirectory(dir);
151             }
152 
153             resolver.delete(scanUri, null, null);
154 
155         } finally {
156             if (!MediaStore.VOLUME_INTERNAL.equals(volumeName)) {
157                 context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_FINISHED, uri));
158             }
159         }
160     }
161 
onScanFile(Context context, Uri uri)162     public static Uri onScanFile(Context context, Uri uri) throws IOException {
163         final File file = new File(uri.getPath()).getCanonicalFile();
164         return MediaScanner.instance(context).scanFile(file);
165     }
166 
resolveDirectories(String volumeName)167     private static Collection<File> resolveDirectories(String volumeName)
168             throws FileNotFoundException {
169         return MediaStore.getVolumeScanPaths(volumeName);
170     }
171 
172     /**
173      * Ensure that we've set ringtones at least once after initial scan.
174      */
ensureDefaultRingtones(Context context)175     private static void ensureDefaultRingtones(Context context) {
176         for (int type : new int[] {
177                 TYPE_RINGTONE,
178                 TYPE_NOTIFICATION,
179                 TYPE_ALARM,
180         }) {
181             // Skip if we've already defined it at least once, so we don't
182             // overwrite the user changing to null
183             final String setting = getDefaultRingtoneSetting(type);
184             if (Settings.System.getInt(context.getContentResolver(), setting, 0) != 0) {
185                 continue;
186             }
187 
188             // Try finding the scanned ringtone
189             final String filename = getDefaultRingtoneFilename(type);
190             final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI;
191             try (Cursor cursor = context.getContentResolver().query(baseUri,
192                     new String[] { MediaColumns._ID },
193                     MediaColumns.DISPLAY_NAME + "=?",
194                     new String[] { filename }, null)) {
195                 if (cursor.moveToFirst()) {
196                     final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse(
197                             ContentUris.withAppendedId(baseUri, cursor.getLong(0)));
198                     RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri);
199                     Settings.System.putInt(context.getContentResolver(), setting, 1);
200                 }
201             }
202         }
203     }
204 
getDefaultRingtoneSetting(int type)205     private static String getDefaultRingtoneSetting(int type) {
206         switch (type) {
207             case TYPE_RINGTONE: return "ringtone_set";
208             case TYPE_NOTIFICATION: return "notification_sound_set";
209             case TYPE_ALARM: return "alarm_alert_set";
210             default: throw new IllegalArgumentException();
211         }
212     }
213 
getDefaultRingtoneFilename(int type)214     private static String getDefaultRingtoneFilename(int type) {
215         switch (type) {
216             case TYPE_RINGTONE: return SystemProperties.get("ro.config.ringtone");
217             case TYPE_NOTIFICATION: return SystemProperties.get("ro.config.notification_sound");
218             case TYPE_ALARM: return SystemProperties.get("ro.config.alarm_alert");
219             default: throw new IllegalArgumentException();
220         }
221     }
222 }
223