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