1 /* 2 * Copyright (C) 2024 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.settings.notification.modes; 18 19 import static android.app.NotificationManager.EXTRA_AUTOMATIC_RULE_ID; 20 21 import android.content.ComponentName; 22 import android.content.Intent; 23 import android.content.pm.ActivityInfo; 24 import android.content.pm.ComponentInfo; 25 import android.content.pm.PackageManager; 26 import android.content.pm.ResolveInfo; 27 import android.service.notification.ConditionProviderService; 28 import android.util.Log; 29 30 import androidx.annotation.Nullable; 31 32 import com.android.settingslib.notification.modes.ZenMode; 33 34 import java.util.List; 35 import java.util.function.Function; 36 37 class ConfigurationActivityHelper { 38 39 private static final String TAG = "ConfigurationActivityHelper"; 40 41 private final PackageManager mPm; 42 ConfigurationActivityHelper(PackageManager pm)43 ConfigurationActivityHelper(PackageManager pm) { 44 mPm = pm; 45 } 46 47 @Nullable getConfigurationActivityIntentForMode(ZenMode zenMode, Function<ComponentName, ComponentInfo> approvedServiceFinder)48 Intent getConfigurationActivityIntentForMode(ZenMode zenMode, 49 Function<ComponentName, ComponentInfo> approvedServiceFinder) { 50 51 ZenMode.Owner owner = zenMode.getOwner(); 52 ComponentName configActivity = null; 53 if (owner.configurationActivity() != null) { 54 // If a configuration activity is present, use that directly in the intent 55 configActivity = owner.configurationActivity(); 56 } else { 57 // Otherwise, look for a condition provider service for the rule's package 58 ComponentInfo ci = approvedServiceFinder.apply(owner.conditionProvider()); 59 if (ci != null) { 60 configActivity = extractConfigurationActivityFromComponent(ci); 61 } 62 } 63 64 if (configActivity != null 65 && (owner.packageName() == null 66 || isSameOwnerPackage(owner.packageName(), configActivity)) 67 && isResolvableActivity(configActivity)) { 68 return new Intent() 69 .setComponent(configActivity) 70 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) 71 .putExtra(ConditionProviderService.EXTRA_RULE_ID, zenMode.getId()) 72 .putExtra(EXTRA_AUTOMATIC_RULE_ID, zenMode.getId()); 73 } else { 74 return null; 75 } 76 } 77 78 @Nullable getConfigurationActivityFromApprovedComponent(ComponentInfo ci)79 ComponentName getConfigurationActivityFromApprovedComponent(ComponentInfo ci) { 80 ComponentName configActivity = extractConfigurationActivityFromComponent(ci); 81 if (configActivity != null 82 && isSameOwnerPackage(ci.packageName, configActivity) 83 && isResolvableActivity(configActivity)) { 84 return configActivity; 85 } else { 86 return null; 87 } 88 } 89 90 /** 91 * Extract the {@link ComponentName} corresponding to the mode configuration <em>activity</em> 92 * from the component declaring the rule (which may be the Activity itself, or a CPS that points 93 * to the activity in question in its metadata). 94 * 95 * <p>This method doesn't perform any validation, so the activity may or may not exist. 96 */ 97 @Nullable extractConfigurationActivityFromComponent(ComponentInfo ci)98 private ComponentName extractConfigurationActivityFromComponent(ComponentInfo ci) { 99 if (ci instanceof ActivityInfo) { 100 // New (activity-backed) rule. 101 return new ComponentName(ci.packageName, ci.name); 102 } else if (ci.metaData != null) { 103 // Old (service-backed) rule. 104 final String configurationActivity = ci.metaData.getString( 105 ConditionProviderService.META_DATA_CONFIGURATION_ACTIVITY); 106 if (configurationActivity != null) { 107 return ComponentName.unflattenFromString(configurationActivity); 108 } 109 } 110 return null; 111 } 112 113 /** 114 * Verifies that the activity is the same package as the rule owner. 115 */ isSameOwnerPackage(String ownerPkg, ComponentName activityName)116 private boolean isSameOwnerPackage(String ownerPkg, ComponentName activityName) { 117 try { 118 int ownerUid = mPm.getPackageUid(ownerPkg, 0); 119 int configActivityOwnerUid = mPm.getPackageUid(activityName.getPackageName(), 0); 120 if (ownerUid == configActivityOwnerUid) { 121 return true; 122 } else { 123 Log.w(TAG, String.format("Config activity (%s) not in owner package (%s)", 124 activityName, ownerPkg)); 125 return false; 126 } 127 } catch (PackageManager.NameNotFoundException e) { 128 Log.e(TAG, "Failed to find config activity " + activityName); 129 return false; 130 } 131 } 132 133 /** Verifies that the activity exists and hasn't been disabled. */ isResolvableActivity(ComponentName activityName)134 private boolean isResolvableActivity(ComponentName activityName) { 135 Intent intent = new Intent().setComponent(activityName); 136 List<ResolveInfo> results = mPm.queryIntentActivities(intent, /* flags= */ 0); 137 138 if (intent.resolveActivity(mPm) == null || results.isEmpty()) { 139 Log.w(TAG, "Cannot resolve: " + activityName); 140 return false; 141 } 142 return true; 143 } 144 } 145