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.documentsui.theme;
18 
19 import android.content.Context;
20 import android.content.om.OverlayInfo;
21 import android.content.om.OverlayManager;
22 import android.content.pm.ApplicationInfo;
23 import android.content.pm.PackageManager;
24 import android.os.AsyncTask;
25 import android.os.UserHandle;
26 import android.util.Log;
27 
28 import androidx.annotation.NonNull;
29 import androidx.annotation.Nullable;
30 import androidx.core.content.ContextCompat;
31 import androidx.core.util.Consumer;
32 
33 import java.util.List;
34 
35 /**
36  * ThemeOverlayManager manage runtime resource overlay packages of DocumentsUI
37  */
38 public class ThemeOverlayManager {
39     private static final String TAG = ThemeOverlayManager.class.getSimpleName();
40     private static final String PERMISSION_CHANGE_OVERLAY_PACKAGES =
41             "android.permission.CHANGE_OVERLAY_PACKAGES";
42 
43     private final OverlayManager mOverlayManager;
44     private String mTargetPackageId;
45     private UserHandle mUserHandle;
46 
ThemeOverlayManager(@onNull OverlayManager overlayManager, String targetPackageId)47     public ThemeOverlayManager(@NonNull OverlayManager overlayManager, String targetPackageId) {
48         mOverlayManager = overlayManager;
49         mTargetPackageId = targetPackageId;
50         mUserHandle = UserHandle.of(UserHandle.myUserId());
51     }
52 
53     /**
54      * Apply runtime overlay package, dynamic enabled overlay do not support priority yet
55      *
56      * @param context the activity or context from caller
57      * @param enabled whether or not enable overlay package
58      */
applyOverlays(Context context, boolean enabled, Consumer<Boolean> callback)59     public void applyOverlays(Context context, boolean enabled, Consumer<Boolean> callback) {
60         if (ContextCompat.checkSelfPermission(context, PERMISSION_CHANGE_OVERLAY_PACKAGES)
61                 == PackageManager.PERMISSION_GRANTED) {
62             setEnabled(enabled, callback);
63         } else {
64             Log.w(TAG, "Permission: " + PERMISSION_CHANGE_OVERLAY_PACKAGES + " did not granted!");
65             callback.accept(false);
66         }
67     }
68 
getOverlayInfo()69     private List<OverlayInfo> getOverlayInfo() {
70         // (b/132933212): Only static overlay package support priority attrs
71         // TODO: Alternative way to support enabled multiple overlay packages by priority is
72         //       tag meta-data in the application of overlay package's AndroidManifest.xml
73         // TODO: Parse meta data through PM in DocumentsApplication and use collection to reorder
74         return mOverlayManager.getOverlayInfosForTarget(mTargetPackageId, mUserHandle);
75     }
76 
77     /**
78      * Return the OverlayInfo which is provided by the docsUI overlay package pre-installed in
79      * device. We assume there should only one docsUI overlay package because priority not work
80      * for non-static overlay, so vendor should put only one docsUI overlay package.
81      *
82      * @param pm the PackageManager
83      */
84     @Nullable
getValidOverlay(@onNull PackageManager pm)85     public OverlayInfo getValidOverlay(@NonNull PackageManager pm) {
86         for (OverlayInfo info : getOverlayInfo()) {
87             try {
88                 final ApplicationInfo ai = pm.getApplicationInfo(info.getPackageName(), 0);
89                 if ((ai.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
90                     return info;
91                 }
92             } catch (PackageManager.NameNotFoundException e) {
93                 Log.w(TAG, "Can't get ApplicationInfo of overlay package " + info.getPackageName());
94             }
95         }
96         return null;
97     }
98 
setEnabled(boolean enabled, Consumer<Boolean> callback)99     private void setEnabled(boolean enabled, Consumer<Boolean> callback) {
100         new AsyncTask<Void, Void, Boolean>() {
101             @Override
102             protected Boolean doInBackground(Void... params) {
103                 return setEnabledOverlayPackages(getOverlayInfo(), enabled);
104             }
105 
106             @Override
107             protected void onPostExecute(Boolean result) {
108                 super.onPostExecute(result);
109                 if (callback != null) {
110                     callback.accept(result);
111                 }
112             }
113         }.execute();
114     }
115 
setEnabledOverlayPackages(List<OverlayInfo> infos, boolean enabled)116     private boolean setEnabledOverlayPackages(List<OverlayInfo> infos, boolean enabled) {
117         boolean bSuccess = true;
118         for (OverlayInfo info : infos) {
119             try {
120                 if (info.isEnabled() != enabled) {
121                     mOverlayManager.setEnabled(info.getPackageName(), enabled, mUserHandle);
122                 } else {
123                     Log.w(TAG, "Skip enabled overlay package:" + info.getPackageName()
124                             + ", user:" + mUserHandle);
125                     bSuccess = false;
126                 }
127             } catch (RuntimeException re) {
128                 Log.e(TAG, "Failed to enable overlay: " + info.getPackageName() + ", user: "
129                         + mUserHandle);
130                 bSuccess = false;
131             }
132         }
133         return bSuccess;
134     }
135 }
136