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.net.ConnectivityManager;
26import android.net.Network;
27import android.os.AsyncResult;
28import android.os.Handler;
29import android.os.Message;
30import android.os.PersistableBundle;
31import android.telephony.CarrierConfigManager;
32import android.telephony.Rlog;
33import android.text.TextUtils;
34import android.util.LocalLog;
35import android.util.Log;
36
37import com.android.internal.util.ArrayUtils;
38import com.android.internal.util.IndentingPrintWriter;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.util.Arrays;
43import java.util.HashMap;
44import java.util.HashSet;
45import java.util.Map;
46import java.util.Set;
47
48import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
49import static android.telephony.CarrierConfigManager.KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY;
50
51/**
52 * This class act as an CarrierSignalling Agent.
53 * it load registered carrier signalling receivers from carrier config, cache the result to avoid
54 * repeated polling and send the intent to the interested receivers.
55 * Each CarrierSignalAgent is associated with a phone object.
56 */
57public class CarrierSignalAgent extends Handler {
58
59    private static final String LOG_TAG = CarrierSignalAgent.class.getSimpleName();
60    private static final boolean DBG = true;
61    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
62    private static final boolean WAKE = true;
63    private static final boolean NO_WAKE = false;
64
65    /** delimiters for parsing config of the form: pakName./receiverName : signal1, signal2,..*/
66    private static final String COMPONENT_NAME_DELIMITER = "\\s*:\\s*";
67    private static final String CARRIER_SIGNAL_DELIMITER = "\\s*,\\s*";
68
69    /** Member variables */
70    private final Phone mPhone;
71    private boolean mDefaultNetworkAvail;
72
73    /**
74     * This is a map of intent action -> set of component name of statically registered
75     * carrier signal receivers(wakeup receivers).
76     * Those intents are declared in the Manifest files, aiming to wakeup broadcast receivers.
77     * Carrier apps should be careful when configuring the wake signal list to avoid unnecessary
78     * wakeup. Note we use Set as the entry value to compare config directly regardless of element
79     * order.
80     * @see CarrierConfigManager#KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY
81     */
82    private Map<String, Set<ComponentName>> mCachedWakeSignalConfigs = new HashMap<>();
83
84    /**
85     * This is a map of intent action -> set of component name of dynamically registered
86     * carrier signal receivers(non-wakeup receivers). Those intents will not wake up the apps.
87     * Note Carrier apps should avoid configuring no wake signals in there Manifest files.
88     * Note we use Set as the entry value to compare config directly regardless of element order.
89     * @see CarrierConfigManager#KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY
90     */
91    private Map<String, Set<ComponentName>> mCachedNoWakeSignalConfigs = new HashMap<>();
92
93    private static final int EVENT_REGISTER_DEFAULT_NETWORK_AVAIL = 0;
94
95    /**
96     * This is a list of supported signals from CarrierSignalAgent
97     */
98    private final Set<String> mCarrierSignalList = new HashSet<>(Arrays.asList(
99            TelephonyIntents.ACTION_CARRIER_SIGNAL_PCO_VALUE,
100            TelephonyIntents.ACTION_CARRIER_SIGNAL_REDIRECTED,
101            TelephonyIntents.ACTION_CARRIER_SIGNAL_REQUEST_NETWORK_FAILED,
102            TelephonyIntents.ACTION_CARRIER_SIGNAL_RESET,
103            TelephonyIntents.ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE));
104
105    private final LocalLog mErrorLocalLog = new LocalLog(20);
106
107    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
108        public void onReceive(Context context, Intent intent) {
109            String action = intent.getAction();
110            if (DBG) log("CarrierSignalAgent receiver action: " + action);
111            if (action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
112                loadCarrierConfig();
113            }
114        }
115    };
116
117    private ConnectivityManager.NetworkCallback mNetworkCallback;
118
119    /** Constructor */
120    public CarrierSignalAgent(Phone phone) {
121        mPhone = phone;
122        loadCarrierConfig();
123        // reload configurations on CARRIER_CONFIG_CHANGED
124        mPhone.getContext().registerReceiver(mReceiver,
125                new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
126        mPhone.getCarrierActionAgent().registerForCarrierAction(
127                CarrierActionAgent.CARRIER_ACTION_REPORT_DEFAULT_NETWORK_STATUS, this,
128                EVENT_REGISTER_DEFAULT_NETWORK_AVAIL, null, false);
129    }
130
131    @Override
132    public void handleMessage(Message msg) {
133        switch (msg.what) {
134            case EVENT_REGISTER_DEFAULT_NETWORK_AVAIL:
135                AsyncResult ar = (AsyncResult) msg.obj;
136                if (ar.exception != null) {
137                    Rlog.e(LOG_TAG, "Register default network exception: " + ar.exception);
138                    return;
139                }
140                final ConnectivityManager connectivityMgr =  ConnectivityManager
141                        .from(mPhone.getContext());
142                if ((boolean) ar.result) {
143                    mNetworkCallback = new ConnectivityManager.NetworkCallback() {
144                        @Override
145                        public void onAvailable(Network network) {
146                            // an optimization to avoid signaling on every default network switch.
147                            if (!mDefaultNetworkAvail) {
148                                if (DBG) log("Default network available: " + network);
149                                Intent intent = new Intent(TelephonyIntents
150                                        .ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
151                                intent.putExtra(
152                                        TelephonyIntents.EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, true);
153                                notifyCarrierSignalReceivers(intent);
154                                mDefaultNetworkAvail = true;
155                            }
156                        }
157                        @Override
158                        public void onLost(Network network) {
159                            if (DBG) log("Default network lost: " + network);
160                            Intent intent = new Intent(TelephonyIntents
161                                    .ACTION_CARRIER_SIGNAL_DEFAULT_NETWORK_AVAILABLE);
162                            intent.putExtra(
163                                    TelephonyIntents.EXTRA_DEFAULT_NETWORK_AVAILABLE_KEY, false);
164                            notifyCarrierSignalReceivers(intent);
165                            mDefaultNetworkAvail = false;
166                        }
167                    };
168                    connectivityMgr.registerDefaultNetworkCallback(mNetworkCallback, mPhone);
169                    log("Register default network");
170
171                } else if (mNetworkCallback != null) {
172                    connectivityMgr.unregisterNetworkCallback(mNetworkCallback);
173                    mNetworkCallback = null;
174                    mDefaultNetworkAvail = false;
175                    log("unregister default network");
176                }
177                break;
178            default:
179                break;
180        }
181    }
182
183    /**
184     * load carrier config and cached the results into a hashMap action -> array list of components.
185     */
186    private void loadCarrierConfig() {
187        CarrierConfigManager configManager = (CarrierConfigManager) mPhone.getContext()
188                .getSystemService(Context.CARRIER_CONFIG_SERVICE);
189        PersistableBundle b = null;
190        if (configManager != null) {
191            b = configManager.getConfig();
192        }
193        if (b != null) {
194            synchronized (mCachedWakeSignalConfigs) {
195                log("Loading carrier config: " + KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
196                Map<String, Set<ComponentName>> config = parseAndCache(
197                        b.getStringArray(KEY_CARRIER_APP_WAKE_SIGNAL_CONFIG_STRING_ARRAY));
198                // In some rare cases, up-to-date config could be fetched with delay and all signals
199                // have already been delivered the receivers from the default carrier config.
200                // To handle this raciness, we should notify those receivers (from old configs)
201                // and reset carrier actions. This should be done before cached Config got purged
202                // and written with the up-to-date value, Otherwise those receivers from the
203                // old config might lingers without properly clean-up.
204                if (!mCachedWakeSignalConfigs.isEmpty()
205                        && !config.equals(mCachedWakeSignalConfigs)) {
206                    if (VDBG) log("carrier config changed, reset receivers from old config");
207                    mPhone.getCarrierActionAgent().sendEmptyMessage(
208                            CarrierActionAgent.CARRIER_ACTION_RESET);
209                }
210                mCachedWakeSignalConfigs = config;
211            }
212
213            synchronized (mCachedNoWakeSignalConfigs) {
214                log("Loading carrier config: "
215                        + KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY);
216                Map<String, Set<ComponentName>> config = parseAndCache(
217                        b.getStringArray(KEY_CARRIER_APP_NO_WAKE_SIGNAL_CONFIG_STRING_ARRAY));
218                if (!mCachedNoWakeSignalConfigs.isEmpty()
219                        && !config.equals(mCachedNoWakeSignalConfigs)) {
220                    if (VDBG) log("carrier config changed, reset receivers from old config");
221                    mPhone.getCarrierActionAgent().sendEmptyMessage(
222                            CarrierActionAgent.CARRIER_ACTION_RESET);
223                }
224                mCachedNoWakeSignalConfigs = config;
225            }
226        }
227    }
228
229    /**
230     * Parse each config with the form {pakName./receiverName : signal1, signal2,.} and cached the
231     * result internally to avoid repeated polling
232     * @see #CARRIER_SIGNAL_DELIMITER
233     * @see #COMPONENT_NAME_DELIMITER
234     * @param configs raw information from carrier config
235     */
236    private Map<String, Set<ComponentName>> parseAndCache(String[] configs) {
237        Map<String, Set<ComponentName>> newCachedWakeSignalConfigs = new HashMap<>();
238        if (!ArrayUtils.isEmpty(configs)) {
239            for (String config : configs) {
240                if (!TextUtils.isEmpty(config)) {
241                    String[] splitStr = config.trim().split(COMPONENT_NAME_DELIMITER, 2);
242                    if (splitStr.length == 2) {
243                        ComponentName componentName = ComponentName
244                                .unflattenFromString(splitStr[0]);
245                        if (componentName == null) {
246                            loge("Invalid component name: " + splitStr[0]);
247                            continue;
248                        }
249                        String[] signals = splitStr[1].split(CARRIER_SIGNAL_DELIMITER);
250                        for (String s : signals) {
251                            if (!mCarrierSignalList.contains(s)) {
252                                loge("Invalid signal name: " + s);
253                                continue;
254                            }
255                            Set<ComponentName> componentList = newCachedWakeSignalConfigs.get(s);
256                            if (componentList == null) {
257                                componentList = new HashSet<>();
258                                newCachedWakeSignalConfigs.put(s, componentList);
259                            }
260                            componentList.add(componentName);
261                            if (VDBG) {
262                                logv("Add config " + "{signal: " + s
263                                        + " componentName: " + componentName + "}");
264                            }
265                        }
266                    } else {
267                        loge("invalid config format: " + config);
268                    }
269                }
270            }
271        }
272        return newCachedWakeSignalConfigs;
273    }
274
275    /**
276     * Check if there are registered carrier broadcast receivers to handle the passing intent
277     */
278    public boolean hasRegisteredReceivers(String action) {
279        return mCachedWakeSignalConfigs.containsKey(action)
280                || mCachedNoWakeSignalConfigs.containsKey(action);
281    }
282
283    /**
284     * Broadcast the intents explicitly.
285     * Some sanity check will be applied before broadcasting.
286     * - for non-wakeup(runtime) receivers, make sure the intent is not declared in their manifests
287     * and apply FLAG_EXCLUDE_STOPPED_PACKAGES to avoid wake-up
288     * - for wakeup(manifest) receivers, make sure there are matched receivers with registered
289     * intents.
290     *
291     * @param intent intent which signals carrier apps
292     * @param receivers a list of component name for broadcast receivers.
293     *                  Those receivers could either be statically declared in Manifest or
294     *                  registered during run-time.
295     * @param wakeup true indicate wakeup receivers otherwise non-wakeup receivers
296     */
297    private void broadcast(Intent intent, Set<ComponentName> receivers, boolean wakeup) {
298        final PackageManager packageManager = mPhone.getContext().getPackageManager();
299        for (ComponentName name : receivers) {
300            Intent signal = new Intent(intent);
301            signal.setComponent(name);
302
303            if (wakeup && packageManager.queryBroadcastReceivers(signal,
304                    PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
305                loge("Carrier signal receivers are configured but unavailable: "
306                        + signal.getComponent());
307                return;
308            }
309            if (!wakeup && !packageManager.queryBroadcastReceivers(signal,
310                    PackageManager.MATCH_DEFAULT_ONLY).isEmpty()) {
311                loge("Runtime signals shouldn't be configured in Manifest: "
312                        + signal.getComponent());
313                return;
314            }
315
316            signal.putExtra(PhoneConstants.SUBSCRIPTION_KEY, mPhone.getSubId());
317            signal.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
318            if (!wakeup) signal.setFlags(Intent.FLAG_EXCLUDE_STOPPED_PACKAGES);
319
320            try {
321                mPhone.getContext().sendBroadcast(signal);
322                if (DBG) {
323                    log("Sending signal " + signal.getAction() + ((signal.getComponent() != null)
324                            ? " to the carrier signal receiver: " + signal.getComponent() : ""));
325                }
326            } catch (ActivityNotFoundException e) {
327                loge("Send broadcast failed: " + e);
328            }
329        }
330    }
331
332    /**
333     * Match the intent against cached tables to find a list of registered carrier signal
334     * receivers and broadcast the intent.
335     * @param intent broadcasting intent, it could belong to wakeup, non-wakeup signal list or both
336     *
337     */
338    public void notifyCarrierSignalReceivers(Intent intent) {
339        Set<ComponentName> receiverSet;
340
341        synchronized (mCachedWakeSignalConfigs) {
342            receiverSet = mCachedWakeSignalConfigs.get(intent.getAction());
343            if (!ArrayUtils.isEmpty(receiverSet)) {
344                broadcast(intent, receiverSet, WAKE);
345            }
346        }
347
348        synchronized (mCachedNoWakeSignalConfigs) {
349            receiverSet = mCachedNoWakeSignalConfigs.get(intent.getAction());
350            if (!ArrayUtils.isEmpty(receiverSet)) {
351                broadcast(intent, receiverSet, NO_WAKE);
352            }
353        }
354    }
355
356    private void log(String s) {
357        Rlog.d(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
358    }
359
360    private void loge(String s) {
361        mErrorLocalLog.log(s);
362        Rlog.e(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
363    }
364
365    private void logv(String s) {
366        Rlog.v(LOG_TAG, "[" + mPhone.getPhoneId() + "]" + s);
367    }
368
369    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
370        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
371        pw.println("mCachedWakeSignalConfigs:");
372        ipw.increaseIndent();
373        for (Map.Entry<String, Set<ComponentName>> entry : mCachedWakeSignalConfigs.entrySet()) {
374            pw.println("signal: " + entry.getKey() + " componentName list: " + entry.getValue());
375        }
376        ipw.decreaseIndent();
377
378        pw.println("mCachedNoWakeSignalConfigs:");
379        ipw.increaseIndent();
380        for (Map.Entry<String, Set<ComponentName>> entry : mCachedNoWakeSignalConfigs.entrySet()) {
381            pw.println("signal: " + entry.getKey() + " componentName list: " + entry.getValue());
382        }
383        ipw.decreaseIndent();
384
385        pw.println("mDefaultNetworkAvail: " + mDefaultNetworkAvail);
386
387        pw.println("error log:");
388        ipw.increaseIndent();
389        mErrorLocalLog.dump(fd, pw, args);
390        ipw.decreaseIndent();
391    }
392}
393