• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 package com.android.settings.location;
15 
16 import android.content.ComponentName;
17 import android.content.Context;
18 import android.content.Intent;
19 import android.content.pm.ActivityInfo;
20 import android.content.pm.ApplicationInfo;
21 import android.content.pm.PackageManager;
22 import android.content.pm.PackageManager.NameNotFoundException;
23 import android.content.pm.ResolveInfo;
24 import android.location.LocationManager;
25 import android.support.annotation.VisibleForTesting;
26 import android.support.v7.preference.Preference;
27 import android.support.v7.preference.PreferenceCategory;
28 import android.util.Log;
29 import com.android.settingslib.core.lifecycle.Lifecycle;
30 import com.android.settingslib.core.lifecycle.LifecycleObserver;
31 import com.android.settingslib.core.lifecycle.events.OnPause;
32 import com.android.settingslib.widget.FooterPreference;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.List;
37 
38 /**
39  * Preference controller for location footer preference category
40  */
41 public class LocationFooterPreferenceController extends LocationBasePreferenceController
42         implements LifecycleObserver, OnPause {
43     private static final String TAG = "LocationFooter";
44     private static final String KEY_LOCATION_FOOTER = "location_footer";
45     private static final Intent INJECT_INTENT =
46             new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
47     private final Context mContext;
48     private final PackageManager mPackageManager;
49     private Collection<ComponentName> mFooterInjectors;
50 
LocationFooterPreferenceController(Context context, Lifecycle lifecycle)51     public LocationFooterPreferenceController(Context context, Lifecycle lifecycle) {
52         super(context, lifecycle);
53         mContext = context;
54         mPackageManager = mContext.getPackageManager();
55         mFooterInjectors = new ArrayList<>();
56         if (lifecycle != null) {
57             lifecycle.addObserver(this);
58         }
59     }
60 
61     @Override
getPreferenceKey()62     public String getPreferenceKey() {
63         return KEY_LOCATION_FOOTER;
64     }
65 
66     /**
67      * Insert footer preferences. Send a {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION}
68      * broadcast to receivers who have injected a footer
69      */
70     @Override
updateState(Preference preference)71     public void updateState(Preference preference) {
72         PreferenceCategory category = (PreferenceCategory) preference;
73         category.removeAll();
74         mFooterInjectors.clear();
75         Collection<FooterData> footerData = getFooterData();
76         for (FooterData data : footerData) {
77             // Generate a footer preference with the given text
78             FooterPreference footerPreference = new FooterPreference(preference.getContext());
79             String footerString;
80             try {
81                 footerString =
82                         mPackageManager
83                                 .getResourcesForApplication(data.applicationInfo)
84                                 .getString(data.footerStringRes);
85             } catch (NameNotFoundException exception) {
86                 if (Log.isLoggable(TAG, Log.WARN)) {
87                     Log.w(
88                             TAG,
89                             "Resources not found for application "
90                                     + data.applicationInfo.packageName);
91                 }
92                 continue;
93             }
94             footerPreference.setTitle(footerString);
95             // Inject the footer
96             category.addPreference(footerPreference);
97             // Send broadcast to the injector announcing a footer has been injected
98             sendBroadcastFooterDisplayed(data.componentName);
99             mFooterInjectors.add(data.componentName);
100         }
101     }
102 
103     /**
104      * Do nothing on location mode changes.
105      */
106     @Override
onLocationModeChanged(int mode, boolean restricted)107     public void onLocationModeChanged(int mode, boolean restricted) {}
108 
109     /**
110      * Location footer preference group should be displayed if there is at least one footer to
111      * inject.
112      */
113     @Override
isAvailable()114     public boolean isAvailable() {
115         return !getFooterData().isEmpty();
116     }
117 
118     /**
119      * Send a {@link LocationManager#SETTINGS_FOOTER_REMOVED_ACTION} broadcast to footer injectors
120      * when LocationFragment is on pause
121      */
122     @Override
onPause()123     public void onPause() {
124         // Send broadcast to the footer injectors. Notify them the footer is not visible.
125         for (ComponentName componentName : mFooterInjectors) {
126             final Intent intent = new Intent(LocationManager.SETTINGS_FOOTER_REMOVED_ACTION);
127             intent.setComponent(componentName);
128             mContext.sendBroadcast(intent);
129         }
130     }
131 
132     /**
133      * Send a {@link LocationManager#SETTINGS_FOOTER_DISPLAYED_ACTION} broadcast to a footer
134      * injector.
135      */
136     @VisibleForTesting
sendBroadcastFooterDisplayed(ComponentName componentName)137     void sendBroadcastFooterDisplayed(ComponentName componentName) {
138         Intent intent = new Intent(LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION);
139         intent.setComponent(componentName);
140         mContext.sendBroadcast(intent);
141     }
142 
143     /**
144      * Return a list of strings with text provided by ACTION_INJECT_FOOTER broadcast receivers.
145      */
getFooterData()146     private Collection<FooterData> getFooterData() {
147         // Fetch footer text from system apps
148         final List<ResolveInfo> resolveInfos =
149                 mPackageManager.queryBroadcastReceivers(
150                         INJECT_INTENT, PackageManager.GET_META_DATA);
151         if (resolveInfos == null) {
152             if (Log.isLoggable(TAG, Log.ERROR)) {
153                 Log.e(TAG, "Unable to resolve intent " + INJECT_INTENT);
154                 return Collections.emptyList();
155             }
156         } else if (Log.isLoggable(TAG, Log.DEBUG)) {
157             Log.d(TAG, "Found broadcast receivers: " + resolveInfos);
158         }
159 
160         final Collection<FooterData> footerDataList = new ArrayList<>(resolveInfos.size());
161         for (ResolveInfo resolveInfo : resolveInfos) {
162             final ActivityInfo activityInfo = resolveInfo.activityInfo;
163             final ApplicationInfo appInfo = activityInfo.applicationInfo;
164 
165             // If a non-system app tries to inject footer, ignore it
166             if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
167                 if (Log.isLoggable(TAG, Log.WARN)) {
168                     Log.w(TAG, "Ignoring attempt to inject footer from app not in system image: "
169                             + resolveInfo);
170                     continue;
171                 }
172             }
173 
174             // Get the footer text resource id from broadcast receiver's metadata
175             if (activityInfo.metaData == null) {
176                 if (Log.isLoggable(TAG, Log.DEBUG)) {
177                     Log.d(TAG, "No METADATA in broadcast receiver " + activityInfo.name);
178                     continue;
179                 }
180             }
181 
182             final int footerTextRes =
183                     activityInfo.metaData.getInt(LocationManager.METADATA_SETTINGS_FOOTER_STRING);
184             if (footerTextRes == 0) {
185                 if (Log.isLoggable(TAG, Log.WARN)) {
186                     Log.w(
187                             TAG,
188                             "No mapping of integer exists for "
189                                     + LocationManager.METADATA_SETTINGS_FOOTER_STRING);
190                 }
191                 continue;
192             }
193             footerDataList.add(
194                     new FooterData(
195                             footerTextRes,
196                             appInfo,
197                             new ComponentName(activityInfo.packageName, activityInfo.name)));
198         }
199         return footerDataList;
200     }
201 
202     /**
203      * Contains information related to a footer.
204      */
205     private static class FooterData {
206 
207         // The string resource of the footer
208         final int footerStringRes;
209 
210         // Application info of receiver injecting this footer
211         final ApplicationInfo applicationInfo;
212 
213         // The component that injected the footer. It must be a receiver of broadcast
214         // LocationManager.SETTINGS_FOOTER_DISPLAYED_ACTION
215         final ComponentName componentName;
216 
FooterData(int footerRes, ApplicationInfo appInfo, ComponentName componentName)217         FooterData(int footerRes, ApplicationInfo appInfo, ComponentName componentName) {
218             this.footerStringRes = footerRes;
219             this.applicationInfo = appInfo;
220             this.componentName = componentName;
221         }
222     }
223 }
224