1 /* 2 * Copyright (C) 2018 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.car.settings.location; 18 19 import android.car.drivingstate.CarUxRestrictions; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ActivityInfo; 23 import android.content.pm.ApplicationInfo; 24 import android.content.pm.PackageManager; 25 import android.content.pm.ResolveInfo; 26 import android.location.LocationManager; 27 28 import androidx.annotation.StringRes; 29 import androidx.annotation.VisibleForTesting; 30 import androidx.preference.PreferenceGroup; 31 32 import com.android.car.settings.R; 33 import com.android.car.settings.common.FragmentController; 34 import com.android.car.settings.common.Logger; 35 import com.android.car.settings.common.PreferenceController; 36 import com.android.car.ui.preference.CarUiPreference; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.List; 41 42 /** 43 * Injects Location Footers into a {@link PreferenceGroup} with a matching key. 44 */ 45 public class LocationFooterPreferenceController extends PreferenceController<PreferenceGroup> { 46 private static final Logger LOG = new Logger(LocationFooterPreferenceController.class); 47 private static final Intent INJECT_INTENT = 48 new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); 49 50 private PackageManager mPackageManager; 51 LocationFooterPreferenceController(Context context, String preferenceKey, FragmentController fragmentController, CarUxRestrictions uxRestrictions)52 public LocationFooterPreferenceController(Context context, String preferenceKey, 53 FragmentController fragmentController, CarUxRestrictions uxRestrictions) { 54 super(context, preferenceKey, fragmentController, uxRestrictions); 55 mPackageManager = context.getPackageManager(); 56 } 57 58 @VisibleForTesting setPackageManager(PackageManager packageManager)59 void setPackageManager(PackageManager packageManager) { 60 mPackageManager = packageManager; 61 } 62 63 @Override getPreferenceType()64 protected Class<PreferenceGroup> getPreferenceType() { 65 return PreferenceGroup.class; 66 } 67 68 @Override onCreateInternal()69 protected void onCreateInternal() { 70 for (LocationFooter footer : getInjectedLocationFooters()) { 71 try { 72 String footerString = mPackageManager 73 .getResourcesForApplication(footer.mApplicationInfo) 74 .getString(footer.mFooterStringRes); 75 76 // For each injected footer: Create a new preference, set the summary 77 // and icon, then inject under the footer preference group. 78 CarUiPreference newPreference = new CarUiPreference(getContext()); 79 newPreference.setSummary(footerString); 80 newPreference.setIcon(R.drawable.ic_settings_about); 81 newPreference.setSelectable(false); 82 getPreference().addPreference(newPreference); 83 } catch (PackageManager.NameNotFoundException exception) { 84 LOG.w("Resources not found for application " 85 + footer.mApplicationInfo.packageName); 86 } 87 } 88 } 89 90 @Override updateState(PreferenceGroup preferenceGroup)91 protected void updateState(PreferenceGroup preferenceGroup) { 92 preferenceGroup.setVisible(preferenceGroup.getPreferenceCount() > 0); 93 } 94 95 /** 96 * Return a list of strings provided by ACTION_INJECT_FOOTER broadcast receivers. If there 97 * are no injectors, an immutable emptry list is returned. 98 */ getInjectedLocationFooters()99 private List<LocationFooter> getInjectedLocationFooters() { 100 List<ResolveInfo> resolveInfos = mPackageManager.queryBroadcastReceivers( 101 INJECT_INTENT, PackageManager.GET_META_DATA); 102 if (resolveInfos == null) { 103 LOG.e("Unable to resolve intent " + INJECT_INTENT); 104 return Collections.emptyList(); 105 } else { 106 LOG.d("Found broadcast receivers: " + resolveInfos); 107 } 108 109 List<LocationFooter> locationFooters = new ArrayList<>(resolveInfos.size()); 110 for (ResolveInfo resolveInfo : resolveInfos) { 111 ActivityInfo activityInfo = resolveInfo.activityInfo; 112 ApplicationInfo appInfo = activityInfo.applicationInfo; 113 114 // If a non-system app tries to inject footer, ignore it 115 if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 116 LOG.w("Ignoring attempt to inject footer from a non-system app: " + resolveInfo); 117 continue; 118 } 119 120 // If the injector does not have valid METADATA, ignore it 121 if (activityInfo.metaData == null) { 122 LOG.d("No METADATA in broadcast receiver " + activityInfo.name); 123 continue; 124 } 125 126 // Get the footer text resource id from broadcast receiver's metadata 127 int footerTextRes = 128 activityInfo.metaData.getInt(LocationManager.METADATA_SETTINGS_FOOTER_STRING); 129 if (footerTextRes == 0) { 130 LOG.w("No mapping of integer exists for " 131 + LocationManager.METADATA_SETTINGS_FOOTER_STRING); 132 continue; 133 } 134 locationFooters.add(new LocationFooter(footerTextRes, appInfo)); 135 } 136 return locationFooters; 137 } 138 139 /** 140 * Contains information related to a footer. 141 */ 142 private static class LocationFooter { 143 // The string resource of the footer. 144 @StringRes 145 private final int mFooterStringRes; 146 // Application info of the receiver injecting this footer. 147 private final ApplicationInfo mApplicationInfo; 148 LocationFooter(@tringRes int footerRes, ApplicationInfo appInfo)149 LocationFooter(@StringRes int footerRes, ApplicationInfo appInfo) { 150 mFooterStringRes = footerRes; 151 mApplicationInfo = appInfo; 152 } 153 } 154 } 155