| /* |
| * Copyright (C) 2013 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.cellbroadcastservice; |
| |
| import android.Manifest; |
| import android.app.Activity; |
| import android.app.AppOpsManager; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Bundle; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.provider.Telephony.Sms.Intents; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.cdma.CdmaSmsCbProgramData; |
| |
| import java.util.ArrayList; |
| import java.util.concurrent.ConcurrentLinkedQueue; |
| import java.util.function.Consumer; |
| |
| /** |
| * Handle CDMA Service Category Program Data requests and responses. |
| */ |
| public final class CdmaServiceCategoryProgramHandler extends WakeLockStateMachine { |
| |
| // hold the callbacks provided from the framework, executed after SCP messages are broadcast |
| private ConcurrentLinkedQueue<Consumer<Bundle>> mScpCallback = new ConcurrentLinkedQueue<>(); |
| |
| /** |
| * Create a new CDMA inbound SMS handler. |
| */ |
| CdmaServiceCategoryProgramHandler(Context context) { |
| super("CdmaServiceCategoryProgramHandler", context, Looper.myLooper()); |
| mContext = context; |
| } |
| |
| /** |
| * Create a new State machine for SCPD requests. |
| * |
| * @param context the context to use |
| * @return the new SCPD handler |
| */ |
| static CdmaServiceCategoryProgramHandler makeScpHandler(Context context) { |
| CdmaServiceCategoryProgramHandler handler = new CdmaServiceCategoryProgramHandler( |
| context); |
| handler.start(); |
| return handler; |
| } |
| |
| /** |
| * Handle a CDMA SCP message. |
| * |
| * @param slotIndex the index of the slot which received the message |
| * @param programData the SMS CB program data of the message |
| * @param originatingAddress the originating address of the message |
| * @param callback a callback to run after each cell broadcast receiver has handled |
| * the SCP message |
| */ |
| public void onCdmaScpMessage(int slotIndex, ArrayList<CdmaSmsCbProgramData> programData, |
| String originatingAddress, Consumer<Bundle> callback) { |
| onCdmaCellBroadcastSms(new CdmaScpMessage(slotIndex, programData, originatingAddress, |
| callback)); |
| } |
| |
| /** |
| * Class for holding parts of a CDMA Service Program Category message. |
| */ |
| private class CdmaScpMessage { |
| int mSlotIndex; |
| ArrayList<CdmaSmsCbProgramData> mProgamData; |
| String mOriginatingAddress; |
| Consumer<Bundle> mCallback; |
| |
| CdmaScpMessage(int slotIndex, ArrayList<CdmaSmsCbProgramData> programData, |
| String originatingAddress, Consumer<Bundle> callback) { |
| mSlotIndex = slotIndex; |
| mProgamData = programData; |
| mOriginatingAddress = originatingAddress; |
| mCallback = callback; |
| } |
| } |
| |
| |
| /** |
| * Handle Cell Broadcast messages from {@code CdmaInboundSmsHandler}. |
| * 3GPP-format Cell Broadcast messages sent from radio are handled in the subclass. |
| * |
| * @param message the message to process |
| * @return true if an ordered broadcast was sent; false on failure |
| */ |
| @Override |
| protected boolean handleSmsMessage(Message message) { |
| if (message.obj instanceof CdmaScpMessage) { |
| CdmaScpMessage cdmaScpMessage = (CdmaScpMessage) message.obj; |
| return handleServiceCategoryProgramData(cdmaScpMessage.mProgamData, |
| cdmaScpMessage.mOriginatingAddress, cdmaScpMessage.mSlotIndex, |
| cdmaScpMessage.mCallback); |
| } else { |
| loge("handleMessage got object of type: " + message.obj.getClass().getName()); |
| return false; |
| } |
| } |
| |
| /** |
| * Send SCPD request to CellBroadcastReceiver as an ordered broadcast. |
| * |
| * @param programData the program data of the SCP message |
| * @param originatingAddress the address of the sender |
| * @param phoneId the phoneId that the message was received on |
| * @param callback a callback to run after each broadcast |
| * @return true if an ordered broadcast was sent; false on failure |
| */ |
| private boolean handleServiceCategoryProgramData(ArrayList<CdmaSmsCbProgramData> programData, |
| String originatingAddress, int phoneId, Consumer<Bundle> callback) { |
| if (programData == null) { |
| loge("handleServiceCategoryProgramData: program data list is null!"); |
| return false; |
| } |
| |
| Intent intent = new Intent(Intents.SMS_SERVICE_CATEGORY_PROGRAM_DATA_RECEIVED_ACTION); |
| intent.putExtra("sender", originatingAddress); |
| intent.putParcelableArrayListExtra("program_data", programData); |
| SubscriptionManager.putPhoneIdAndSubIdExtra(intent, phoneId); |
| |
| // TODO: move this resource and its overlays to the CellBroadcastService directory |
| String[] pkgs = mContext.getResources().getStringArray( |
| com.android.internal.R.array.config_defaultCellBroadcastReceiverPkgs); |
| mReceiverCount.addAndGet(pkgs.length); |
| for (String pkg : pkgs) { |
| intent.setPackage(pkg); |
| mContext.sendOrderedBroadcast(intent, Manifest.permission.RECEIVE_SMS, |
| AppOpsManager.OP_RECEIVE_SMS, mScpResultsReceiver, |
| getHandler(), Activity.RESULT_OK, null, null); |
| mScpCallback.add(callback); |
| } |
| return true; |
| } |
| |
| /** |
| * Broadcast receiver to handle results of ordered broadcast. Sends the results back to the |
| * framework through a provided callback. |
| */ |
| private final BroadcastReceiver mScpResultsReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| int resultCode = getResultCode(); |
| if ((resultCode != Activity.RESULT_OK) && (resultCode != Intents.RESULT_SMS_HANDLED)) { |
| loge("SCP results error: result code = " + resultCode); |
| return; |
| } |
| Bundle extras = getResultExtras(false); |
| Consumer<Bundle> callback = mScpCallback.poll(); |
| callback.accept(extras); |
| if (DBG) log("mScpResultsReceiver finished"); |
| if (mReceiverCount.decrementAndGet() == 0) { |
| sendMessage(EVENT_BROADCAST_COMPLETE); |
| } |
| } |
| }; |
| } |