DO NOT MERGE Add premium SMS access settings.

Premium SMS has an overlayable config value for easily turning off the entry point for OEMs who choose not to support the feature.

Bug: 122824071

Test: build and deploy, RunCarSettingsRoboTests
Change-Id: Iaa58b080397c47e0cdef1f1b3a06abbcbfb2f2af
diff --git a/Android.mk b/Android.mk
index 1fd2fc6..2e19d00 100644
--- a/Android.mk
+++ b/Android.mk
@@ -29,7 +29,8 @@
   LOCAL_USE_AAPT2 := true
 
   LOCAL_JAVA_LIBRARIES := \
-      android.car
+      android.car \
+      telephony-common
 
   LOCAL_STATIC_ANDROID_LIBRARIES := \
       androidx.car_car \
@@ -89,7 +90,8 @@
   LOCAL_USE_AAPT2 := true
 
   LOCAL_JAVA_LIBRARIES := \
-      android.car
+      android.car \
+      telephony-common
 
   LOCAL_STATIC_ANDROID_LIBRARIES := \
       androidx.car_car \
diff --git a/res/values/arrays.xml b/res/values/arrays.xml
new file mode 100644
index 0000000..62cb3be
--- /dev/null
+++ b/res/values/arrays.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 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.
+-->
+
+<resources>
+
+    <!-- Values for premium SMS permission selector [CHAR LIMIT=30] -->
+    <string-array name="premium_sms_access_values">
+        <!-- Ask user before sending to premium SMS short code. -->
+        <item>Ask</item>
+        <!-- Never allow app to send to premium SMS short code. -->
+        <item>Never allow</item>
+        <!-- Always allow app to send to premium SMS short code. -->
+        <item>Always allow</item>
+    </string-array>
+
+</resources>
diff --git a/res/values/config.xml b/res/values/config.xml
index 1bfa3dc..21b7908 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -24,6 +24,8 @@
     <bool name="config_show_wifi_mac_address">true</bool>
     <!-- Whether to show regulatory info or not. -->
     <bool name="config_show_regulatory_info">true</bool>
+    <!-- Whether premium SMS should be shown or not. -->
+    <bool name="config_show_premium_sms">true</bool>
 
     <!-- The component which listens for the enabling of developer options. -->
     <string name="config_dev_options_module" translatable="false">com.android.car.developeroptions/.Settings$DevelopmentSettingsDashboardActivity</string>
diff --git a/res/values/preference_keys.xml b/res/values/preference_keys.xml
index 7a34f12..2690118 100644
--- a/res/values/preference_keys.xml
+++ b/res/values/preference_keys.xml
@@ -180,6 +180,12 @@
     <string name="pk_notification_access_entry" translatable="false">notification_access_entry
     </string>
     <string name="pk_notification_access" translatable="false">notification_access</string>
+    <string name="pk_premium_sms_access_entry" translatable="false">premium_sms_access_entry
+    </string>
+    <string name="pk_premium_sms_access" translatable="false">premium_sms_access</string>
+    <string name="pk_premium_sms_access_description" translatable="false">
+        premium_sms_access_description
+    </string>
     <string name="pk_usage_access_entry" translatable="false">usage_access_entry</string>
     <string name="pk_usage_access" translatable="false">usage_access</string>
     <string name="pk_usage_access_description" translatable="false">usage_access_description
diff --git a/res/values/strings.xml b/res/values/strings.xml
index c5e510d..fa4c4e8 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -499,6 +499,10 @@
     <string name="notification_listener_revoke_warning_confirm">Turn off</string>
     <!-- Negative button for a dialog warning message about revoking notification listener access. [CHAR_LIMIT=30] -->
     <string name="notification_listener_revoke_warning_cancel">Cancel</string>
+    <!-- Title for screen controlling which apps have access to send premium sms messages. [CHAR_LIMIT=60] -->
+    <string name="premium_sms_access_title">Premium SMS access</string>
+    <!-- Message shown for option to enable Premium SMS for an app. [CHAR_LIMIT=NONE] -->
+    <string name="premium_sms_access_description">Premium SMS may cost you money and will add up to your carrier bills. If you enable permission for an app, you will be able to send premium SMS using that app.</string>
     <!-- Title for managing apps which can query usage data. [CHAR_LIMIT=30] -->
     <string name="usage_access_title">Usage access</string>
     <!-- Description of the usage access permission. [CHAR_LIMIT=NONE] -->
diff --git a/res/xml/premium_sms_access_fragment.xml b/res/xml/premium_sms_access_fragment.xml
new file mode 100644
index 0000000..829cfd9
--- /dev/null
+++ b/res/xml/premium_sms_access_fragment.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+    Copyright 2019 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.
+-->
+
+<PreferenceScreen
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:title="@string/premium_sms_access_title">
+    <Preference
+        android:key="@string/pk_premium_sms_access_description"
+        android:selectable="false"
+        android:summary="@string/premium_sms_access_description"/>
+    <com.android.car.settings.common.LogicalPreferenceGroup
+        android:key="@string/pk_premium_sms_access"
+        settings:controller="com.android.car.settings.applications.specialaccess.PremiumSmsAccessPreferenceController"/>
+</PreferenceScreen>
diff --git a/res/xml/special_access_fragment.xml b/res/xml/special_access_fragment.xml
index 33c1706..a9abce1 100644
--- a/res/xml/special_access_fragment.xml
+++ b/res/xml/special_access_fragment.xml
@@ -30,6 +30,11 @@
         android:title="@string/notification_access_title"
         settings:controller="com.android.car.settings.common.DefaultRestrictionsPreferenceController"/>
     <Preference
+        android:fragment="com.android.car.settings.applications.specialaccess.PremiumSmsAccessFragment"
+        android:key="@string/pk_premium_sms_access_entry"
+        android:title="@string/premium_sms_access_title"
+        settings:controller="com.android.car.settings.applications.specialaccess.PremiumSmsAccessEntryPreferenceController"/>
+    <Preference
         android:fragment="com.android.car.settings.applications.specialaccess.UsageAccessFragment"
         android:key="@string/pk_usage_access_entry"
         android:title="@string/usage_access_title"
diff --git a/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridge.java b/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridge.java
new file mode 100644
index 0000000..b13fedb
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridge.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2019 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.car.settings.applications.specialaccess;
+
+import android.os.RemoteException;
+
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState;
+
+import java.util.List;
+
+/**
+ * Bridges the value of {@link ISms#getPremiumSmsPermission(String)} into the {@link
+ * ApplicationsState.AppEntry#extraInfo} for each entry's package name.
+ */
+public class AppStatePremiumSmsBridge implements AppEntryListManager.ExtraInfoBridge {
+
+    private final ISms mSmsManager;
+
+    public AppStatePremiumSmsBridge(ISms smsManager) {
+        mSmsManager = smsManager;
+    }
+
+    @Override
+    public void loadExtraInfo(List<ApplicationsState.AppEntry> entries) {
+        for (ApplicationsState.AppEntry entry : entries) {
+            entry.extraInfo = getSmsState(entry.info.packageName);
+        }
+    }
+
+    private int getSmsState(String packageName) {
+        try {
+            return mSmsManager.getPremiumSmsPermission(packageName);
+        } catch (RemoteException e) {
+            return SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN;
+        }
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessEntryPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessEntryPreferenceController.java
new file mode 100644
index 0000000..3270b0e
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessEntryPreferenceController.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2019 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.car.settings.applications.specialaccess;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+
+import androidx.preference.Preference;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.PreferenceController;
+
+/**
+ * Controller for the entry point to premium SMS access settings. It reads an overlayable config
+ * value to determine if premium SMS is supported and returns an appropriate availability status.
+ */
+public class PremiumSmsAccessEntryPreferenceController extends PreferenceController<Preference> {
+
+    public PremiumSmsAccessEntryPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+    }
+
+    @Override
+    protected Class<Preference> getPreferenceType() {
+        return Preference.class;
+    }
+
+    @Override
+    protected int getAvailabilityStatus() {
+        return getContext().getResources().getBoolean(R.bool.config_show_premium_sms) ? AVAILABLE
+                : UNSUPPORTED_ON_DEVICE;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessFragment.java b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessFragment.java
new file mode 100644
index 0000000..d485581
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessFragment.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2019 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.car.settings.applications.specialaccess;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.SettingsFragment;
+
+/** Displays controls for managing premium SMS access. */
+public class PremiumSmsAccessFragment extends SettingsFragment {
+
+    @Override
+    @XmlRes
+    protected int getPreferenceScreenResId() {
+        return R.xml.premium_sms_access_fragment;
+    }
+}
diff --git a/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceController.java b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceController.java
new file mode 100644
index 0000000..b62f9d7
--- /dev/null
+++ b/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceController.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2019 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.car.settings.applications.specialaccess;
+
+import android.app.Application;
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.R;
+import com.android.car.settings.common.FragmentController;
+import com.android.car.settings.common.Logger;
+import com.android.car.settings.common.PreferenceController;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+import com.android.settingslib.applications.ApplicationsState.AppFilter;
+
+import java.util.List;
+
+/**
+ * Displays the list of apps which have a known premium SMS access state. When a user selects an
+ * app, they are shown a dialog which allows them to configure the state to one of:
+ *
+ * <ul>
+ * <li>Ask - the user will be prompted before app sends premium SMS.
+ * <li>Never allow - app can never send premium SMS.
+ * <li>Always allow - app can automatically send premium SMS.
+ * </ul>
+ */
+public class PremiumSmsAccessPreferenceController extends PreferenceController<PreferenceGroup> {
+
+    private static final Logger LOG = new Logger(PremiumSmsAccessPreferenceController.class);
+
+    private static final AppFilter FILTER_SMS_STATE_KNOWN = new AppFilter() {
+        @Override
+        public void init() {
+            // No op.
+        }
+
+        @Override
+        public boolean filterApp(AppEntry info) {
+            return info.extraInfo != null
+                    && (Integer) info.extraInfo != SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN;
+        }
+    };
+
+    private final ISms mSmsManager;
+    private final ApplicationsState mApplicationsState;
+
+    private final Preference.OnPreferenceChangeListener mOnPreferenceChangeListener =
+            new Preference.OnPreferenceChangeListener() {
+                @Override
+                public boolean onPreferenceChange(Preference preference, Object newValue) {
+                    PremiumSmsPreference appPreference = (PremiumSmsPreference) preference;
+                    AppEntry entry = appPreference.mEntry;
+                    int smsState = Integer.parseInt((String) newValue);
+                    if (smsState != (Integer) entry.extraInfo) {
+                        try {
+                            mSmsManager.setPremiumSmsPermission(entry.info.packageName, smsState);
+                        } catch (RemoteException e) {
+                            LOG.w("Unable to set premium sms permission for "
+                                    + entry.info.packageName + " " + entry.info.uid, e);
+                            return false;
+                        }
+                        // Update the extra info of this entry so that it reflects the new state.
+                        mAppEntryListManager.forceUpdate(entry);
+                        return true;
+                    }
+                    return false;
+                }
+            };
+
+    private final AppEntryListManager.Callback mCallback = new AppEntryListManager.Callback() {
+        @Override
+        public void onAppEntryListChanged(List<AppEntry> entries) {
+            mEntries = entries;
+            refreshUi();
+        }
+    };
+
+    @VisibleForTesting
+    AppEntryListManager mAppEntryListManager;
+    private List<AppEntry> mEntries;
+
+    public PremiumSmsAccessPreferenceController(Context context, String preferenceKey,
+            FragmentController fragmentController, CarUxRestrictions uxRestrictions) {
+        super(context, preferenceKey, fragmentController, uxRestrictions);
+        mSmsManager = ISms.Stub.asInterface(ServiceManager.getService("isms"));
+        mApplicationsState = ApplicationsState.getInstance(
+                (Application) context.getApplicationContext());
+        mAppEntryListManager = new AppEntryListManager(context);
+    }
+
+    @Override
+    protected Class<PreferenceGroup> getPreferenceType() {
+        return PreferenceGroup.class;
+    }
+
+    @Override
+    protected void onCreateInternal() {
+        mAppEntryListManager.init(new AppStatePremiumSmsBridge(mSmsManager),
+                () -> FILTER_SMS_STATE_KNOWN, mCallback);
+    }
+
+    @Override
+    protected void onStartInternal() {
+        mAppEntryListManager.start();
+    }
+
+    @Override
+    protected void onStopInternal() {
+        mAppEntryListManager.stop();
+    }
+
+    @Override
+    protected void onDestroyInternal() {
+        mAppEntryListManager.destroy();
+    }
+
+    @Override
+    protected void updateState(PreferenceGroup preference) {
+        if (mEntries == null) {
+            // Still loading.
+            return;
+        }
+        preference.removeAll();
+        for (AppEntry entry : mEntries) {
+            mApplicationsState.ensureIcon(entry);
+            Preference appPreference = new PremiumSmsPreference(getContext(), entry);
+            appPreference.setOnPreferenceChangeListener(mOnPreferenceChangeListener);
+            preference.addPreference(appPreference);
+        }
+    }
+
+    private static class PremiumSmsPreference extends ListPreference {
+
+        private final AppEntry mEntry;
+
+        PremiumSmsPreference(Context context, AppEntry entry) {
+            super(context);
+            String key = entry.info.packageName + "|" + entry.info.uid;
+            setKey(key);
+            setTitle(entry.label);
+            setIcon(entry.icon);
+            setPersistent(false);
+            setEntries(R.array.premium_sms_access_values);
+            setEntryValues(new CharSequence[]{
+                    String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER),
+                    String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW),
+                    String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW)
+            });
+            setValue(String.valueOf(entry.extraInfo));
+            setSummary("%s");
+            mEntry = entry;
+        }
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridgeTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridgeTest.java
new file mode 100644
index 0000000..382917c
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/AppStatePremiumSmsBridgeTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2019 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.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.content.pm.ApplicationInfo;
+import android.os.RemoteException;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+/** Unit test for {@link AppStatePremiumSmsBridge}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class AppStatePremiumSmsBridgeTest {
+
+    @Mock
+    private ISms mSmsManager;
+    private AppStatePremiumSmsBridge mBridge;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mBridge = new AppStatePremiumSmsBridge(mSmsManager);
+    }
+
+    @Test
+    public void loadExtraInfo() throws RemoteException {
+        String package1 = "test.package1";
+        AppEntry appEntry1 = createAppEntry(package1);
+        int value1 = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW;
+        when(mSmsManager.getPremiumSmsPermission(package1)).thenReturn(value1);
+
+        String package2 = "test.package2";
+        AppEntry appEntry2 = createAppEntry(package2);
+        int value2 = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW;
+        when(mSmsManager.getPremiumSmsPermission(package2)).thenReturn(value2);
+
+        mBridge.loadExtraInfo(Arrays.asList(appEntry1, appEntry2));
+
+        assertThat(appEntry1.extraInfo).isEqualTo(value1);
+        assertThat(appEntry2.extraInfo).isEqualTo(value2);
+    }
+
+    private AppEntry createAppEntry(String packageName) {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+
+        AppEntry appEntry = mock(AppEntry.class);
+        appEntry.info = info;
+        appEntry.label = packageName;
+
+        return appEntry;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceControllerTest.java
new file mode 100644
index 0000000..38c1678
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/applications/specialaccess/PremiumSmsAccessPreferenceControllerTest.java
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2019 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.car.settings.applications.specialaccess;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.Looper;
+import android.os.RemoteException;
+
+import androidx.lifecycle.Lifecycle;
+import androidx.preference.ListPreference;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceGroup;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.common.LogicalPreferenceGroup;
+import com.android.car.settings.common.PreferenceControllerTestHelper;
+import com.android.car.settings.testutils.ShadowApplicationsState;
+import com.android.car.settings.testutils.ShadowISms;
+import com.android.internal.telephony.ISms;
+import com.android.internal.telephony.SmsUsageMonitor;
+import com.android.settingslib.applications.ApplicationsState;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** Unit test for {@link PremiumSmsAccessPreferenceController}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+@Config(shadows = {ShadowApplicationsState.class, ShadowISms.class})
+public class PremiumSmsAccessPreferenceControllerTest {
+
+    @Mock
+    private AppEntryListManager mAppEntryListManager;
+    @Mock
+    private ApplicationsState mApplicationsState;
+    @Mock
+    private ISms mISms;
+    @Captor
+    private ArgumentCaptor<AppEntryListManager.Callback> mCallbackCaptor;
+
+    private PreferenceGroup mPreferenceGroup;
+    private PreferenceControllerTestHelper<PremiumSmsAccessPreferenceController> mControllerHelper;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        ShadowApplicationsState.setInstance(mApplicationsState);
+        when(mApplicationsState.getBackgroundLooper()).thenReturn(Looper.getMainLooper());
+        ShadowISms.setISms(mISms);
+
+        Context context = RuntimeEnvironment.application;
+        mPreferenceGroup = new LogicalPreferenceGroup(context);
+        mControllerHelper = new PreferenceControllerTestHelper<>(context,
+                PremiumSmsAccessPreferenceController.class, mPreferenceGroup);
+        mControllerHelper.getController().mAppEntryListManager = mAppEntryListManager;
+        mControllerHelper.markState(Lifecycle.State.CREATED);
+        verify(mAppEntryListManager).init(any(AppStatePremiumSmsBridge.class), any(),
+                mCallbackCaptor.capture());
+    }
+
+    @After
+    public void tearDown() {
+        ShadowApplicationsState.reset();
+        ShadowISms.reset();
+    }
+
+    @Test
+    public void onStart_startsListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_START);
+
+        verify(mAppEntryListManager).start();
+    }
+
+    @Test
+    public void onStop_stopsListManager() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_STOP);
+
+        verify(mAppEntryListManager).stop();
+    }
+
+    @Test
+    public void onDestroy_destroysListManager() {
+        mControllerHelper.sendLifecycleEvent(Lifecycle.Event.ON_DESTROY);
+
+        verify(mAppEntryListManager).destroy();
+    }
+
+    @Test
+    public void onAppEntryListChanged_addsPreferencesForEntries() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<AppEntry> entries = Arrays.asList(
+                createAppEntry("test.package", /* uid= */ 1,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW),
+                createAppEntry("another.test.package", /* uid= */ 2,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+
+        assertThat(mPreferenceGroup.getPreferenceCount()).isEqualTo(2);
+        assertThat(((ListPreference) mPreferenceGroup.getPreference(0)).getValue()).isEqualTo(
+                String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ALWAYS_ALLOW));
+        assertThat(((ListPreference) mPreferenceGroup.getPreference(1)).getValue()).isEqualTo(
+                String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+    }
+
+    @Test
+    public void onPreferenceChange_setsPremiumSmsPermission() throws RemoteException {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        String packageName = "test.package";
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry(packageName, /* uid= */ 1,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        Preference appPref = mPreferenceGroup.getPreference(0);
+        int updatedValue = SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER;
+
+        appPref.getOnPreferenceChangeListener().onPreferenceChange(appPref,
+                String.valueOf(updatedValue));
+
+        verify(mISms).setPremiumSmsPermission(packageName, updatedValue);
+    }
+
+    @Test
+    public void onPreferenceChange_updatesEntry() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        List<AppEntry> entries = Collections.singletonList(
+                createAppEntry("test.package", /* uid= */ 1,
+                        SmsUsageMonitor.PREMIUM_SMS_PERMISSION_NEVER_ALLOW));
+        mCallbackCaptor.getValue().onAppEntryListChanged(entries);
+        Preference appPref = mPreferenceGroup.getPreference(0);
+
+        appPref.getOnPreferenceChangeListener().onPreferenceChange(appPref,
+                String.valueOf(SmsUsageMonitor.PREMIUM_SMS_PERMISSION_ASK_USER));
+
+        verify(mAppEntryListManager).forceUpdate(entries.get(0));
+    }
+
+    @Test
+    public void appFilter_removesUnknownStates() {
+        mControllerHelper.markState(Lifecycle.State.STARTED);
+        ArgumentCaptor<AppEntryListManager.AppFilterProvider> filterCaptor =
+                ArgumentCaptor.forClass(AppEntryListManager.AppFilterProvider.class);
+        verify(mAppEntryListManager).init(any(), filterCaptor.capture(), any());
+        ApplicationsState.AppFilter filter = filterCaptor.getValue().getAppFilter();
+        AppEntry unknownStateApp = createAppEntry("test.package", /* uid= */ 1,
+                SmsUsageMonitor.PREMIUM_SMS_PERMISSION_UNKNOWN);
+
+        assertThat(filter.filterApp(unknownStateApp)).isFalse();
+    }
+
+    private AppEntry createAppEntry(String packageName, int uid, int smsState) {
+        ApplicationInfo info = new ApplicationInfo();
+        info.packageName = packageName;
+        info.uid = uid;
+
+        AppEntry appEntry = mock(AppEntry.class);
+        appEntry.info = info;
+        appEntry.label = packageName;
+        appEntry.extraInfo = smsState;
+
+        return appEntry;
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/testutils/ShadowISms.java b/tests/robotests/src/com/android/car/settings/testutils/ShadowISms.java
new file mode 100644
index 0000000..17a5073
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/testutils/ShadowISms.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2019 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.car.settings.testutils;
+
+import android.os.IBinder;
+
+import com.android.internal.telephony.ISms;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.annotation.Resetter;
+
+@Implements(value = ISms.Stub.class)
+public class ShadowISms {
+
+    private static ISms sISms;
+
+    @Resetter
+    public static void reset() {
+        sISms = null;
+    }
+
+    public static void setISms(ISms iSms) {
+        sISms = iSms;
+    }
+
+    @Implementation
+    protected static ISms asInterface(IBinder obj) {
+        return sISms;
+    }
+}