1 /* 2 * Copyright (C) 2011 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 android.content.BroadcastReceiver; 20 import android.content.ContentProviderClient; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.SharedPreferences; 24 import android.provider.MediaStore; 25 import android.util.Log; 26 27 import com.android.providers.media.util.ForegroundThread; 28 29 import java.util.Optional; 30 31 /** 32 * This will be launched during system boot, after the core system has 33 * been brought up but before any non-persistent processes have been 34 * started. It is launched in a special state, with no content provider 35 * or custom application class associated with the process running. 36 * 37 * It's job is to prime the contacts database. Either create it 38 * if it doesn't exist, or open it and force any necessary upgrades. 39 * All of this heavy lifting happens before the boot animation ends. 40 */ 41 public class MediaUpgradeReceiver extends BroadcastReceiver { 42 static final String TAG = "MediaUpgradeReceiver"; 43 static final String PREF_DB_VERSION = "db_version"; 44 45 @Override onReceive(Context context, Intent intent)46 public void onReceive(Context context, Intent intent) { 47 // We are now running with the system up, but no apps started, 48 // so can do whatever cleanup after an upgrade that we want. 49 ForegroundThread.getExecutor().execute(() -> { 50 // Run database migration on a separate thread so that main thread 51 // is available for handling other MediaService requests. 52 tryMigratingDatabases(context); 53 }); 54 } 55 tryMigratingDatabases(Context context)56 private void tryMigratingDatabases(Context context) { 57 // Lookup the last known database version 58 SharedPreferences prefs = context.getSharedPreferences(TAG, Context.MODE_PRIVATE); 59 int prefVersion = prefs.getInt(PREF_DB_VERSION, 0); 60 int dbVersion = DatabaseHelper.getDatabaseVersion(context); 61 if (prefVersion == dbVersion) { 62 return; 63 } 64 prefs.edit().putInt(PREF_DB_VERSION, dbVersion).commit(); 65 66 try { 67 String[] files = context.databaseList(); 68 if (files == null) return; 69 70 MediaProvider mediaProvider = getMediaProvider(context); 71 for (String file : files) { 72 Optional<DatabaseHelper> helper = mediaProvider.getDatabaseHelper(file); 73 if (helper.isPresent()) { 74 long startTime = System.currentTimeMillis(); 75 Log.i(TAG, "---> Start upgrade of media database " + file); 76 try { 77 helper.get().runWithTransaction((db) -> { 78 // Perform just enough to force database upgrade 79 return db.getVersion(); 80 }); 81 } catch (Throwable t) { 82 Log.wtf(TAG, "Error during upgrade of media db " + file, t); 83 } 84 85 Log.i(TAG, "<--- Finished upgrade of media database " + file 86 + " in " + (System.currentTimeMillis() - startTime) + "ms"); 87 } 88 } 89 } catch (Throwable t) { 90 // Something has gone terribly wrong. 91 Log.wtf(TAG, "Error during upgrade attempt.", t); 92 } 93 } 94 getMediaProvider(Context context)95 private MediaProvider getMediaProvider(Context context) { 96 try (ContentProviderClient cpc = 97 context.getContentResolver().acquireContentProviderClient( 98 MediaStore.AUTHORITY)) { 99 return (MediaProvider) cpc.getLocalContentProvider(); 100 } catch (Exception e) { 101 throw new IllegalStateException("Failed to acquire MediaProvider", e); 102 } 103 } 104 } 105