1/*
2 * Copyright (C) 2014 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
17package com.android.mms.service;
18
19import android.content.BroadcastReceiver;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentFilter;
23import android.content.res.Configuration;
24import android.os.Bundle;
25import android.os.PersistableBundle;
26import android.telephony.CarrierConfigManager;
27import android.telephony.SmsManager;
28import android.telephony.SubscriptionInfo;
29import android.telephony.SubscriptionManager;
30import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
31import android.util.ArrayMap;
32
33import com.android.internal.telephony.IccCardConstants;
34import com.android.internal.telephony.TelephonyIntents;
35
36import java.util.List;
37import java.util.Map;
38
39/**
40 * This class manages cached copies of all the MMS configuration for each subscription ID.
41 * A subscription ID loosely corresponds to a particular SIM. See the
42 * {@link android.telephony.SubscriptionManager} for more details.
43 *
44 */
45public class MmsConfigManager {
46    private static volatile MmsConfigManager sInstance = new MmsConfigManager();
47
48    public static MmsConfigManager getInstance() {
49        return sInstance;
50    }
51
52    // Map the various subIds to their corresponding MmsConfigs.
53    private final Map<Integer, Bundle> mSubIdConfigMap = new ArrayMap<Integer, Bundle>();
54    private Context mContext;
55    private SubscriptionManager mSubscriptionManager;
56
57    /**
58     * This receiver listens for changes made to SubInfoRecords and for a broadcast telling us
59     * the TelephonyManager has loaded the information needed in order to get the mcc/mnc's for
60     * each subscription Id. When either of these broadcasts are received, we rebuild the
61     * MmsConfig table.
62     *
63     */
64    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
65        public void onReceive(Context context, Intent intent) {
66            String action = intent.getAction();
67            LogUtil.i("MmsConfigManager receiver action: " + action);
68            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED) ||
69                    action.equals(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)) {
70                loadInBackground();
71            }
72        }
73    };
74
75    private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
76            new OnSubscriptionsChangedListener() {
77        @Override
78        public void onSubscriptionsChanged() {
79            loadInBackground();
80        }
81    };
82
83
84    public void init(final Context context) {
85        mContext = context;
86        mSubscriptionManager = SubscriptionManager.from(context);
87
88        // TODO: When this object "finishes" we should unregister.
89        final IntentFilter intentFilterLoaded =
90                new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
91        intentFilterLoaded.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
92        context.registerReceiver(mReceiver, intentFilterLoaded);
93
94        // TODO: When this object "finishes" we should unregister by invoking
95        // SubscriptionManager.getInstance(mContext).unregister(mOnSubscriptionsChangedListener);
96        // This is not strictly necessary because it will be unregistered if the
97        // notification fails but it is good form.
98
99        // Register for SubscriptionInfo list changes which is guaranteed
100        // to invoke onSubscriptionsChanged the first time.
101        SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
102                mOnSubscriptionsChangedListener);
103    }
104
105    private void loadInBackground() {
106        // TODO (ywen) - AsyncTask to avoid creating a new thread?
107        new Thread() {
108            @Override
109            public void run() {
110                Configuration configuration = mContext.getResources().getConfiguration();
111                // Always put the mnc/mcc in the log so we can tell which mms_config.xml
112                // was loaded.
113                LogUtil.i("MmsConfigManager loads in background mcc/mnc: " +
114                        configuration.mcc + "/" + configuration.mnc);
115                load(mContext);
116            }
117        }.start();
118    }
119
120    /**
121     * Find and return the MMS config for a particular subscription id.
122     *
123     * @param subId Subscription id of the desired MMS config bundle
124     * @return MMS config bundle for the particular subscription id. This function can return null
125     *         if the MMS config cannot be found or if this function is called before the
126     *         TelephonyManager has set up the SIMs, or if loadInBackground is still spawning a
127     *         thread after a recent LISTEN_SUBSCRIPTION_INFO_LIST_CHANGED event.
128     */
129    public Bundle getMmsConfigBySubId(int subId) {
130        Bundle mmsConfig;
131        synchronized(mSubIdConfigMap) {
132            mmsConfig = mSubIdConfigMap.get(subId);
133        }
134        LogUtil.i("mms config for sub " + subId + ": " + mmsConfig);
135        // Return a copy so that callers can mutate it.
136        if (mmsConfig != null) {
137          return new Bundle(mmsConfig);
138        }
139        return null;
140    }
141
142    /**
143     * This loads the MMS config for each active subscription.
144     *
145     * MMS config is fetched from CarrierConfigManager and filtered to only include MMS config
146     * variables. The resulting bundles are stored in mSubIdConfigMap.
147     */
148    private void load(Context context) {
149        List<SubscriptionInfo> subs = mSubscriptionManager.getActiveSubscriptionInfoList();
150        if (subs == null || subs.size() < 1) {
151            LogUtil.e(" Failed to load mms config: empty getActiveSubInfoList");
152            return;
153        }
154        // Load all the config bundles into a new map and then swap it with the real map to avoid
155        // blocking.
156        final Map<Integer, Bundle> newConfigMap = new ArrayMap<Integer, Bundle>();
157        final CarrierConfigManager configManager =
158                (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
159        for (SubscriptionInfo sub : subs) {
160            final int subId = sub.getSubscriptionId();
161            PersistableBundle config = configManager.getConfigForSubId(subId);
162            newConfigMap.put(subId, SmsManager.getMmsConfig(config));
163        }
164        synchronized(mSubIdConfigMap) {
165            mSubIdConfigMap.clear();
166            mSubIdConfigMap.putAll(newConfigMap);
167        }
168    }
169
170}
171