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 static { 68 final String processName = getProcessName(); 69 sIsUiProcess = processName.endsWith(":PhotoPicker"); 70 71 // We package some of our "production" source code in some of our case (for more details 72 // see MediaProviderTests build rule in packages/providers/MediaProvider/tests/Android.bp), 73 // and occasionally may need to know if we are running as a "real" MediaProvider or "in a 74 // test". 75 // For this - we may check the process. Since process names on Android usually match the 76 // package name of the corresponding package, and the package names of our test end with 77 // ".test" (e.g. "com.android.providers.media.tests") - that's what we are checking for. 78 final boolean isTestProcess = processName.endsWith(".tests"); 79 80 // Only need to load fuse lib in the primary process. 81 if (!sIsUiProcess) { 82 try { 83 System.loadLibrary("fuse_jni"); 84 } catch (UnsatisfiedLinkError e) { 85 86 if (isTestProcess) { 87 // We are "in a test", which does not ship out native lib - log a warning and 88 // carry on. 89 Log.w(TAG, "Could not load fuse_jni.so in a test (" + processName + ")", e); 90 } else { 91 // We are not "in a test" - rethrow. 92 throw e; 93 } 94 } 95 } 96 } 97 98 @Override onCreate()99 public void onCreate() { 100 super.onCreate(); 101 final ConfigStore configStore; 102 synchronized (MediaApplication.class) { 103 sInstance = this; 104 if (sConfigStore == null) { 105 sConfigStore = new ConfigStore.ConfigStoreImpl(getResources()); 106 } 107 configStore = sConfigStore; 108 } 109 110 final File persistentDir = this.getDir("logs", Context.MODE_PRIVATE); 111 Logging.initPersistent(persistentDir); 112 113 if (isPrimaryProcess()) { 114 maybeEnablePhotoPickerSettingsActivity(); 115 configStore.addOnChangeListener( 116 BackgroundThread.getExecutor(), this::maybeEnablePhotoPickerSettingsActivity); 117 } 118 } 119 120 /** Provides access to the Application Context. */ getAppContext()121 public static synchronized Context getAppContext() { 122 // ContentProviders instances may get created before the Application instance 123 // (see javadoc to Application#onCreate()) 124 if (sInstance != null) { 125 return sInstance.getApplicationContext(); 126 } 127 final MediaProvider mediaProviderInstance = MediaProvider.getInstance(); 128 if (mediaProviderInstance != null) { 129 return mediaProviderInstance.getContext().getApplicationContext(); 130 } 131 throw new IllegalStateException("Neither a MediaApplication instance nor a MediaProvider " 132 + "instance has been created yet."); 133 } 134 135 /** Check if this process is the primary MediaProvider's process. */ isPrimaryProcess()136 public static boolean isPrimaryProcess() { 137 return !sIsUiProcess; 138 } 139 140 /** Check if this process is the MediaProvider's UI (PhotoPicker) process. */ isUiProcess()141 public static boolean isUiProcess() { 142 return sIsUiProcess; 143 } 144 145 /** Retrieve {@link ConfigStore} instance. */ 146 @NonNull getConfigStore()147 public static synchronized ConfigStore getConfigStore() { 148 if (sConfigStore == null) { 149 // Normally ConfigStore would be created in onCreate() above, but in some cases the 150 // framework may create ContentProvider-s *before* the Application#onCreate() is called. 151 // In this case we use the MediaProvider instance to create the ConfigStore. 152 sConfigStore = new ConfigStore.ConfigStoreImpl(getAppContext().getResources()); 153 } 154 return sConfigStore; 155 } 156 157 /** 158 * Enable or disable {@link PhotoPickerSettingsActivity} depending on whether 159 * Cloud-Media-in-Photo-Picker feature is enabled or not. 160 */ maybeEnablePhotoPickerSettingsActivity()161 private void maybeEnablePhotoPickerSettingsActivity() { 162 final boolean isCloudMediaEnabled = getConfigStore().isCloudMediaInPhotoPickerEnabled(); 163 164 getPackageManager().setComponentEnabledSetting( 165 new ComponentName(this, PhotoPickerSettingsActivity.class), 166 isCloudMediaEnabled 167 ? COMPONENT_ENABLED_STATE_ENABLED : COMPONENT_ENABLED_STATE_DISABLED, 168 /* flags */ PackageManager.DONT_KILL_APP); 169 170 Log.i(TAG, "PhotoPickerSettingsActivity is now " 171 + (isCloudMediaEnabled ? "enabled" : "disabled" + ".")); 172 } 173 } 174