Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 66128c65 authored by Pankaj Kanwar's avatar Pankaj Kanwar Committed by Android (Google) Code Review
Browse files

Merge "Show notification when VoWiFi is active, and user is out of service for...

Merge "Show notification when VoWiFi is active, and user is out of service for voice." into oc-mr1-dev
parents fb6f7632 a092e483
Loading
Loading
Loading
Loading
+284 −76
Original line number Diff line number Diff line
@@ -16,9 +16,9 @@

package com.android.internal.telephony;

import android.app.PendingIntent;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
@@ -31,8 +31,13 @@ import android.telephony.CarrierConfigManager;
import android.telephony.Rlog;
import android.telephony.ServiceState;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.util.NotificationChannelController;

import java.util.HashMap;
import java.util.Map;


/**
 * This contains Carrier specific logic based on the states/events
 * managed in ServiceStateTracker.
@@ -45,19 +50,28 @@ public class CarrierServiceStateTracker extends Handler {
    protected static final int CARRIER_EVENT_VOICE_DEREGISTRATION = CARRIER_EVENT_BASE + 2;
    protected static final int CARRIER_EVENT_DATA_REGISTRATION = CARRIER_EVENT_BASE + 3;
    protected static final int CARRIER_EVENT_DATA_DEREGISTRATION = CARRIER_EVENT_BASE + 4;
    private static final int SHOW_NOTIFICATION = 200;
    private static final int NOTIFICATION_ID = 1000;
    private static final int UNINITIALIZED_DELAY_VALUE = -1;
    private int mDelay = UNINITIALIZED_DELAY_VALUE;
    private Phone mPhone;
    private boolean mIsPhoneRegistered = false;
    private ServiceStateTracker mSST;

    public static final int NOTIFICATION_PREF_NETWORK = 1000;
    public static final int NOTIFICATION_EMERGENCY_NETWORK = 1001;

    private final Map<Integer, NotificationType> mNotificationTypeMap = new HashMap<>();

    public CarrierServiceStateTracker(Phone phone, ServiceStateTracker sst) {
        this.mPhone = phone;
        this.mSST = sst;
        phone.getContext().registerReceiver(mBroadcastReceiver, new IntentFilter(
                CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED));
        registerNotificationTypes();
    }

    private void registerNotificationTypes() {
        mNotificationTypeMap.put(NOTIFICATION_PREF_NETWORK,
                new PrefNetworkNotification(NOTIFICATION_PREF_NETWORK));
        mNotificationTypeMap.put(NOTIFICATION_EMERGENCY_NETWORK,
                new EmergencyNetworkNotification(NOTIFICATION_EMERGENCY_NETWORK));
    }

    @Override
@@ -65,19 +79,22 @@ public class CarrierServiceStateTracker extends Handler {
        switch (msg.what) {
            case CARRIER_EVENT_VOICE_REGISTRATION:
            case CARRIER_EVENT_DATA_REGISTRATION:
                mIsPhoneRegistered = true;
                handleConfigChanges();
                break;
            case CARRIER_EVENT_VOICE_DEREGISTRATION:
            case CARRIER_EVENT_DATA_DEREGISTRATION:
                if (isGlobalModeOrRadioOffOrAirplaneMode() || isPhoneStillRegistered()) {
                if (isRadioOffOrAirplaneMode()) {
                    break;
                }
                mIsPhoneRegistered = false;
                handleConfigChanges();
                break;
            case SHOW_NOTIFICATION:
                sendNotification();
            case NOTIFICATION_EMERGENCY_NETWORK:
            case NOTIFICATION_PREF_NETWORK:
                Rlog.d(LOG_TAG, "sending notification after delay: " + msg.what);
                NotificationType notificationType = mNotificationTypeMap.get(msg.what);
                if (notificationType != null) {
                    sendNotification(notificationType);
                }
                break;
        }
    }
@@ -90,48 +107,97 @@ public class CarrierServiceStateTracker extends Handler {
                || mSST.mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE);
    }

    private boolean isPhoneVoiceRegistered() {
        if (mSST.mSS == null) {
            return true; //something has gone wrong, return true and not show the notification.
        }
        return (mSST.mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE);
    }

    private boolean isPhoneRegisteredForWifiCalling() {
        Rlog.d(LOG_TAG, "isPhoneRegisteredForWifiCalling: " + mPhone.isWifiCallingEnabled());
        return mPhone.isWifiCallingEnabled();
    }

    /**
     * Returns true if the preferred network is set to 'Global' or the radio is off or in
     * Airplane Mode else returns false.
     * Returns true if the radio is off or in Airplane Mode else returns false.
     */
    private boolean isGlobalModeOrRadioOffOrAirplaneMode() {
    @VisibleForTesting
    public boolean isRadioOffOrAirplaneMode() {
        Context context = mPhone.getContext();
        int preferredNetworkSetting = -1;
        int airplaneMode = -1;
        int subId = mPhone.getSubId();
        try {
            preferredNetworkSetting =
                    android.provider.Settings.Global.getInt(context.getContentResolver(),
                            android.provider.Settings.Global.PREFERRED_NETWORK_MODE + subId,
                            Phone.PREFERRED_NT_MODE);
            airplaneMode = Settings.Global.getInt(context.getContentResolver(),
                    Settings.Global.AIRPLANE_MODE_ON, 0);
        } catch (Exception e) {
            Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
            Rlog.e(LOG_TAG, "Unable to get AIRPLACE_MODE_ON.");
            return true;
        }
        return ((preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA) ||
                !mSST.isRadioOn() || (airplaneMode != 0));
        return (!mSST.isRadioOn() || (airplaneMode != 0));
    }

    /**
     * Contains logic to decide when to create/cancel notifications.
     * Returns true if the preferred network is set to 'Global'.
     */
    private void handleConfigChanges() {
        if (mDelay == UNINITIALIZED_DELAY_VALUE) {
            cancelNotification();
            return;
    private boolean isGlobalMode() {
        Context context = mPhone.getContext();
        int preferredNetworkSetting = -1;
        try {
            preferredNetworkSetting =
                    android.provider.Settings.Global.getInt(context.getContentResolver(),
                            android.provider.Settings.Global.PREFERRED_NETWORK_MODE
                                    + mPhone.getSubId(), Phone.PREFERRED_NT_MODE);
        } catch (Exception e) {
            Rlog.e(LOG_TAG, "Unable to get PREFERRED_NETWORK_MODE.");
            return true;
        }
        return (preferredNetworkSetting == RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
    }
        // send a notification if the device is registerd to a network.
        if (mIsPhoneRegistered) {
            cancelNotification();
            Rlog.i(LOG_TAG, "canceling all notifications. ");

    private void handleConfigChanges() {
        for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
            NotificationType notificationType = entry.getValue();
            if (evaluateSendingMessage(notificationType)) {
                Message notificationMsg = obtainMessage(notificationType.getTypeId(), null);
                Rlog.i(LOG_TAG, "starting timer for notifications." + notificationType.getTypeId());
                sendMessageDelayed(notificationMsg, getDelay(notificationType));
            } else {
            Message notificationMsg;
            notificationMsg = obtainMessage(SHOW_NOTIFICATION, null);
            Rlog.i(LOG_TAG, "starting timer for notifications. ");
            sendMessageDelayed(notificationMsg, mDelay);
                cancelNotification(notificationType.getTypeId());
                Rlog.i(LOG_TAG, "canceling notifications: " + notificationType.getTypeId());
            }
        }
    }

    /**
     * This method adds a level of indirection, and was created so we can unit the class.
     **/
    @VisibleForTesting
    public boolean evaluateSendingMessage(NotificationType notificationType) {
        return notificationType.sendMessage();
    }

    /**
     * This method adds a level of indirection, and was created so we can unit the class.
     **/
    @VisibleForTesting
    public int getDelay(NotificationType notificationType) {
        return notificationType.getDelay();
    }

    /**
     * This method adds a level of indirection, and was created so we can unit the class.
     **/
    @VisibleForTesting
    public Notification.Builder getNotificationBuilder(NotificationType notificationType) {
        return notificationType.getNotificationBuilder();
    }

    /**
     * This method adds a level of indirection, and was created so we can unit the class.
     **/
    @VisibleForTesting
    public NotificationManager getNotificationManager(Context context) {
        return (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
    }

    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -139,9 +205,12 @@ public class CarrierServiceStateTracker extends Handler {
        public void onReceive(Context context, Intent intent) {
            CarrierConfigManager carrierConfigManager = (CarrierConfigManager)
                    context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
            PersistableBundle b = carrierConfigManager.getConfig();
            mDelay = b.getInt(CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
            Rlog.i(LOG_TAG, "reading time to delay notification: " + mDelay);
            PersistableBundle b = carrierConfigManager.getConfigForSubId(mPhone.getSubId());

            for (Map.Entry<Integer, NotificationType> entry : mNotificationTypeMap.entrySet()) {
                NotificationType notificationType = entry.getValue();
                notificationType.setDelay(b);
            }
            handleConfigChanges();
        }
    };
@@ -149,56 +218,195 @@ public class CarrierServiceStateTracker extends Handler {
    /**
     * Post a notification to the NotificationManager for changing network type.
     */
    private void sendNotification() {
    @VisibleForTesting
    public void sendNotification(NotificationType notificationType) {
        if (!evaluateSendingMessage(notificationType)) {
            return;
        }

        Context context = mPhone.getContext();
        Notification.Builder builder = getNotificationBuilder(notificationType);
        // set some common attributes
        builder.setWhen(System.currentTimeMillis())
                .setAutoCancel(true)
                .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
                .setColor(context.getResources().getColor(
                       com.android.internal.R.color.system_notification_accent_color));

        Rlog.i(LOG_TAG, "w/values: " + "," + mIsPhoneRegistered + "," + mDelay
                + "," + isGlobalModeOrRadioOffOrAirplaneMode() + "," + mSST.isRadioOn());
        getNotificationManager(context).notify(notificationType.getTypeId(), builder.build());
    }

        // exit if the network preference is set to Global or if the phone is registered.
        if (isGlobalModeOrRadioOffOrAirplaneMode() || mIsPhoneRegistered) {
    /**
     * Cancel notifications if a registration is pending or has been sent.
     **/
    public void cancelNotification(int notificationId) {
        Context context = mPhone.getContext();
        removeMessages(notificationId);
        getNotificationManager(context).cancel(notificationId);
    }

    /**
     * Class that defines the different types of notifications.
     */
    public interface NotificationType {

        /**
         * decides if the message should be sent, Returns boolean
         **/
        boolean sendMessage();

        /**
         * returns the interval by which the message is delayed.
         **/
        int getDelay();

        /** sets the interval by which the message is delayed.
         * @param bundle PersistableBundle
        **/
        void setDelay(PersistableBundle bundle);

        /**
         * returns notification type id.
         **/
        int getTypeId();

        /**
         * returns the notification builder, for the notification to be displayed.
         **/
        Notification.Builder getNotificationBuilder();
    }

    /**
     * Class that defines the network notification, which is shown when the phone cannot camp on
     * a network, and has 'preferred mode' set to global.
     */
    public class PrefNetworkNotification implements NotificationType {

        private final int mTypeId;
        private int mDelay = UNINITIALIZED_DELAY_VALUE;

        PrefNetworkNotification(int typeId) {
            this.mTypeId = typeId;
        }

        /** sets the interval by which the message is delayed.
         * @param bundle PersistableBundle
         **/
        public void setDelay(PersistableBundle bundle) {
            if (bundle == null) {
                Rlog.e(LOG_TAG, "bundle is null");
                return;
            }
            this.mDelay = bundle.getInt(
                    CarrierConfigManager.KEY_PREF_NETWORK_NOTIFICATION_DELAY_INT);
            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
        }

        NotificationManager notificationManager = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);
        public int getDelay() {
            return mDelay;
        }

        public int getTypeId() {
            return mTypeId;
        }

        /**
         * Contains logic on sending notifications.
         */
        public boolean sendMessage() {
            Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
                    + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
                    + "," + mSST.isRadioOn());
            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered()
                    || isGlobalMode()) {
                return false;
            }
            return true;
        }

        /**
         * Builds a partial notificaiton builder, and returns it.
         */
        public Notification.Builder getNotificationBuilder() {
            Context context = mPhone.getContext();
            Intent notificationIntent = new Intent(Settings.ACTION_DATA_ROAMING_SETTINGS);
            PendingIntent settingsIntent = PendingIntent.getActivity(context, 0, notificationIntent,
                    PendingIntent.FLAG_ONE_SHOT);

        CharSequence title =
                context.getText(com.android.internal.R.string.NetworkPreferenceSwitchTitle);
        CharSequence details =
                context.getText(com.android.internal.R.string.NetworkPreferenceSwitchSummary);


        Notification mNotification = new Notification.Builder(context)
                .setWhen(System.currentTimeMillis())
                .setAutoCancel(true)
                .setSmallIcon(com.android.internal.R.drawable.stat_sys_warning)
            CharSequence title = context.getText(
                    com.android.internal.R.string.NetworkPreferenceSwitchTitle);
            CharSequence details = context.getText(
                    com.android.internal.R.string.NetworkPreferenceSwitchSummary);
            return new Notification.Builder(context)
                    .setContentTitle(title)
                .setColor(context.getResources().getColor(
                        com.android.internal.R.color.system_notification_accent_color))
                    .setStyle(new Notification.BigTextStyle().bigText(details))
                    .setContentText(details)
                .setContentIntent(settingsIntent)
                    .setChannel(NotificationChannelController.CHANNEL_ID_ALERT)
                .build();
                    .setContentIntent(settingsIntent);
        }
    }

    /**
     * Class that defines the emergency notification, which is shown when the user is out of cell
     * connectivity, but has wifi enabled.
     */
    public class EmergencyNetworkNotification implements NotificationType {

        private final int mTypeId;
        private int mDelay = UNINITIALIZED_DELAY_VALUE;

        notificationManager.notify(NOTIFICATION_ID, mNotification);
        EmergencyNetworkNotification(int typeId) {
            this.mTypeId = typeId;
        }

        /** sets the interval by which the message is delayed.
         * @param bundle PersistableBundle
         **/
        public void setDelay(PersistableBundle bundle) {
            if (bundle == null) {
                Rlog.e(LOG_TAG, "bundle is null");
                return;
            }
            this.mDelay = bundle.getInt(
                    CarrierConfigManager.KEY_EMERGENCY_NOTIFICATION_DELAY_INT);
            Rlog.i(LOG_TAG, "reading time to delay notification emergency: " + mDelay);
        }

        public int getDelay() {
            return mDelay;
        }

        public int getTypeId() {
            return mTypeId;
        }

        /**
     * Cancel notifications if a registration is pending or has been sent.
         * Contains logic on sending notifications,
         */
    private void cancelNotification() {
        public boolean sendMessage() {
            Rlog.i(LOG_TAG, "EmergencyNetworkNotification: sendMessage() w/values: "
                    + "," + isPhoneVoiceRegistered() + "," + mDelay + ","
                    + isPhoneRegisteredForWifiCalling() + "," + mSST.isRadioOn());
            if (mDelay == UNINITIALIZED_DELAY_VALUE || isPhoneVoiceRegistered()
                    || !isPhoneRegisteredForWifiCalling()) {
                return false;
            }
            return true;
        }

        /**
         * Builds a partial notificaiton builder, and returns it.
         */
        public Notification.Builder getNotificationBuilder() {
            Context context = mPhone.getContext();
        mIsPhoneRegistered = true;
        removeMessages(SHOW_NOTIFICATION);
        NotificationManager notificationManager = (NotificationManager)
                context.getSystemService(Context.NOTIFICATION_SERVICE);
        notificationManager.cancel(NOTIFICATION_ID);
            CharSequence title = context.getText(
                    com.android.internal.R.string.EmergencyCallWarningTitle);
            CharSequence details = context.getText(
                    com.android.internal.R.string.EmergencyCallWarningSummary);
            return new Notification.Builder(context)
                    .setContentTitle(title)
                    .setStyle(new Notification.BigTextStyle().bigText(details))
                    .setContentText(details)
                    .setChannel(NotificationChannelController.CHANNEL_ID_WFC);
        }
    }
}
+128 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2017 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.internal.telephony;

import android.app.Notification;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.Resources;
import android.os.HandlerThread;
import android.os.Message;
import android.test.suitebuilder.annotation.SmallTest;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isA;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

/**
 * Unit tests for {@link com.android.internal.telephony.CarrierServiceStateTracker}.
 */
@SmallTest
public class CarrierServiceStateTrackerTest extends TelephonyTest {
    public static final String LOG_TAG = "CSST";
    public static final int TEST_TIMEOUT = 5000;

    private CarrierServiceStateTracker mCarrierSST;
    private CarrierServiceStateTrackerTestHandler mCarrierServiceStateTrackerTestHandler;
    private  CarrierServiceStateTracker.PrefNetworkNotification mPrefNetworkNotification;
    private  CarrierServiceStateTracker.EmergencyNetworkNotification mEmergencyNetworkNotification;

    @Mock Context mContext;
    @Mock ServiceStateTracker mServiceStateTracker;
    @Mock NotificationManager mNotificationManager;
    @Mock Resources mResources;

    private class CarrierServiceStateTrackerTestHandler extends HandlerThread {

        private CarrierServiceStateTrackerTestHandler(String name) {
            super(name);
        }

        @Override
        public void onLooperPrepared() {
            mCarrierSST = spy(new CarrierServiceStateTracker(mPhone, mServiceStateTracker));
            setReady(true);
        }
    }

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        logd(LOG_TAG + "Setup!");
        super.setUp(getClass().getSimpleName());
        mCarrierServiceStateTrackerTestHandler =
                new CarrierServiceStateTrackerTestHandler(getClass().getSimpleName());
        mCarrierServiceStateTrackerTestHandler.start();
        when(mContext.getResources()).thenReturn(mResources);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mContext.getApplicationInfo()).thenReturn(new ApplicationInfo());
        waitUntilReady();
    }

    @After
    public void tearDown() throws Exception {
        mCarrierServiceStateTrackerTestHandler.quit();
        super.tearDown();
    }

    @Test
    @SmallTest
    public void testCancelBothNotifications() {
        logd(LOG_TAG + ":testCancelBothNotifications()");
        Message notificationMsg = mCarrierSST.obtainMessage(
                CarrierServiceStateTracker.CARRIER_EVENT_DATA_REGISTRATION, null);
        doReturn(false).when(mCarrierSST).evaluateSendingMessage(any());
        doReturn(mNotificationManager).when(mCarrierSST).getNotificationManager(any());
        mCarrierSST.handleMessage(notificationMsg);
        waitForHandlerAction(mCarrierSST, TEST_TIMEOUT);
        verify(mNotificationManager).cancel(
                CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK);
        verify(mNotificationManager).cancel(
                CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK);
    }

    @Test
    @SmallTest
    public void testSendBothNotifications() {
        logd(LOG_TAG + ":testSendBothNotifications()");
        Notification.Builder mNotificationBuilder = new Notification.Builder(mContext);
        Message notificationMsg = mCarrierSST.obtainMessage(
                CarrierServiceStateTracker.CARRIER_EVENT_DATA_DEREGISTRATION, null);
        doReturn(true).when(mCarrierSST).evaluateSendingMessage(any());
        doReturn(false).when(mCarrierSST).isRadioOffOrAirplaneMode();
        doReturn(0).when(mCarrierSST).getDelay(any());
        doReturn(mNotificationBuilder).when(mCarrierSST).getNotificationBuilder(any());
        doReturn(mNotificationManager).when(mCarrierSST).getNotificationManager(any());
        mCarrierSST.handleMessage(notificationMsg);
        waitForHandlerAction(mCarrierSST, TEST_TIMEOUT);
        verify(mNotificationManager).notify(
                eq(CarrierServiceStateTracker.NOTIFICATION_PREF_NETWORK), isA(Notification.class));
        verify(mNotificationManager).notify(
                eq(CarrierServiceStateTracker.NOTIFICATION_EMERGENCY_NETWORK), any());
    }
}