1 /*
2  * Copyright (C) 2013 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.documentsui;
18 
19 import android.annotation.SuppressLint;
20 import android.app.ActivityManager;
21 import android.app.Application;
22 import android.content.BroadcastReceiver;
23 import android.content.ContentProviderClient;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.om.OverlayManager;
29 import android.net.Uri;
30 import android.os.RemoteException;
31 import android.os.UserHandle;
32 import android.text.format.DateUtils;
33 import android.util.Log;
34 
35 import androidx.annotation.Nullable;
36 import androidx.localbroadcastmanager.content.LocalBroadcastManager;
37 
38 import com.android.documentsui.base.Lookup;
39 import com.android.documentsui.base.UserId;
40 import com.android.documentsui.clipping.ClipStorage;
41 import com.android.documentsui.clipping.ClipStore;
42 import com.android.documentsui.clipping.DocumentClipper;
43 import com.android.documentsui.queries.SearchHistoryManager;
44 import com.android.documentsui.roots.ProvidersCache;
45 import com.android.documentsui.theme.ThemeOverlayManager;
46 import com.android.modules.utils.build.SdkLevel;
47 
48 import com.google.common.collect.Lists;
49 
50 import java.util.List;
51 
52 import javax.annotation.concurrent.GuardedBy;
53 
54 public class DocumentsApplication extends Application {
55     private static final String TAG = "DocumentsApplication";
56     private static final long PROVIDER_ANR_TIMEOUT = 20 * DateUtils.SECOND_IN_MILLIS;
57 
58     private static final List<String> PACKAGE_FILTER_ACTIONS = Lists.newArrayList(
59             Intent.ACTION_PACKAGE_ADDED,
60             Intent.ACTION_PACKAGE_CHANGED,
61             Intent.ACTION_PACKAGE_REMOVED,
62             Intent.ACTION_PACKAGE_DATA_CLEARED
63     );
64 
65     private static final List<String> PROFILE_FILTER_ACTIONS = Lists.newArrayList(
66             Intent.ACTION_MANAGED_PROFILE_ADDED,
67             Intent.ACTION_MANAGED_PROFILE_REMOVED,
68             Intent.ACTION_MANAGED_PROFILE_UNLOCKED,
69             Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE
70     );
71 
72     @GuardedBy("DocumentsApplication.class")
73     @Nullable
74     private static volatile ConfigStore sConfigStore;
75 
76     private ProvidersCache mProviders;
77     private ThumbnailCache mThumbnailCache;
78     private ClipStorage mClipStore;
79     private DocumentClipper mClipper;
80     private DragAndDropManager mDragAndDropManager;
81     private UserIdManager mUserIdManager;
82     private UserManagerState mUserManagerState;
83     private Lookup<String, String> mFileTypeLookup;
84 
getProvidersCache(Context context)85     public static ProvidersCache getProvidersCache(Context context) {
86         return ((DocumentsApplication) context.getApplicationContext()).mProviders;
87     }
88 
getThumbnailCache(Context context)89     public static ThumbnailCache getThumbnailCache(Context context) {
90         final DocumentsApplication app = (DocumentsApplication) context.getApplicationContext();
91         return app.mThumbnailCache;
92     }
93 
acquireUnstableProviderOrThrow( ContentResolver resolver, String authority)94     public static ContentProviderClient acquireUnstableProviderOrThrow(
95             ContentResolver resolver, String authority) throws RemoteException {
96         final ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
97                 authority);
98         if (client == null) {
99             throw new RemoteException("Failed to acquire provider for " + authority);
100         }
101         client.setDetectNotResponding(PROVIDER_ANR_TIMEOUT);
102         return client;
103     }
104 
getDocumentClipper(Context context)105     public static DocumentClipper getDocumentClipper(Context context) {
106         return ((DocumentsApplication) context.getApplicationContext()).mClipper;
107     }
108 
getClipStore(Context context)109     public static ClipStore getClipStore(Context context) {
110         return ((DocumentsApplication) context.getApplicationContext()).mClipStore;
111     }
112 
getUserIdManager(Context context)113     public static UserIdManager getUserIdManager(Context context) {
114         UserIdManager userIdManager =
115                 ((DocumentsApplication) context.getApplicationContext()).mUserIdManager;
116         if (userIdManager == null) {
117             userIdManager = UserIdManager.create(context);
118             ((DocumentsApplication) context.getApplicationContext()).mUserIdManager = userIdManager;
119         }
120         return userIdManager;
121     }
122 
123     /**
124      * UserManagerState class is used to maintain the list of userIds and other details like
125      * cross profile access, label and badge associated with these userIds.
126      */
getUserManagerState(Context context)127     public static UserManagerState getUserManagerState(Context context) {
128         UserManagerState userManagerState =
129                 ((DocumentsApplication) context.getApplicationContext()).mUserManagerState;
130         if (userManagerState == null && getConfigStore().isPrivateSpaceInDocsUIEnabled()
131                 && SdkLevel.isAtLeastS()) {
132             userManagerState = UserManagerState.create(context);
133             ((DocumentsApplication) context.getApplicationContext()).mUserManagerState =
134                     userManagerState;
135         }
136         return userManagerState;
137     }
138 
getDragAndDropManager(Context context)139     public static DragAndDropManager getDragAndDropManager(Context context) {
140         return ((DocumentsApplication) context.getApplicationContext()).mDragAndDropManager;
141     }
142 
getFileTypeLookup(Context context)143     public static Lookup<String, String> getFileTypeLookup(Context context) {
144         return ((DocumentsApplication) context.getApplicationContext()).mFileTypeLookup;
145     }
146 
147     /**
148      * Retrieve {@link ConfigStore} instance to access feature flags in production code.
149      */
getConfigStore()150     public static synchronized ConfigStore getConfigStore() {
151         if (sConfigStore == null) {
152             sConfigStore = new ConfigStore.ConfigStoreImpl();
153         }
154         return sConfigStore;
155     }
156 
157     /**
158      * Set {@link #mUserManagerState} as null onDestroy of BaseActivity so that new session uses new
159      * instance of {@link #mUserManagerState}
160      */
invalidateUserManagerState(Context context)161     public static void invalidateUserManagerState(Context context) {
162         ((DocumentsApplication) context.getApplicationContext()).mUserManagerState = null;
163     }
164 
onApplyOverlayFinish(boolean result)165     private void onApplyOverlayFinish(boolean result) {
166         Log.d(TAG, "OverlayManager.setEnabled() result: " + result);
167     }
168 
169     @SuppressLint("NewApi") // OverlayManager.class is @hide
170     @Override
onCreate()171     public void onCreate() {
172         super.onCreate();
173         synchronized (DocumentsApplication.class) {
174             if (sConfigStore == null) {
175                 sConfigStore = new ConfigStore.ConfigStoreImpl();
176             }
177         }
178 
179         final ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
180         final OverlayManager om = getSystemService(OverlayManager.class);
181         final int memoryClassBytes = am.getMemoryClass() * 1024 * 1024;
182 
183         if (om != null) {
184             new ThemeOverlayManager(om, getPackageName()).applyOverlays(this, true,
185                     this::onApplyOverlayFinish);
186         } else {
187             Log.w(TAG, "Can't obtain OverlayManager from System Service!");
188         }
189 
190         if (getConfigStore().isPrivateSpaceInDocsUIEnabled()) {
191             mUserManagerState = UserManagerState.create(this);
192             mUserIdManager = null;
193         } else {
194             mUserManagerState = null;
195             mUserIdManager = UserIdManager.create(this);
196         }
197         mProviders = new ProvidersCache(this);
198 
199         mProviders.updateAsync(/* forceRefreshAll= */ false, /* callback= */  null);
200 
201         mThumbnailCache = new ThumbnailCache(memoryClassBytes / 4);
202 
203         mClipStore = new ClipStorage(
204                 ClipStorage.prepareStorage(getCacheDir()),
205                 getSharedPreferences(ClipStorage.PREF_NAME, 0));
206         mClipper = DocumentClipper.create(this, mClipStore);
207 
208         mDragAndDropManager = DragAndDropManager.create(this, mClipper);
209 
210         mFileTypeLookup = new FileTypeMap(this);
211 
212         final IntentFilter packageFilter = new IntentFilter();
213         for (String packageAction : PACKAGE_FILTER_ACTIONS) {
214             packageFilter.addAction(packageAction);
215         }
216         packageFilter.addDataScheme("package");
217         registerReceiver(mCacheReceiver, packageFilter);
218 
219         final IntentFilter localeFilter = new IntentFilter();
220         localeFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
221         registerReceiver(mCacheReceiver, localeFilter);
222 
223         if (SdkLevel.isAtLeastV()) {
224             PROFILE_FILTER_ACTIONS.addAll(Lists.newArrayList(
225                     Intent.ACTION_PROFILE_ADDED,
226                     Intent.ACTION_PROFILE_REMOVED,
227                     Intent.ACTION_PROFILE_AVAILABLE,
228                     Intent.ACTION_PROFILE_UNAVAILABLE
229             ));
230         }
231         final IntentFilter profileFilter = new IntentFilter();
232         for (String profileAction : PROFILE_FILTER_ACTIONS) {
233             profileFilter.addAction(profileAction);
234         }
235         registerReceiver(mCacheReceiver, profileFilter);
236 
237         SearchHistoryManager.getInstance(getApplicationContext());
238     }
239 
240     @Override
onTrimMemory(int level)241     public void onTrimMemory(int level) {
242         super.onTrimMemory(level);
243 
244         mThumbnailCache.onTrimMemory(level);
245     }
246 
247     private BroadcastReceiver mCacheReceiver = new BroadcastReceiver() {
248         @Override
249         public void onReceive(Context context, Intent intent) {
250             final Uri data = intent.getData();
251             final String action = intent.getAction();
252             if (PACKAGE_FILTER_ACTIONS.contains(action) && data != null) {
253                 final String packageName = data.getSchemeSpecificPart();
254                 mProviders.updatePackageAsync(UserId.DEFAULT_USER, packageName);
255             } else if (PROFILE_FILTER_ACTIONS.contains(action)) {
256                 // Make the changes to UserManagerState object before calling providers updateAsync
257                 // so that providers for all the users are loaded
258                 if (getConfigStore().isPrivateSpaceInDocsUIEnabled() && SdkLevel.isAtLeastV()) {
259                     UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
260                     UserId userId = UserId.of(userHandle);
261                     getUserManagerState(context).onProfileActionStatusChange(action, userId);
262                 }
263                 // After we have reloaded roots. Resend the broadcast locally so the other
264                 // components can reload properly after roots are updated.
265                 mProviders.updateAsync(/* forceRefreshAll= */ true,
266                         () -> LocalBroadcastManager.getInstance(context).sendBroadcast(intent));
267             } else {
268                 mProviders.updateAsync(/* forceRefreshAll= */ true, /* callback= */ null);
269             }
270         }
271     };
272 }
273