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