1 /*
2  * Copyright (C) 2015 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 package com.android.car.pm;
17 
18 import static android.car.content.pm.CarPackageManager.DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS;
19 
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.UserIdInt;
23 import android.car.builtin.content.pm.PackageManagerHelper;
24 import android.car.builtin.util.Slogf;
25 import android.car.content.pm.CarPackageManager;
26 import android.content.Context;
27 import android.content.pm.ActivityInfo;
28 import android.content.pm.PackageInfo;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.os.Bundle;
32 
33 import com.android.car.CarLog;
34 
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.Collections;
38 import java.util.List;
39 
40 /**
41  *  Read App meta data and look for Distraction Optimization(DO) tags.
42  *  An app can tag a distraction optimized activity to be DO by adding the following meta-data
43  *  to that <activity> element:
44  *
45  *  <pre>{@code
46  *  <activity>
47  *      <meta-data android:name="distractionOptimized" android:value="true"/>
48  *  </activity>
49  *  }</pre>
50  *
51  */
52 public class CarAppMetadataReader {
53 
54     private static final String TAG = CarLog.tagFor(CarAppMetadataReader.class);
55 
56     /** Name of the meta-data attribute of the Activity that denotes distraction optimized */
57     private static final String DO_METADATA_ATTRIBUTE = "distractionOptimized";
58 
59     private static final List<String> ALL_REGION_ONLY = Collections.singletonList(
60             CarPackageManager.DRIVING_SAFETY_REGION_ALL);
61 
62     @Nullable
getAllActivitiesForPackageAsUser(Context context, String packageName, @UserIdInt int userId)63     private static ActivityInfo[] getAllActivitiesForPackageAsUser(Context context,
64             String packageName, @UserIdInt int userId)  throws NameNotFoundException {
65         final PackageManager pm = context.getPackageManager();
66         PackageInfo pkgInfo =
67                 PackageManagerHelper.getPackageInfoAsUser(pm, packageName,
68                         PackageManager.GET_ACTIVITIES
69                                 | PackageManager.GET_META_DATA
70                                 | PackageManager.MATCH_DISABLED_COMPONENTS
71                                 | PackageManager.MATCH_DIRECT_BOOT_AWARE
72                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
73                         userId);
74         if (pkgInfo == null) {
75             return null;
76         }
77 
78         return pkgInfo.activities;
79     }
80 
81     /**
82      * Parses the given package and returns Distraction Optimized information, if present.
83      *
84      * @return Array of DO activity names in the given package
85      */
86     @Nullable
findDistractionOptimizedActivitiesAsUser(Context context, String packageName, @UserIdInt int userId, @NonNull String drivingSafetyRegion)87     public static String[] findDistractionOptimizedActivitiesAsUser(Context context,
88             String packageName, @UserIdInt int userId, @NonNull String drivingSafetyRegion)
89             throws NameNotFoundException {
90 
91 
92         // Check if any of the activities in the package are DO by checking all the
93         // <activity> elements. MATCH_DISABLED_COMPONENTS is included so that we are immediately
94         // prepared to respond to any components that toggle from disabled to enabled.
95         ActivityInfo[] activities = getAllActivitiesForPackageAsUser(context, packageName, userId);
96         if (activities == null) {
97             if (CarPackageManagerService.DBG) {
98                 Slogf.d(TAG, "Null Activities for " + packageName);
99             }
100             return null;
101         }
102         List<String> optimizedActivityList = new ArrayList();
103         for (ActivityInfo activity : activities) {
104             Bundle metaData = activity.metaData;
105             if (metaData == null) {
106                 continue;
107             }
108             String regionString = metaData.getString(DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS,
109                     CarPackageManager.DRIVING_SAFETY_REGION_ALL);
110             if (!isRegionSupported(regionString, drivingSafetyRegion)) {
111                 continue;
112             }
113             if (metaData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
114                 if (CarPackageManagerService.DBG) {
115                     Slogf.d(TAG,
116                             "DO Activity:" + activity.packageName + "/" + activity.name);
117                 }
118                 optimizedActivityList.add(activity.name);
119             }
120         }
121         if (optimizedActivityList.isEmpty()) {
122             return null;
123         }
124         return optimizedActivityList.toArray(new String[optimizedActivityList.size()]);
125     }
126 
127     /**
128      * Check {@link CarPackageManager#getSupportedDrivingSafetyRegionsForActivityAsUser(
129      * String, String, int)}.
130      */
getSupportedDrivingSafetyRegionsForActivityAsUser(Context context, String packageName, String activityClassName, @UserIdInt int userId)131     public static List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(Context context,
132             String packageName, String activityClassName, @UserIdInt int userId)
133             throws NameNotFoundException {
134         ActivityInfo[] activities = getAllActivitiesForPackageAsUser(context, packageName, userId);
135         if (activities == null) {
136             throw new NameNotFoundException();
137         }
138         for (ActivityInfo info: activities) {
139             if (!info.name.equals(activityClassName)) {
140                 continue;
141             }
142             // Found activity
143             Bundle metaData = info.metaData;
144             if (metaData == null) {
145                 return Collections.EMPTY_LIST;
146             }
147             if (!metaData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
148                 // no distractionOptimized, so region metadata does not matter.
149                 return Collections.EMPTY_LIST;
150             }
151             String regionString = metaData.getString(DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS,
152                     CarPackageManager.DRIVING_SAFETY_REGION_ALL);
153             String[] regions = regionString.split(",");
154             for (String region: regions) {
155                 if (CarPackageManager.DRIVING_SAFETY_REGION_ALL.equals(region)) {
156                     return ALL_REGION_ONLY;
157                 }
158             }
159             return Arrays.asList(regions);
160         }
161         throw new NameNotFoundException();
162     }
163 
isRegionSupported(String regionString, String currentRegion)164     private static boolean isRegionSupported(String regionString, String currentRegion) {
165         if (regionString.isEmpty()) { // not specified means all regions.
166             return true;
167         }
168         if (currentRegion.equals(CarPackageManager.DRIVING_SAFETY_REGION_ALL)) {
169             return true;
170         }
171         String[] regions = regionString.split(",");
172         for (String region: regions) {
173             if (CarPackageManager.DRIVING_SAFETY_REGION_ALL.equals(region)) {
174                 return true;
175             }
176             if (currentRegion.equals(region)) {
177                 return true;
178             }
179         }
180         // valid regions but does not match currentRegion.
181         if (CarPackageManagerService.DBG) {
182             Slogf.d(TAG,
183                     "isRegionSupported not supported, regionString:" + regionString
184                             + " region:" + currentRegion);
185         }
186         return false;
187     }
188 }
189