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.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; 20 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; 21 22 import android.annotation.SuppressLint; 23 import android.app.Application; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 32 import com.android.internal.annotations.GuardedBy; 33 import com.android.modules.utils.BackgroundThread; 34 import com.android.providers.media.photopicker.PhotoPickerSettingsActivity; 35 import com.android.providers.media.util.Logging; 36 37 import java.io.File; 38 39 public class MediaApplication extends Application { 40 private static final String TAG = "MediaApplication"; 41 42 @SuppressLint("StaticFieldLeak") 43 @GuardedBy("MediaApplication.class") 44 @Nullable 45 private static volatile MediaApplication sInstance; 46 47 @GuardedBy("MediaApplication.class") 48 @Nullable 49 private static volatile ConfigStore sConfigStore; 50 51 /** 52 * MediaProvider's code runs in two processes: primary and UI (PhotoPicker). 53 * 54 * The <b>primary</b> process hosts the {@link MediaProvider} itself, along with 55 * the {@link MediaService} and 56 * the {@link com.android.providers.media.fuse.ExternalStorageServiceImpl "Fuse" Service}. 57 * The name of the process matches the package name of the MediaProvider module. 58 * 59 * The <b>UI</b> (PhotoPicker) process hosts MediaProvider's UI components, namely 60 * the {@link com.android.providers.media.photopicker.PhotoPickerActivity} and 61 * the {@link com.android.providers.media.photopicker.PhotoPickerSettingsActivity}. 62 * The name of the process is the package name of the MediaProvider module suffixed with 63 * ":PhotoPicker". 64 */ 65 private static final boolean sIsUiProcess; 66 67 private static final boolean sIsTestProcess; 68 69 static { 70 final String processName = getProcessName(); 71 sIsUiProcess = processName.endsWith(":PhotoPicker"); 72 73 // We package some of our "production" source code in some of our case (for more details 74 // see MediaProviderTests build rule in packages/providers/MediaProvider/tests/Android.bp), 75 // and occasionally may need to know if we are running as a "real" MediaProvider or "in a 76 // test". 77 // For this - we may check the process. Since process names on Android usually match the 78 // package name of the corresponding package, and the package names of our test end with 79 // ".test" (e.g. "com.android.providers.media.tests") - that's what we are checking for. 80 sIsTestProcess = processName.endsWith(".tests"); 81 82 // Only need to load fuse lib in the primary process. 83 if (!sIsUiProcess) { 84 try { 85 System.loadLibrary("fuse_jni"); 86 } catch (UnsatisfiedLinkError e) { 87 88 if (sIsTestProcess) { 89 // We are "in a test", which does not ship out native lib - log a warning and 90 // carry on. 91 Log.w(TAG, "Could not load fuse_jni.so in a test (" + processName + ")", e); 92 } else { 93 // We are not "in a test" - rethrow. 94 throw e; 95 } 96 } 97 } 98 } 99 100 @Override onCreate()101 public void onCreate() { 102 super.onCreate(); 103 final ConfigStore configStore; 104 synchronized (MediaApplication.class) { 105 sInstance = this; 106 if (sConfigStore == null) { 107 sConfigStore = sIsTestProcess ? ConfigStore.getDefaultConfigStore() : 108 new ConfigStore.ConfigStoreImpl(getResources()); 109 } 110 configStore = sConfigStore; 111 } 112 113 final File persistentDir = this.getDir("logs", Context.MODE_PRIVATE); 114 Logging.initPersistent(persistentDir); 115 116 if (!sIsUiProcess && !sIsTestProcess) { 117 maybeEnablePhotoPickerSettingsActivity(); 118 configStore.addOnChangeListener( 119 BackgroundThread.getExecutor(), this::maybeEnablePhotoPickerSettingsActivity); 120 } 121 } 122 123 /** Provides access to the Application Context. */ getAppContext()124 public static synchronized Context getAppContext() { 125 // ContentProviders instances may get created before the Application instance 126 // (see javadoc to Application#onCreate()) 127 if (sInstance != null) { 128 return sInstance.getApplicationContext(); 129 } 130 final MediaProvider mediaProviderInstance = MediaProvider.getInstance(); 131 if (mediaProviderInstance != null) { 132 return mediaProviderInstance.getContext().getApplicationContext(); 133 } 134 throw new IllegalStateException("Neither a MediaApplication instance nor a MediaProvider " 135 + "instance has been created yet."); 136 } 137 138 /** Retrieve {@link ConfigStore} instance. */ 139 @NonNull getConfigStore()140 public static synchronized ConfigStore getConfigStore() { 141 if (sConfigStore == null) { 142 // Normally ConfigStore would be created in onCreate() above, but in some cases the 143 // framework may create ContentProvider-s *before* the Application#onCreate() is called. 144 // In this case we use the MediaProvider instance to create the ConfigStore. 145 sConfigStore = sIsTestProcess ? ConfigStore.getDefaultConfigStore() : 146 new ConfigStore.ConfigStoreImpl(getAppContext().getResources()); 147 } 148 return sConfigStore; 149 } 150 151 /** 152 * Enable or disable {@link PhotoPickerSettingsActivity} depending on whether 153 * Cloud-Media-in-Photo-Picker feature is enabled or not. 154 */ maybeEnablePhotoPickerSettingsActivity()155 private void maybeEnablePhotoPickerSettingsActivity() { 156 final boolean isCloudMediaEnabled = getConfigStore().isCloudMediaInPhotoPickerEnabled(); 157 158 getPackageManager().setComponentEnabledSetting( 159 new ComponentName(this, PhotoPickerSettingsActivity.class), 160 isCloudMediaEnabled 161 ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED, 162 /* flags */ PackageManager.DONT_KILL_APP); 163 164 Log.i(TAG, "PhotoPickerSettingsActivity is now " 165 + (isCloudMediaEnabled ? "enabled" : "disabled" + ".")); 166 } 167 } 168