Extract abstract SafetyCenterViewModel and factory.

This is prep for adding a fake or mock viewmodel to use in unit tests
for the SafetyCenterDashboardFragment.

Bug: 229854704
Test: Deployed to phone locally
Change-Id: Ib710ec0c28843e504da1a805abb90dd6c7f0e128
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
index 3058671..ae7c0cd 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterActivity.java
@@ -28,9 +28,7 @@
 import com.android.permissioncontroller.R;
 import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity;
 
-/**
- * Entry-point activity for SafetyCenter.
- */
+/** Entry-point activity for SafetyCenter. */
 @Keep
 public final class SafetyCenterActivity extends CollapsingToolbarBaseActivity {
 
@@ -45,10 +43,12 @@
         if (maybeRedirectIfDisabled()) return;
 
         setTitle(getString(R.string.safety_center_dashboard_page_title));
-        getSupportFragmentManager()
-                .beginTransaction()
-                .replace(R.id.content_frame, new SafetyCenterDashboardFragment())
-                .commitNow();
+        if (savedInstanceState == null) {
+            getSupportFragmentManager()
+                    .beginTransaction()
+                    .add(R.id.content_frame, new SafetyCenterDashboardFragment())
+                    .commitNow();
+        }
     }
 
     @Override
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index a9e1775..fe1c418 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -33,12 +33,14 @@
 import android.widget.Toast;
 
 import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
 import androidx.lifecycle.ViewModelProvider;
 import androidx.preference.PreferenceCategory;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceGroup;
 
 import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.safetycenter.ui.model.LiveSafetyCenterViewModelFactory;
 import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
 
 import java.util.List;
@@ -53,17 +55,42 @@
     private static final String ENTRIES_GROUP_KEY = "entries_group";
     private static final String STATIC_ENTRIES_GROUP_KEY = "static_entries_group";
 
+    @Nullable
+    private final ViewModelProvider.Factory mSafetyCenterViewModelFactoryOverride;
+
     private SafetyStatusPreference mSafetyStatusPreference;
     private PreferenceGroup mIssuesGroup;
     private PreferenceGroup mEntriesGroup;
     private PreferenceGroup mStaticEntriesGroup;
     private SafetyCenterViewModel mViewModel;
 
+    public SafetyCenterDashboardFragment() {
+        this(null);
+    }
+
+    /**
+     * Allows providing an override view model factory for testing this fragment. The view model
+     * factory will not be retained between recreations of the fragment.
+     */
+    @VisibleForTesting
+    public SafetyCenterDashboardFragment(
+            @Nullable ViewModelProvider.Factory safetyCenterViewModelFactoryOverride) {
+        mSafetyCenterViewModelFactoryOverride = safetyCenterViewModelFactoryOverride;
+    }
+
+    private ViewModelProvider.Factory getSafetyCenterViewModelFactory() {
+        return mSafetyCenterViewModelFactoryOverride != null
+                ? mSafetyCenterViewModelFactoryOverride
+                : new LiveSafetyCenterViewModelFactory(requireActivity().getApplication());
+    }
+
     @Override
     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
         setPreferencesFromResource(R.xml.safety_center_dashboard, rootKey);
 
-        mViewModel = new ViewModelProvider(requireActivity()).get(SafetyCenterViewModel.class);
+        mViewModel =
+                new ViewModelProvider(requireActivity(), getSafetyCenterViewModelFactory())
+                        .get(SafetyCenterViewModel.class);
 
         mSafetyStatusPreference =
                 requireNonNull(getPreferenceScreen().findPreference(SAFETY_STATUS_KEY));
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
new file mode 100644
index 0000000..9d1b0a7
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/LiveSafetyCenterViewModel.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.permissioncontroller.safetycenter.ui.model
+
+import android.app.Application
+import android.safetycenter.SafetyCenterData
+import android.safetycenter.SafetyCenterErrorDetails
+import android.safetycenter.SafetyCenterIssue
+import android.safetycenter.SafetyCenterManager
+import androidx.core.content.ContextCompat.getMainExecutor
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+
+/* A SafetyCenterViewModel that talks to the real backing service for Safety Center. */
+class LiveSafetyCenterViewModel(app: Application) : SafetyCenterViewModel(app) {
+
+    override val safetyCenterLiveData: LiveData<SafetyCenterData>
+        get() = _safetyCenterLiveData
+    override val errorLiveData: LiveData<SafetyCenterErrorDetails>
+        get() = _errorLiveData
+
+    private val _safetyCenterLiveData = SafetyCenterLiveData()
+    private val _errorLiveData = MutableLiveData<SafetyCenterErrorDetails>()
+
+    private val safetyCenterManager = app.getSystemService(SafetyCenterManager::class.java)!!
+
+    override fun dismissIssue(issue: SafetyCenterIssue) {
+        safetyCenterManager.dismissSafetyCenterIssue(issue.id)
+    }
+
+    override fun executeIssueAction(issue: SafetyCenterIssue, action: SafetyCenterIssue.Action) {
+        safetyCenterManager.executeSafetyCenterIssueAction(issue.id, action.id)
+    }
+
+    override fun rescan() {
+        safetyCenterManager.refreshSafetySources(
+            SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK)
+    }
+
+    override fun clearError() {
+        _errorLiveData.value = null
+    }
+
+    override fun refresh() {
+        safetyCenterManager.refreshSafetySources(SafetyCenterManager.REFRESH_REASON_PAGE_OPEN)
+    }
+
+    inner class SafetyCenterLiveData :
+        MutableLiveData<SafetyCenterData>(), SafetyCenterManager.OnSafetyCenterDataChangedListener {
+
+        override fun onActive() {
+            safetyCenterManager.addOnSafetyCenterDataChangedListener(
+                getMainExecutor(app.applicationContext), this)
+            super.onActive()
+        }
+
+        override fun onInactive() {
+            safetyCenterManager.removeOnSafetyCenterDataChangedListener(this)
+            super.onInactive()
+        }
+
+        override fun onSafetyCenterDataChanged(data: SafetyCenterData) {
+            value = data
+        }
+
+        override fun onError(errorDetails: SafetyCenterErrorDetails) {
+            _errorLiveData.value = errorDetails
+        }
+    }
+
+    inner class AutoRefreshManager : DefaultLifecycleObserver {
+        // TODO(b/222323674): We may need to do this in onResume to cover certain edge cases.
+        // i.e. FMD changed from quick settings while SC is open
+        override fun onStart(owner: LifecycleOwner) {
+            refresh()
+        }
+    }
+}
+
+class LiveSafetyCenterViewModelFactory(private val app: Application) : ViewModelProvider.Factory {
+    override fun <T : ViewModel> create(modelClass: Class<T>): T {
+        @Suppress("UNCHECKED_CAST") return LiveSafetyCenterViewModel(app) as T
+    }
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt
index ad6e1dd..ebd83f0 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterViewModel.kt
@@ -20,64 +20,26 @@
 import android.safetycenter.SafetyCenterData
 import android.safetycenter.SafetyCenterErrorDetails
 import android.safetycenter.SafetyCenterIssue
-import android.safetycenter.SafetyCenterManager
-import androidx.core.content.ContextCompat.getMainExecutor
 import androidx.lifecycle.AndroidViewModel
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.LiveData
 
-class SafetyCenterViewModel(val app: Application) : AndroidViewModel(app) {
+abstract class SafetyCenterViewModel(protected val app: Application) : AndroidViewModel(app) {
 
-    val safetyCenterLiveData = SafetyCenterLiveData()
-    val errorLiveData = MutableLiveData<SafetyCenterErrorDetails>()
+    abstract val safetyCenterLiveData: LiveData<SafetyCenterData>
+    abstract val errorLiveData: LiveData<SafetyCenterErrorDetails>
     val autoRefreshManager = AutoRefreshManager()
 
-    private val safetyCenterManager = app.getSystemService(SafetyCenterManager::class.java)!!
+    abstract fun dismissIssue(issue: SafetyCenterIssue)
 
-    fun dismissIssue(issue: SafetyCenterIssue) {
-        safetyCenterManager.dismissSafetyCenterIssue(issue.id)
-    }
+    abstract fun executeIssueAction(issue: SafetyCenterIssue, action: SafetyCenterIssue.Action)
 
-    fun executeIssueAction(issue: SafetyCenterIssue, action: SafetyCenterIssue.Action) {
-        safetyCenterManager.executeSafetyCenterIssueAction(issue.id, action.id)
-    }
+    abstract fun rescan()
 
-    fun rescan() {
-        safetyCenterManager.refreshSafetySources(
-            SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLICK)
-    }
+    abstract fun clearError()
 
-    fun clearError() {
-        errorLiveData.value = null
-    }
-
-    private fun refresh() {
-        safetyCenterManager.refreshSafetySources(SafetyCenterManager.REFRESH_REASON_PAGE_OPEN)
-    }
-
-    inner class SafetyCenterLiveData :
-        MutableLiveData<SafetyCenterData>(), SafetyCenterManager.OnSafetyCenterDataChangedListener {
-
-        override fun onActive() {
-            safetyCenterManager.addOnSafetyCenterDataChangedListener(
-                getMainExecutor(app.applicationContext), this)
-            super.onActive()
-        }
-
-        override fun onInactive() {
-            safetyCenterManager.removeOnSafetyCenterDataChangedListener(this)
-            super.onInactive()
-        }
-
-        override fun onSafetyCenterDataChanged(data: SafetyCenterData) {
-            value = data
-        }
-
-        override fun onError(errorDetails: SafetyCenterErrorDetails) {
-            errorLiveData.value = errorDetails
-        }
-    }
+    protected abstract fun refresh()
 
     inner class AutoRefreshManager : DefaultLifecycleObserver {
         // TODO(b/222323674): We may need to do this in onResume to cover certain edge cases.