1 /*
2  * Copyright (C) 2022 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.systemui.car.drivemode;
18 
19 import android.content.Context;
20 import android.content.om.OverlayInfo;
21 import android.content.om.OverlayManager;
22 import android.os.UserHandle;
23 import android.text.TextUtils;
24 import android.util.Log;
25 
26 import androidx.annotation.NonNull;
27 
28 import com.android.systemui.dagger.SysUISingleton;
29 
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Map;
33 
34 import javax.inject.Inject;
35 
36 /**
37  * A class that activates and deactivates RROs based on the selected drive mode.
38  */
39 @SysUISingleton
40 public class DriveModeThemeSwitcher implements DriveModeManager.Callback {
41 
42     private static final boolean DEBUG = false;
43     private static final String TAG = "DriveModeThemeSwitcher";
44     private static final String RRO_TARGET_PACKAGE = "android";
45     private static final String RRO_PACKAGE = "drivemode.modes";
46 
47     private final OverlayManager mOverlayManager;
48     private final Map<String, String> mDriveModeOverlays;
49 
50     @Inject
DriveModeThemeSwitcher(Context context, DriveModeManager driveModeManager)51     DriveModeThemeSwitcher(Context context, DriveModeManager driveModeManager) {
52         mOverlayManager = context.getSystemService(OverlayManager.class);
53         if (mOverlayManager != null) {
54             mDriveModeOverlays = createDriveModeToRROPackageMap();
55             driveModeManager.addCallback(this);
56         } else {
57             mDriveModeOverlays = new HashMap<>();
58             if (DEBUG) Log.e(TAG, "No overlay manager");
59         }
60     }
61 
62     @Override
onDriveModeChanged(@onNull String newDriveMode)63     public void onDriveModeChanged(@NonNull String newDriveMode) {
64         String overlayName = mDriveModeOverlays.getOrDefault(newDriveMode, null);
65         if (isOverlayActive(overlayName)) {
66             return;
67         }
68         disableActiveOverlays();
69         enableOverlay(mDriveModeOverlays.get(newDriveMode));
70     }
71 
isOverlayActive(String packageName)72     private boolean isOverlayActive(String packageName) {
73         if (mOverlayManager == null || TextUtils.isEmpty(packageName)) {
74             return false;
75         }
76 
77         OverlayInfo overlayInfo = mOverlayManager.getOverlayInfo(packageName, UserHandle.CURRENT);
78         return overlayInfo != null && overlayInfo.isEnabled();
79     }
80 
enableOverlay(String packageName)81     private void enableOverlay(String packageName) {
82         if (mOverlayManager == null || packageName == null) {
83             return;
84         }
85         mOverlayManager.setEnabled(packageName, true, UserHandle.CURRENT);
86     }
87 
disableActiveOverlays()88     private void disableActiveOverlays() {
89         if (mOverlayManager == null) {
90             return;
91         }
92 
93         mDriveModeOverlays.forEach((overlayName, overlayPackage) -> {
94             if (mOverlayManager.getOverlayInfo(overlayPackage, UserHandle.CURRENT).isEnabled()) {
95                 mOverlayManager.setEnabled(overlayPackage, false, UserHandle.CURRENT);
96             }
97         });
98     }
99 
100     /*
101      * Method that initializes a map of <DriveMode, RRO Package> pairs. For convenience, the drive
102      * mode is extracted directly from the RRO package in this iteration. In a real world scenario
103      * there would be a separate mapping of the DriveMode to its RRO package
104      * (e.g. an array in Resources).
105      */
createDriveModeToRROPackageMap()106     private HashMap<String, String> createDriveModeToRROPackageMap() {
107         List<OverlayInfo> rroList = mOverlayManager.getOverlayInfosForTarget(
108                 RRO_TARGET_PACKAGE,
109                 UserHandle.CURRENT
110         );
111 
112         HashMap<String, String> driveModeOverlays = new HashMap<>();
113         rroList.forEach(rro -> {
114             if (rro.packageName.contains(RRO_PACKAGE)) {
115                 driveModeOverlays.put(extractRROName(rro.packageName), rro.packageName);
116             }
117         });
118         return driveModeOverlays;
119     }
120 
extractRROName(String overlayPackage)121     private String extractRROName(String overlayPackage) {
122         String name;
123         try {
124             String[] split = overlayPackage.split("\\.");
125             String rawName = split[split.length - 2];
126             name =  rawName.substring(0, 1).toUpperCase() + rawName.substring(1);
127         } catch (IndexOutOfBoundsException e) {
128             Log.e(TAG, "Error extracting the name from the RRO " + overlayPackage);
129             name = "Name not found";
130         } catch (NullPointerException e) {
131             Log.e(TAG, "Error extracting the name from the RRO, overlayPackage is null.");
132             name = "Name not found";
133         }
134 
135         return name;
136     }
137 }
138