Merge "Fix typo and update char limit." into pi-car-dev
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 9377ac7..cfa2779 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/src/com/android/car/settings/common/BaseCarSettingsActivity.java b/src/com/android/car/settings/common/BaseCarSettingsActivity.java
new file mode 100644
index 0000000..132ee60
--- /dev/null
+++ b/src/com/android/car/settings/common/BaseCarSettingsActivity.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 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.common;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.android.car.apps.common.util.Themes;
+import com.android.car.settings.R;
+
+/**
+ * Base activity class for car settings, provides a action bar with a back button that goes to
+ * previous activity.
+ */
+public abstract class BaseCarSettingsActivity extends FragmentActivity implements
+        FragmentController, OnUxRestrictionsChangedListener, UxRestrictionsProvider,
+        OnBackStackChangedListener, PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+    private static final Logger LOG = new Logger(BaseCarSettingsActivity.class);
+
+    private CarUxRestrictionsHelper mUxRestrictionsHelper;
+    private View mRestrictedMessage;
+    // Default to minimum restriction.
+    private CarUxRestrictions mCarUxRestrictions = new CarUxRestrictions.Builder(
+            /* reqOpt= */ true,
+            CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
+            /* timestamp= */ 0
+    ).build();
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.car_setting_activity);
+        if (mUxRestrictionsHelper == null) {
+            mUxRestrictionsHelper = new CarUxRestrictionsHelper(/* context= */ this, /* listener= */
+                    this);
+        }
+        mUxRestrictionsHelper.start();
+        getSupportFragmentManager().addOnBackStackChangedListener(this);
+        mRestrictedMessage = findViewById(R.id.restricted_message);
+
+        launchIfDifferent(getInitialFragment());
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        mUxRestrictionsHelper.stop();
+        mUxRestrictionsHelper = null;
+    }
+
+    @Override
+    public void onBackPressed() {
+        super.onBackPressed();
+        hideKeyboard();
+        // If the backstack is empty, finish the activity.
+        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
+            finish();
+        }
+    }
+
+    @Override
+    public void launchFragment(Fragment fragment) {
+        if (fragment instanceof DialogFragment) {
+            throw new IllegalArgumentException(
+                    "cannot launch dialogs with launchFragment() - use showDialog() instead");
+        }
+
+        getSupportFragmentManager()
+                .beginTransaction()
+                .setCustomAnimations(
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentOpenEnterAnimation),
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentOpenExitAnimation),
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentCloseEnterAnimation),
+                        Themes.getAttrResourceId(/* context= */ this,
+                                android.R.attr.fragmentCloseExitAnimation))
+                .replace(R.id.fragment_container, fragment,
+                        Integer.toString(getSupportFragmentManager().getBackStackEntryCount()))
+                .addToBackStack(null)
+                .commit();
+    }
+
+    @Override
+    public void goBack() {
+        onBackPressed();
+    }
+
+    @Override
+    public void showBlockingMessage() {
+        Toast.makeText(this, R.string.restricted_while_driving, Toast.LENGTH_SHORT).show();
+    }
+
+    @Override
+    public void showDialog(DialogFragment dialogFragment, @Nullable String tag) {
+        dialogFragment.show(getSupportFragmentManager(), tag);
+    }
+
+    @Override
+    @Nullable
+    public DialogFragment findDialogByTag(String tag) {
+        Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
+        if (fragment instanceof DialogFragment) {
+            return (DialogFragment) fragment;
+        }
+        return null;
+    }
+
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode,
+            ActivityResultCallback callback) {
+        throw new UnsupportedOperationException(
+                "Unimplemented for activities that implement FragmentController");
+    }
+
+    @Override
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, Bundle options,
+            ActivityResultCallback callback) {
+        throw new UnsupportedOperationException(
+                "Unimplemented for activities that implement FragmentController");
+    }
+
+    @Override
+    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
+        mCarUxRestrictions = restrictionInfo;
+        Fragment currentFragment = getCurrentFragment();
+        if (currentFragment instanceof OnUxRestrictionsChangedListener) {
+            ((OnUxRestrictionsChangedListener) currentFragment)
+                    .onUxRestrictionsChanged(restrictionInfo);
+        }
+        updateBlockingView(currentFragment);
+    }
+
+    @Override
+    public CarUxRestrictions getCarUxRestrictions() {
+        return mCarUxRestrictions;
+    }
+
+    @Override
+    public void onBackStackChanged() {
+        updateBlockingView(getCurrentFragment());
+    }
+
+    @Override
+    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
+        if (pref.getFragment() != null) {
+            Fragment fragment = Fragment.instantiate(/* context= */ this, pref.getFragment(),
+                    pref.getExtras());
+            launchFragment(fragment);
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Gets the fragment to show onCreate. If null, the activity will not perform an initial
+     * fragment transaction.
+     */
+    @Nullable
+    protected abstract Fragment getInitialFragment();
+
+    protected void launchIfDifferent(Fragment newFragment) {
+        Fragment currentFragment = getCurrentFragment();
+        if ((newFragment != null) && differentFragment(newFragment, currentFragment)) {
+            LOG.d("launchIfDifferent: " + newFragment + " replacing " + currentFragment);
+            launchFragment(newFragment);
+        }
+    }
+
+    protected Fragment getCurrentFragment() {
+        return getSupportFragmentManager().findFragmentById(R.id.fragment_container);
+    }
+
+    /**
+     * Returns {code true} if newFragment is different from current fragment.
+     */
+    private boolean differentFragment(Fragment newFragment, Fragment currentFragment) {
+        return (currentFragment == null)
+                || (!currentFragment.getClass().equals(newFragment.getClass()));
+    }
+
+    private void hideKeyboard() {
+        InputMethodManager imm = (InputMethodManager) this.getSystemService(
+                Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
+    }
+
+    private void updateBlockingView(@Nullable Fragment currentFragment) {
+        if (currentFragment instanceof BaseFragment) {
+            boolean canBeShown = ((BaseFragment) currentFragment).canBeShown(mCarUxRestrictions);
+            mRestrictedMessage.setVisibility(canBeShown ? View.GONE : View.VISIBLE);
+        }
+    }
+}
diff --git a/src/com/android/car/settings/common/CarSettingActivity.java b/src/com/android/car/settings/common/CarSettingActivity.java
index f7fe3a8..1a33ccc 100644
--- a/src/com/android/car/settings/common/CarSettingActivity.java
+++ b/src/com/android/car/settings/common/CarSettingActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 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.
@@ -11,71 +11,45 @@
  * 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
+ * limitations under the License.
  */
 
 package com.android.car.settings.common;
 
-import android.car.drivingstate.CarUxRestrictions;
-import android.car.drivingstate.CarUxRestrictionsManager.OnUxRestrictionsChangedListener;
-import android.content.Context;
 import android.content.Intent;
-import android.content.IntentSender;
 import android.os.Bundle;
-import android.view.View;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.Toast;
 
 import androidx.annotation.Nullable;
-import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentActivity;
 import androidx.fragment.app.FragmentManager;
-import androidx.fragment.app.FragmentManager.OnBackStackChangedListener;
-import androidx.preference.Preference;
-import androidx.preference.PreferenceFragmentCompat;
 
-import com.android.car.apps.common.util.Themes;
 import com.android.car.settings.R;
 
 /**
- * Base activity class for car settings, provides a action bar with a back button that goes to
- * previous activity.
+ * Root activity used for most of the Settings app. This activity provides additional functionality
+ * which handles intents.
  */
-public class CarSettingActivity extends FragmentActivity implements FragmentController,
-        OnUxRestrictionsChangedListener, UxRestrictionsProvider, OnBackStackChangedListener,
-        PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+public class CarSettingActivity extends BaseCarSettingsActivity {
+
     private static final Logger LOG = new Logger(CarSettingActivity.class);
 
     private static final String KEY_HAS_NEW_INTENT =
             "com.android.car.settings.common.CarSettingActivity.KEY_HAS_NEW_INTENT";
 
     private boolean mHasNewIntent = true;
-    private CarUxRestrictionsHelper mUxRestrictionsHelper;
-    private View mRestrictedMessage;
-    // Default to minimum restriction.
-    private CarUxRestrictions mCarUxRestrictions = new CarUxRestrictions.Builder(
-            /* reqOpt= */ true,
-            CarUxRestrictions.UX_RESTRICTIONS_BASELINE,
-            /* timestamp= */ 0
-    ).build();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        setContentView(R.layout.car_setting_activity);
-        if (mUxRestrictionsHelper == null) {
-            mUxRestrictionsHelper = new CarUxRestrictionsHelper(/* context= */ this, /* listener= */
-                    this);
-        }
-        mUxRestrictionsHelper.start();
-        getSupportFragmentManager().addOnBackStackChangedListener(this);
-        mRestrictedMessage = findViewById(R.id.restricted_message);
-
         if (savedInstanceState != null) {
             mHasNewIntent = savedInstanceState.getBoolean(KEY_HAS_NEW_INTENT, mHasNewIntent);
         }
-        launchIfDifferent(getFragment());
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_HAS_NEW_INTENT, mHasNewIntent);
     }
 
     @Override
@@ -97,35 +71,9 @@
     }
 
     @Override
-    public void onDestroy() {
-        super.onDestroy();
-        mUxRestrictionsHelper.stop();
-        mUxRestrictionsHelper = null;
-    }
-
-    @Override
-    protected void onSaveInstanceState(Bundle outState) {
-        super.onSaveInstanceState(outState);
-        outState.putBoolean(KEY_HAS_NEW_INTENT, mHasNewIntent);
-    }
-
-    @Override
-    public void onBackPressed() {
-        super.onBackPressed();
-        hideKeyboard();
-        // if the backstack is empty, finish the activity.
-        if (getSupportFragmentManager().getBackStackEntryCount() == 0) {
-            finish();
-        }
-    }
-
-    @Override
     public void launchFragment(Fragment fragment) {
-        if (fragment instanceof DialogFragment) {
-            throw new IllegalArgumentException(
-                    "cannot launch dialogs with launchFragment() - use showDialog() instead");
-        }
-
+        // Called before super to clear the back stack if necessary before launching the fragment
+        // in question.
         if (fragment.getClass().getName().equals(
                 getString(R.string.config_settings_hierarchy_root_fragment))
                 && getSupportFragmentManager().getBackStackEntryCount() > 1) {
@@ -133,139 +81,20 @@
                     FragmentManager.POP_BACK_STACK_INCLUSIVE);
         }
 
-        getSupportFragmentManager()
-                .beginTransaction()
-                .setCustomAnimations(
-                        Themes.getAttrResourceId(/* context= */ this,
-                                android.R.attr.fragmentOpenEnterAnimation),
-                        Themes.getAttrResourceId(/* context= */ this,
-                                android.R.attr.fragmentOpenExitAnimation),
-                        Themes.getAttrResourceId(/* context= */ this,
-                                android.R.attr.fragmentCloseEnterAnimation),
-                        Themes.getAttrResourceId(/* context= */ this,
-                                android.R.attr.fragmentCloseExitAnimation))
-                .replace(R.id.fragment_container, fragment,
-                        Integer.toString(getSupportFragmentManager().getBackStackEntryCount()))
-                .addToBackStack(null)
-                .commit();
-    }
-
-    @Override
-    public void goBack() {
-        onBackPressed();
-    }
-
-    @Override
-    public void showBlockingMessage() {
-        Toast.makeText(
-                this, R.string.restricted_while_driving, Toast.LENGTH_SHORT).show();
-    }
-
-    @Override
-    public void showDialog(DialogFragment dialogFragment, @Nullable String tag) {
-        dialogFragment.show(getSupportFragmentManager(), tag);
-    }
-
-    @Override
-    @Nullable
-    public DialogFragment findDialogByTag(String tag) {
-        Fragment fragment = getSupportFragmentManager().findFragmentByTag(tag);
-        if (fragment instanceof DialogFragment) {
-            return (DialogFragment) fragment;
-        }
-        return null;
-    }
-
-    @Override
-    public void startActivityForResult(Intent intent, int requestCode,
-            ActivityResultCallback callback) {
-        throw new UnsupportedOperationException(
-                "Unimplemented for activities that implement FragmentController");
-    }
-
-    @Override
-    public void startIntentSenderForResult(IntentSender intent, int requestCode,
-            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, Bundle options,
-            ActivityResultCallback callback) {
-        throw new UnsupportedOperationException(
-                "Unimplemented for activities that implement FragmentController");
-    }
-
-    @Override
-    public void onUxRestrictionsChanged(CarUxRestrictions restrictionInfo) {
-        mCarUxRestrictions = restrictionInfo;
-        Fragment currentFragment = getCurrentFragment();
-        if (currentFragment instanceof OnUxRestrictionsChangedListener) {
-            ((OnUxRestrictionsChangedListener) currentFragment)
-                    .onUxRestrictionsChanged(restrictionInfo);
-        }
-        updateBlockingView(currentFragment);
-    }
-
-    @Override
-    public CarUxRestrictions getCarUxRestrictions() {
-        return mCarUxRestrictions;
-    }
-
-    @Override
-    public void onBackStackChanged() {
-        updateBlockingView(getCurrentFragment());
-    }
-
-    @Override
-    public boolean onPreferenceStartFragment(PreferenceFragmentCompat caller, Preference pref) {
-        if (pref.getFragment() != null) {
-            Fragment fragment = Fragment.instantiate(/* context= */ this, pref.getFragment(),
-                    pref.getExtras());
-            launchFragment(fragment);
-            return true;
-        }
-        return false;
+        super.launchFragment(fragment);
     }
 
     /**
      * Gets the fragment to show onCreate. This will only be launched if it is different from the
      * current fragment shown.
      */
+    @Override
     @Nullable
-    protected Fragment getFragment() {
+    protected Fragment getInitialFragment() {
         if (getCurrentFragment() != null) {
             return getCurrentFragment();
         }
         return Fragment.instantiate(this,
                 getString(R.string.config_settings_hierarchy_root_fragment));
     }
-
-    private void launchIfDifferent(Fragment newFragment) {
-        Fragment currentFragment = getCurrentFragment();
-        if ((newFragment != null) && differentFragment(newFragment, currentFragment)) {
-            LOG.d("launchIfDifferent: " + newFragment + " replacing " + currentFragment);
-            launchFragment(newFragment);
-        }
-    }
-
-    /**
-     * Returns {code true} if newFragment is different from current fragment.
-     */
-    private boolean differentFragment(Fragment newFragment, Fragment currentFragment) {
-        return (currentFragment == null)
-                || (!currentFragment.getClass().equals(newFragment.getClass()));
-    }
-
-    private Fragment getCurrentFragment() {
-        return getSupportFragmentManager().findFragmentById(R.id.fragment_container);
-    }
-
-    private void hideKeyboard() {
-        InputMethodManager imm = (InputMethodManager) this.getSystemService(
-                Context.INPUT_METHOD_SERVICE);
-        imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
-    }
-
-    private void updateBlockingView(@Nullable Fragment currentFragment) {
-        if (currentFragment instanceof BaseFragment) {
-            boolean canBeShown = ((BaseFragment) currentFragment).canBeShown(mCarUxRestrictions);
-            mRestrictedMessage.setVisibility(canBeShown ? View.GONE : View.VISIBLE);
-        }
-    }
 }
diff --git a/src/com/android/car/settings/datausage/AppDataUsageFragment.java b/src/com/android/car/settings/datausage/AppDataUsageFragment.java
index b613e68..2c756e2 100644
--- a/src/com/android/car/settings/datausage/AppDataUsageFragment.java
+++ b/src/com/android/car/settings/datausage/AppDataUsageFragment.java
@@ -97,14 +97,13 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Bundle bundle = getBundleForNetworkStats();
+        mBundle = getBundleForNetworkStats();
 
         LoaderManager loaderManager = LoaderManager.getInstance(this);
-        mAppsNetworkStatsManager.startLoading(loaderManager, bundle);
+        mAppsNetworkStatsManager.startLoading(loaderManager, mBundle);
     }
 
-    @VisibleForTesting
-    Bundle getBundleForNetworkStats() {
+    private Bundle getBundleForNetworkStats() {
         long historyStart = System.currentTimeMillis();
         long historyEnd = historyStart + 1;
 
@@ -139,4 +138,9 @@
 
         return SummaryForAllUidLoader.buildArgs(mNetworkTemplate, start, end);
     }
+
+    @VisibleForTesting(otherwise = VisibleForTesting.NONE)
+    Bundle getBundle() {
+        return mBundle;
+    }
 }
diff --git a/src/com/android/car/settings/security/CheckLockActivity.java b/src/com/android/car/settings/security/CheckLockActivity.java
index 8881e31..ba1407b 100644
--- a/src/com/android/car/settings/security/CheckLockActivity.java
+++ b/src/com/android/car/settings/security/CheckLockActivity.java
@@ -23,7 +23,7 @@
 import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
 
-import com.android.car.settings.common.CarSettingActivity;
+import com.android.car.settings.common.BaseCarSettingsActivity;
 import com.android.car.settings.common.Logger;
 import com.android.internal.widget.LockPatternUtils;
 
@@ -31,13 +31,13 @@
  * Prompts the user to enter their pin, password, or pattern lock (if set) and returns
  * {@link #RESULT_OK} on a successful entry or immediately if the user has no lock setup.
  */
-public class CheckLockActivity extends CarSettingActivity implements CheckLockListener {
+public class CheckLockActivity extends BaseCarSettingsActivity implements CheckLockListener {
 
     private static final Logger LOG = new Logger(CheckLockActivity.class);
 
     @Override
     @Nullable
-    protected Fragment getFragment() {
+    protected Fragment getInitialFragment() {
         Fragment fragment;
         int passwordQuality = new LockPatternUtils(this).getKeyguardStoredPasswordQuality(
                 UserHandle.myUserId());
diff --git a/src/com/android/car/settings/security/SettingsScreenLockActivity.java b/src/com/android/car/settings/security/SettingsScreenLockActivity.java
index 9c48754..e9ad6fc 100644
--- a/src/com/android/car/settings/security/SettingsScreenLockActivity.java
+++ b/src/com/android/car/settings/security/SettingsScreenLockActivity.java
@@ -24,14 +24,15 @@
 import androidx.fragment.app.Fragment;
 
 import com.android.car.settings.R;
-import com.android.car.settings.common.CarSettingActivity;
+import com.android.car.settings.common.BaseCarSettingsActivity;
 import com.android.car.settings.common.Logger;
 import com.android.internal.widget.LockPatternUtils;
 
 /**
  * Activity for setting screen locks
  */
-public class SettingsScreenLockActivity extends CarSettingActivity implements CheckLockListener {
+public class SettingsScreenLockActivity extends BaseCarSettingsActivity implements
+        CheckLockListener {
 
     private static final Logger LOG = new Logger(SettingsScreenLockActivity.class);
 
@@ -39,7 +40,7 @@
 
     @Override
     @Nullable
-    protected Fragment getFragment() {
+    protected Fragment getInitialFragment() {
         mPasswordQuality = new LockPatternUtils(this).getKeyguardStoredPasswordQuality(
                 UserHandle.myUserId());
 
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/common/BaseCarSettingsActivityTest.java b/tests/robotests/src/com/android/car/settings/common/BaseCarSettingsActivityTest.java
new file mode 100644
index 0000000..cb83d85
--- /dev/null
+++ b/tests/robotests/src/com/android/car/settings/common/BaseCarSettingsActivityTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 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.common;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictions;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.preference.Preference;
+
+import com.android.car.settings.CarSettingsRobolectricTestRunner;
+import com.android.car.settings.R;
+import com.android.car.settings.testutils.ShadowCar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.android.controller.ActivityController;
+
+/** Unit test for {@link BaseCarSettingsActivity}. */
+@RunWith(CarSettingsRobolectricTestRunner.class)
+public class BaseCarSettingsActivityTest {
+
+    private static final String TEST_TAG = "test_tag";
+
+    private Context mContext;
+    private ActivityController<TestBaseCarSettingsActivity> mActivityController;
+    private TestBaseCarSettingsActivity mActivity;
+
+    @Mock
+    private CarUxRestrictionsManager mMockCarUxRestrictionsManager;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        CarUxRestrictions noSetupRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
+                CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* time= */ 0).build();
+        when(mMockCarUxRestrictionsManager.getCurrentCarUxRestrictions())
+                .thenReturn(noSetupRestrictions);
+        ShadowCar.setCarManager(Car.CAR_UX_RESTRICTION_SERVICE, mMockCarUxRestrictionsManager);
+        mContext = RuntimeEnvironment.application;
+        mActivityController = ActivityController.of(new TestBaseCarSettingsActivity());
+        mActivity = mActivityController.get();
+        mActivityController.create();
+    }
+
+    @Test
+    public void onPreferenceStartFragment_launchesFragment() {
+        Preference pref = new Preference(mContext);
+        pref.setFragment(TestFragment.class.getName());
+
+        mActivity.onPreferenceStartFragment(/* caller= */ null, pref);
+
+        assertThat(mActivity.getSupportFragmentManager().findFragmentById(
+                R.id.fragment_container)).isInstanceOf(TestFragment.class);
+    }
+
+    @Test
+    public void launchFragment_dialogFragment_throwsError() {
+        DialogFragment dialogFragment = new DialogFragment();
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mActivity.launchFragment(dialogFragment));
+    }
+
+    @Test
+    public void showDialog_launchDialogFragment_noTag() {
+        DialogFragment dialogFragment = mock(DialogFragment.class);
+        mActivity.showDialog(dialogFragment, /* tag */ null);
+        verify(dialogFragment).show(mActivity.getSupportFragmentManager(), null);
+    }
+
+    @Test
+    public void showDialog_launchDialogFragment_withTag() {
+        DialogFragment dialogFragment = mock(DialogFragment.class);
+        mActivity.showDialog(dialogFragment, TEST_TAG);
+        verify(dialogFragment).show(mActivity.getSupportFragmentManager(), TEST_TAG);
+    }
+
+    @Test
+    public void findDialogByTag_retrieveOriginalDialog() {
+        DialogFragment dialogFragment = new DialogFragment();
+        mActivity.showDialog(dialogFragment, TEST_TAG);
+        assertThat(mActivity.findDialogByTag(TEST_TAG)).isEqualTo(dialogFragment);
+    }
+
+    @Test
+    public void findDialogByTag_notDialogFragment() {
+        TestFragment fragment = new TestFragment();
+        mActivity.getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
+                fragment, TEST_TAG).commit();
+        assertThat(mActivity.findDialogByTag(TEST_TAG)).isNull();
+    }
+
+    @Test
+    public void findDialogByTag_noSuchFragment() {
+        assertThat(mActivity.findDialogByTag(TEST_TAG)).isNull();
+    }
+
+    /** Simple instance of {@link BaseCarSettingsActivity}. */
+    private static class TestBaseCarSettingsActivity extends BaseCarSettingsActivity {
+
+        @Nullable
+        @Override
+        protected Fragment getInitialFragment() {
+            return new TestFragment();
+        }
+    }
+
+    /** Simple Fragment for testing use. */
+    public static class TestFragment extends Fragment {
+    }
+}
diff --git a/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java b/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java
index 5601505..09c464b 100644
--- a/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java
+++ b/tests/robotests/src/com/android/car/settings/common/CarSettingActivityTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 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.
@@ -18,10 +18,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.testng.Assert.assertThrows;
 
 import android.car.Car;
 import android.car.drivingstate.CarUxRestrictions;
@@ -31,9 +28,7 @@
 import android.os.Bundle;
 import android.provider.Settings;
 
-import androidx.fragment.app.DialogFragment;
 import androidx.fragment.app.Fragment;
-import androidx.preference.Preference;
 
 import com.android.car.settings.CarSettingsRobolectricTestRunner;
 import com.android.car.settings.R;
@@ -75,8 +70,6 @@
         mActivityController = ActivityController.of(new CarSettingActivity());
         mActivity = mActivityController.get();
         mActivityController.create();
-
-
     }
 
     @Test
@@ -102,12 +95,12 @@
         mActivityController.start().postCreate(null).resume();
         TestFragment testFragment = new TestFragment();
         mActivity.launchFragment(testFragment);
-        assertThat(mActivity.getFragment()).isEqualTo(testFragment);
+        assertThat(mActivity.getCurrentFragment()).isEqualTo(testFragment);
 
         mActivity.onNewIntent(new Intent(Settings.ACTION_DATE_SETTINGS));
         mActivity.onResume();
 
-        assertThat(mActivity.getFragment()).isNotEqualTo(testFragment);
+        assertThat(mActivity.getCurrentFragment()).isNotEqualTo(testFragment);
     }
 
     @Test
@@ -115,7 +108,7 @@
         mActivityController.start().postCreate(null).resume();
         Intent intent = new Intent(Settings.ACTION_DATE_SETTINGS);
         mActivity.onNewIntent(intent);
-        assertThat(mActivity.getFragment()).isNotInstanceOf(TestFragment.class);
+        assertThat(mActivity.getCurrentFragment()).isNotInstanceOf(TestFragment.class);
         mActivity.onResume(); // Showing date time settings (old intent)
         mActivity.launchFragment(new TestFragment()); // Replace with test fragment.
 
@@ -126,18 +119,7 @@
         mActivityController.setup(outState);
 
         // Should still display most recently launched fragment.
-        assertThat(mActivityController.get().getFragment()).isInstanceOf(TestFragment.class);
-    }
-
-    @Test
-    public void onPreferenceStartFragment_launchesFragment() {
-        Preference pref = new Preference(mContext);
-        pref.setFragment(TestFragment.class.getName());
-
-        mActivity.onPreferenceStartFragment(/* caller= */ null, pref);
-
-        assertThat(mActivity.getSupportFragmentManager().findFragmentById(
-                R.id.fragment_container)).isInstanceOf(TestFragment.class);
+        assertThat(mActivityController.get().getCurrentFragment()).isInstanceOf(TestFragment.class);
     }
 
     @Test
@@ -159,48 +141,6 @@
                 .isEqualTo(1);
     }
 
-    @Test
-    public void launchFragment_dialogFragment_throwsError() {
-        DialogFragment dialogFragment = new DialogFragment();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mActivity.launchFragment(dialogFragment));
-    }
-
-    @Test
-    public void showDialog_launchDialogFragment_noTag() {
-        DialogFragment dialogFragment = mock(DialogFragment.class);
-        mActivity.showDialog(dialogFragment, /* tag */ null);
-        verify(dialogFragment).show(mActivity.getSupportFragmentManager(), null);
-    }
-
-    @Test
-    public void showDialog_launchDialogFragment_withTag() {
-        DialogFragment dialogFragment = mock(DialogFragment.class);
-        mActivity.showDialog(dialogFragment, TEST_TAG);
-        verify(dialogFragment).show(mActivity.getSupportFragmentManager(), TEST_TAG);
-    }
-
-    @Test
-    public void findDialogByTag_retrieveOriginalDialog() {
-        DialogFragment dialogFragment = new DialogFragment();
-        mActivity.showDialog(dialogFragment, TEST_TAG);
-        assertThat(mActivity.findDialogByTag(TEST_TAG)).isEqualTo(dialogFragment);
-    }
-
-    @Test
-    public void findDialogByTag_notDialogFragment() {
-        TestFragment fragment = new TestFragment();
-        mActivity.getSupportFragmentManager().beginTransaction().replace(R.id.fragment_container,
-                fragment, TEST_TAG).commit();
-        assertThat(mActivity.findDialogByTag(TEST_TAG)).isNull();
-    }
-
-    @Test
-    public void findDialogByTag_noSuchFragment() {
-        assertThat(mActivity.findDialogByTag(TEST_TAG)).isNull();
-    }
-
     /** Simple Fragment for testing use. */
     public static class TestFragment extends Fragment {
     }
diff --git a/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
index e4cf647..c94cfa8 100644
--- a/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
+++ b/tests/robotests/src/com/android/car/settings/datausage/AppDataUsageFragmentTest.java
@@ -85,7 +85,7 @@
     public void onActivityCreated_policyIsNull_startAndEndDateShouldHaveFourWeeksDifference() {
         mFragmentController.create();
 
-        Bundle bundle = mFragment.getBundleForNetworkStats();
+        Bundle bundle = mFragment.getBundle();
         long start = bundle.getLong(KEY_START);
         long end = bundle.getLong(KEY_END);
         long timeDiff = end - start;
@@ -102,7 +102,7 @@
         ShadowNetworkPolicyManager.setCycleIterator(iterator);
         mFragmentController.create();
 
-        Bundle bundle = mFragment.getBundleForNetworkStats();
+        Bundle bundle = mFragment.getBundle();
         long start = bundle.getLong(KEY_START);
         long end = bundle.getLong(KEY_END);
         long timeDiff = end - start;
@@ -130,7 +130,7 @@
         ShadowNetworkPolicyManager.setCycleIterator(iterator);
         mFragmentController.create();
 
-        Bundle bundle = mFragment.getBundleForNetworkStats();
+        Bundle bundle = mFragment.getBundle();
         long start = bundle.getLong(KEY_START);
         long end = bundle.getLong(KEY_END);
 
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;
+    }
+}