• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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