1 /* 2 * Copyright (C) 2021 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.location; 18 19 import android.content.Context; 20 import android.content.Intent; 21 import android.content.pm.ActivityInfo; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.location.LocationManager; 26 import android.text.Html; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import androidx.preference.Preference; 31 import androidx.preference.PreferenceScreen; 32 33 import com.android.settings.R; 34 import com.android.settingslib.HelpUtils; 35 import com.android.settingslib.widget.FooterPreference; 36 37 import java.util.ArrayList; 38 import java.util.Collection; 39 import java.util.Collections; 40 import java.util.List; 41 42 /** 43 * Preference controller for Location Settings footer. 44 */ 45 public class LocationSettingsFooterPreferenceController extends LocationBasePreferenceController { 46 private static final String TAG = "LocationFooter"; 47 private static final String PARAGRAPH_SEPARATOR = "<br><br>"; 48 private static final Intent INJECT_INTENT = 49 new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION); 50 51 private final PackageManager mPackageManager; 52 private FooterPreference mFooterPreference; 53 private boolean mLocationEnabled; 54 private String mInjectedFooterString; 55 LocationSettingsFooterPreferenceController(Context context, String key)56 public LocationSettingsFooterPreferenceController(Context context, String key) { 57 super(context, key); 58 mPackageManager = context.getPackageManager(); 59 } 60 61 @Override displayPreference(PreferenceScreen screen)62 public void displayPreference(PreferenceScreen screen) { 63 super.displayPreference(screen); 64 mFooterPreference = screen.findPreference(getPreferenceKey()); 65 } 66 67 @Override onLocationModeChanged(int mode, boolean restricted)68 public void onLocationModeChanged(int mode, boolean restricted) { 69 mLocationEnabled = mLocationEnabler.isEnabled(mode); 70 updateFooterPreference(); 71 } 72 73 /** 74 * Insert footer preferences. 75 */ 76 @Override updateState(Preference preference)77 public void updateState(Preference preference) { 78 Collection<FooterData> footerData = getFooterData(); 79 for (FooterData data : footerData) { 80 try { 81 mInjectedFooterString = 82 mPackageManager 83 .getResourcesForApplication(data.applicationInfo) 84 .getString(data.footerStringRes); 85 updateFooterPreference(); 86 } catch (PackageManager.NameNotFoundException exception) { 87 Log.w( 88 TAG, 89 "Resources not found for application " 90 + data.applicationInfo.packageName); 91 } 92 } 93 } 94 updateFooterPreference()95 private void updateFooterPreference() { 96 String footerString = mContext.getString(R.string.location_settings_footer_general); 97 if (mLocationEnabled) { 98 if (!TextUtils.isEmpty(mInjectedFooterString)) { 99 footerString = Html.escapeHtml(mInjectedFooterString) + PARAGRAPH_SEPARATOR 100 + footerString; 101 } 102 } else { 103 footerString = mContext.getString(R.string.location_settings_footer_location_off) 104 + PARAGRAPH_SEPARATOR 105 + footerString; 106 } 107 if (mFooterPreference != null) { 108 mFooterPreference.setTitle(Html.fromHtml(footerString)); 109 mFooterPreference.setLearnMoreAction(v -> openLocationLearnMoreLink()); 110 mFooterPreference.setLearnMoreText(mContext.getString( 111 R.string.location_settings_footer_learn_more_content_description)); 112 } 113 } 114 openLocationLearnMoreLink()115 private void openLocationLearnMoreLink() { 116 mFragment.startActivityForResult( 117 HelpUtils.getHelpIntent( 118 mContext, 119 mContext.getString(R.string.location_settings_footer_learn_more_link), 120 /*backupContext=*/""), 121 /*requestCode=*/ 0); 122 } 123 124 /** 125 * Location footer preference group should always be displayed. 126 */ 127 @Override getAvailabilityStatus()128 public int getAvailabilityStatus() { 129 return AVAILABLE; 130 } 131 132 /** 133 * Return a list of strings with text provided by ACTION_INJECT_FOOTER broadcast receivers. 134 */ getFooterData()135 private List<FooterData> getFooterData() { 136 // Fetch footer text from system apps 137 List<ResolveInfo> resolveInfos = 138 mPackageManager.queryBroadcastReceivers( 139 INJECT_INTENT, PackageManager.GET_META_DATA); 140 if (resolveInfos == null) { 141 Log.e(TAG, "Unable to resolve intent " + INJECT_INTENT); 142 return Collections.emptyList(); 143 } 144 145 if (Log.isLoggable(TAG, Log.DEBUG)) { 146 Log.d(TAG, "Found broadcast receivers: " + resolveInfos); 147 } 148 149 List<FooterData> footerDataList = new ArrayList<>(resolveInfos.size()); 150 for (ResolveInfo resolveInfo : resolveInfos) { 151 ActivityInfo activityInfo = resolveInfo.activityInfo; 152 ApplicationInfo appInfo = activityInfo.applicationInfo; 153 154 // If a non-system app tries to inject footer, ignore it 155 if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 156 Log.w(TAG, "Ignoring attempt to inject footer from app not in system image: " 157 + resolveInfo); 158 continue; 159 } 160 161 // Get the footer text resource id from broadcast receiver's metadata 162 if (activityInfo.metaData == null) { 163 if (Log.isLoggable(TAG, Log.DEBUG)) { 164 Log.d(TAG, "No METADATA in broadcast receiver " + activityInfo.name); 165 } 166 continue; 167 } 168 169 final int footerTextRes = 170 activityInfo.metaData.getInt(LocationManager.METADATA_SETTINGS_FOOTER_STRING); 171 if (footerTextRes == 0) { 172 Log.w( 173 TAG, 174 "No mapping of integer exists for " 175 + LocationManager.METADATA_SETTINGS_FOOTER_STRING); 176 continue; 177 } 178 footerDataList.add(new FooterData(footerTextRes, appInfo)); 179 } 180 return footerDataList; 181 } 182 183 /** 184 * Contains information related to a footer. 185 */ 186 private static class FooterData { 187 188 // The string resource of the footer 189 public final int footerStringRes; 190 191 // Application info of receiver injecting this footer 192 public final ApplicationInfo applicationInfo; 193 FooterData(int footerRes, ApplicationInfo appInfo)194 FooterData(int footerRes, ApplicationInfo appInfo) { 195 this.footerStringRes = footerRes; 196 this.applicationInfo = appInfo; 197 } 198 } 199 } 200