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