1/*
2 * Copyright (C) 2016 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 */
16package com.android.internal.telephony;
17
18import android.content.ActivityNotFoundException;
19import android.content.BroadcastReceiver;
20import android.content.ComponentName;
21import android.content.Context;
22import android.content.Intent;
23import android.content.IntentFilter;
24import android.content.pm.PackageManager;
25import android.os.PersistableBundle;
26import android.telephony.CarrierConfigManager;
27import android.telephony.Rlog;
28import android.text.TextUtils;
29import android.util.LocalLog;
30import android.util.Log;
31
32import com.android.internal.util.ArrayUtils;
33import com.android.internal.util.IndentingPrintWriter;
34
35import java.io.FileDescriptor;
36import java.io.PrintWriter;
37import java.util.ArrayList;
38import java.util.Arrays;
39import java.util.HashMap;
40import java.util.HashSet;
41import java.util.List;
42import java.util.Map;
43import java.util.Set;
44
45import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
46import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
47
48/**
49 * This class act as an CarrierSignalling Agent.
50 * it load registered carrier signalling receivers from carrier config, cache the result to avoid
51 * repeated polling and send the intent to the interested receivers.
52 * Each CarrierSignalAgent is associated with a phone object.
53 */
54public class CarrierSignalAgent {
55
56    private static final String LOG_TAG = CarrierSignalAgent.class.getSimpleName();
57    private static final boolean DBG = true;
58    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
59    private static final boolean WAKE = true;
60    private static final boolean NO_WAKE = false;
61
62    /** delimiters for parsing config of the form: pakName./receiverName : signal1, signal2,..*/
63    private static final String COMPONENT_NAME_DELIMITER = "\\s*:\\s*";
64    private static final String CARRIER_SIGNAL_DELIMITER = "\\s*,\\s*";
65
66    /** Member variables */
67    private final Phone mPhone;
68
69    /**
70     * This is a map of intent action -> array list of component name of statically registered
71     * carrier signal receivers(wakeup receivers).
72     * Those intents are declared in the Manifest files, aiming to wakeup broadcast receivers.
73     * Carrier apps should be careful when configuring the wake signal list to avoid unnecessary
74     * wakeup.
75     * @see CarrierConfigManager#KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY
76     */
77    private final Map<String, List<ComponentName>> mCachedWakeSignalConfigs = new HashMap<>();
78
79    /**
80     * This is a map of intent action -> array list of component name of dynamically registered
81     * carrier signal receivers(non-wakeup receivers). Those intents will not wake up the apps.
82     * Note Carrier apps should avoid configuring no wake signals in there Manifest files.
83     * @see CarrierConfigManager#KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY
84     */
85    private final Map<String, List<ComponentName>> mCachedNoWakeSignalConfigs = new HashMap<>();
86
87    /**
88     * This is a list of supported signals from CarrierSignalAgent
89     */
90    private final Set<String> mCarrierSignalList = new HashSet<>(Arrays.asList(
91            TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE,
92            TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED,
93            TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
94            TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET));
95
96    private final LocalLog mErrorLocalLog = new LocalLog(20);
97
98    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
99        public void onReceive(Context context, Intent intent) {
100            String action = intent.getAction();
101            if (DBG) log("CarrierSignalAgent receiver action: " + action);
102            if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
103                // notify carrier apps before cache get purged
104                if (mPhone.getIccCard() != null
105                        && IccCardConstants.State.ABSENT == mPhone.getIccCard().getState()) {
106                    notifyCarrierSignalReceivers(
107                            new Intent(TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET));
108                }
109                loadCarrierConfig();
110            }
111        }
112    };
113
114    /** Constructor */
115    public CarrierSignalAgent(Phone phone) {
116        mPhone = phone;
117        loadCarrierConfig();
118        // reload configurations on CARRIER_CONFIG_CHANGED
119        mPhone.getContext().registerReceiver(mReceiver,
120                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
121    }
122
123    /**
124     * load carrier config and cached the results into a hashMap action -> array list of components.
125     */
126    private void loadCarrierConfig() {
127        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
128                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
129        PersistableBundle b = null;
130        if (configManager != null) {
131            b = configManager.getConfig();
132        }
133        if (b != null) {
134            synchronized (mCachedWakeSignalConfigs) {
135                mCachedWakeSignalConfigs.clear();
136                log("Loading carrier config: " + KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
137                parseAndCache(b.getStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY),
138                        mCachedWakeSignalConfigs);
139            }
140
141            synchronized (mCachedNoWakeSignalConfigs) {
142                mCachedNoWakeSignalConfigs.clear();
143                log("Loading carrier config: "
144                        + KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
145                parseAndCache(b.getStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY),
146                        mCachedNoWakeSignalConfigs);
147            }
148        }
149    }
150
151    /**
152     * Parse each config with the form {pakName./receiverName : signal1, signal2,.} and cached the
153     * result internally to avoid repeated polling
154     * @see #CARRIER_SIGNAL_DELIMITER
155     * @see #COMPONENT_NAME_DELIMITER
156     * @param configs raw information from carrier config
157     */
158    private void parseAndCache(String[] configs,
159                               Map<String, List<ComponentName>> cachedConfigs) {
160        if (!ArrayUtils.isEmpty(configs)) {
161            for (String config : configs) {
162                if (!TextUtils.isEmpty(config)) {
163                    String[] splitStr = config.trim().split(COMPONENT_NAME_DELIMITER, 2);
164                    if (splitStr.length == 2) {
165                        ComponentName componentName = ComponentName
166                                .unflattenFromString(splitStr[0]);
167                        if (componentName == null) {
168                            loge("Invalid component name: " + splitStr[0]);
169                            continue;
170                        }
171                        String[] signals = splitStr[1].split(CARRIER_SIGNAL_DELIMITER);
172                        for (String s : signals) {
173                            if (!mCarrierSignalList.contains(s)) {
174                                loge("Invalid signal name: " + s);
175                                continue;
176                            }
177                            List<ComponentName> componentList = cachedConfigs.get(s);
178                            if (componentList == null) {
179                                componentList = new ArrayList<>();
180                                cachedConfigs.put(s, componentList);
181                            }
182                            componentList.add(componentName);
183                            if (VDBG) {
184                                logv("Add config " + "{signal: " + s
185                                        + " componentName: " + componentName + "}");
186                            }
187                        }
188                    } else {
189                        loge("invalid config format: " + config);
190                    }
191                }
192            }
193        }
194    }
195
196    /**
197     * Check if there are registered carrier broadcast receivers to handle the passing intent
198     */
199    public boolean hasRegisteredReceivers(String action) {
200        return mCachedWakeSignalConfigs.containsKey(action)
201                || mCachedNoWakeSignalConfigs.containsKey(action);
202    }
203
204    /**
205     * Broadcast the intents explicitly.
206     * Some sanity check will be applied before broadcasting.
207     * - for non-wakeup(runtime) receivers, make sure the intent is not declared in their manifests
208     * and apply FLAG_EXCLUDE_STOPPED_PACKAGES to avoid wake-up
209     * - for wakeup(manifest) receivers, make sure there are matched receivers with registered
210     * intents.
211     *
212     * @param intent intent which signals carrier apps
213     * @param receivers a list of component name for broadcast receivers.
214     *                  Those receivers could either be statically declared in Manifest or
215     *                  registered during run-time.
216     * @param wakeup true indicate wakeup receivers otherwise non-wakeup receivers
217     */
218    private void broadcast(Intent intent, List<ComponentName> receivers, boolean wakeup) {
219        final PackageManager packageManager = mPhone.getContext().getPackageManager();
220        for (ComponentName name : receivers) {
221            Intent signal = new Intent(intent);
222            signal.setComponent(name);
223
224            if (wakeup && packageManager.queryBroadcastReceivers(signal,
225                    PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
226                loge("Carrier signal receivers are configured but unavailable: "
227                        + signal.getComponent());
228                return;
229            }
230            if (!wakeup && !packageManager.queryBroadcastReceivers(signal,
231                    PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
232                loge("Runtime signals shouldn't be configured in Manifest: "
233                        + signal.getComponent());
234                return;
235            }
236
237            signal.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPhone.getSubId());
238            signal.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
239            if (!wakeup) signal.setFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
240
241            try {
242                mPhone.getContext().sendBroadcast(signal);
243                if (DBG) {
244                    log("Sending signal " + signal.getAction() + ((signal.getComponent() != null)
245                            ? " to the carrier signal receiver: " + signal.getComponent() : ""));
246                }
247            } catch (ActivityNotFoundException e) {
248                loge("Send broadcast failed: " + e);
249            }
250        }
251    }
252
253    /**
254     * Match the intent against cached tables to find a list of registered carrier signal
255     * receivers and broadcast the intent.
256     * @param intent broadcasting intent, it could belong to wakeup, non-wakeup signal list or both
257     *
258     */
259    public void notifyCarrierSignalReceivers(Intent intent) {
260        List<ComponentName> receiverList;
261
262        synchronized (mCachedWakeSignalConfigs) {
263            receiverList = mCachedWakeSignalConfigs.get(intent.getAction());
264            if (!ArrayUtils.isEmpty(receiverList)) {
265                broadcast(intent, receiverList, WAKE);
266            }
267        }
268
269        synchronized (mCachedNoWakeSignalConfigs) {
270            receiverList = mCachedNoWakeSignalConfigs.get(intent.getAction());
271            if (!ArrayUtils.isEmpty(receiverList)) {
272                broadcast(intent, receiverList, NO_WAKE);
273            }
274        }
275    }
276
277    private void log(String s) {
278        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
279    }
280
281    private void loge(String s) {
282        mErrorLocalLog.log(s);
283        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
284    }
285
286    private void logv(String s) {
287        Rlog.v(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
288    }
289
290    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
291        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
292        pw.println("mCachedWakeSignalConfigs:");
293        ipw.increaseIndent();
294        for (Map.Entry<String, List<ComponentName>> entry : mCachedWakeSignalConfigs.entrySet()) {
295            pw.println("signal: " + entry.getKey() + " componentName list: " + entry.getValue());
296        }
297        ipw.decreaseIndent();
298
299        pw.println("mCachedNoWakeSignalConfigs:");
300        ipw.increaseIndent();
301        for (Map.Entry<String, List<ComponentName>> entry : mCachedNoWakeSignalConfigs.entrySet()) {
302            pw.println("signal: " + entry.getKey() + " componentName list: " + entry.getValue());
303        }
304        ipw.decreaseIndent();
305
306        pw.println("error log:");
307        ipw.increaseIndent();
308        mErrorLocalLog.dump(fd, pw, args);
309        ipw.decreaseIndent();
310    }
311}
312