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