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