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