Extract class AppsBackedUpOnThisDeviceJournal from BackupManagerService

Puts all the logic for remembering what apps have been backed up on
the current device into own class. Also fixes bug where if an app was
uninstalled, it was removed from the journal. As the journal is used
to decide what restore set to use after a fresh install of an app (as
at this point we do try to restore previous state if it's available)
it doesn't make sense to forget apps that were uninstalled.

Bug: 36850431
Test: adb shell am instrument -w -e package com.android.server.backup com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
Change-Id: I9300883e139ee0773acbf4a09b08c7f5955c66e5
diff --git a/services/tests/servicestests/src/com/android/server/backup/AppsBackedUpOnThisDeviceJournalTest.java b/services/tests/servicestests/src/com/android/server/backup/AppsBackedUpOnThisDeviceJournalTest.java
new file mode 100644
index 0000000..093f920
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/AppsBackedUpOnThisDeviceJournalTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.backup;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.google.android.collect.Sets;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class AppsBackedUpOnThisDeviceJournalTest {
+    private static final String JOURNAL_FILE_NAME = "processed";
+
+    private static final String GOOGLE_PHOTOS = "com.google.photos";
+    private static final String GMAIL = "com.google.gmail";
+    private static final String GOOGLE_PLUS = "com.google.plus";
+
+    @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
+
+    private File mStateDirectory;
+    private AppsBackedUpOnThisDeviceJournal mAppsBackedUpOnThisDeviceJournal;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        mStateDirectory = mTemporaryFolder.newFolder();
+        mAppsBackedUpOnThisDeviceJournal = new AppsBackedUpOnThisDeviceJournal(mStateDirectory);
+    }
+
+    @Test
+    public void constructor_loadsAnyPreviousJournalFromDisk() throws Exception {
+        writePermanentJournalPackages(Sets.newHashSet(GOOGLE_PHOTOS, GMAIL));
+
+        AppsBackedUpOnThisDeviceJournal journalFromDisk =
+                new AppsBackedUpOnThisDeviceJournal(mStateDirectory);
+
+        assertThat(journalFromDisk.hasBeenProcessed(GOOGLE_PHOTOS)).isTrue();
+        assertThat(journalFromDisk.hasBeenProcessed(GMAIL)).isTrue();
+    }
+
+    @Test
+    public void hasBeenProcessed_isFalseForAnyPackageFromBlankInit() {
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GOOGLE_PHOTOS)).isFalse();
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GMAIL)).isFalse();
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GOOGLE_PLUS)).isFalse();
+    }
+
+    @Test
+    public void addPackage_addsPackageToObjectState() {
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PHOTOS);
+
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GOOGLE_PHOTOS)).isTrue();
+    }
+
+    @Test
+    public void addPackage_addsPackageToFileSystem() throws Exception {
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PHOTOS);
+
+        assertThat(readJournalPackages()).contains(GOOGLE_PHOTOS);
+    }
+
+    @Test
+    public void getPackagesCopy_returnsTheCurrentState() throws Exception {
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PHOTOS);
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GMAIL);
+
+        assertThat(mAppsBackedUpOnThisDeviceJournal.getPackagesCopy())
+                .isEqualTo(Sets.newHashSet(GOOGLE_PHOTOS, GMAIL));
+    }
+
+    @Test
+    public void getPackagesCopy_returnsACopy() throws Exception {
+        mAppsBackedUpOnThisDeviceJournal.getPackagesCopy().add(GMAIL);
+
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GMAIL)).isFalse();
+    }
+
+    @Test
+    public void reset_removesAllPackagesFromObjectState() {
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PHOTOS);
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PLUS);
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GMAIL);
+
+        mAppsBackedUpOnThisDeviceJournal.reset();
+
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GOOGLE_PHOTOS)).isFalse();
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GMAIL)).isFalse();
+        assertThat(mAppsBackedUpOnThisDeviceJournal.hasBeenProcessed(GOOGLE_PLUS)).isFalse();
+    }
+
+    @Test
+    public void reset_removesAllPackagesFromFileSystem() throws Exception {
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PHOTOS);
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GOOGLE_PLUS);
+        mAppsBackedUpOnThisDeviceJournal.addPackage(GMAIL);
+
+        mAppsBackedUpOnThisDeviceJournal.reset();
+
+        assertThat(readJournalPackages()).isEmpty();
+    }
+
+    private HashSet<String> readJournalPackages() throws Exception {
+        File journal = new File(mStateDirectory, JOURNAL_FILE_NAME);
+        HashSet<String> packages = new HashSet<>();
+
+        try (FileInputStream fis = new FileInputStream(journal);
+             DataInputStream dis = new DataInputStream(fis)) {
+            while (dis.available() > 0) {
+                packages.add(dis.readUTF());
+            }
+        } catch (FileNotFoundException e) {
+            return new HashSet<>();
+        }
+
+        return packages;
+    }
+
+    private void writePermanentJournalPackages(Set<String> packages) throws Exception {
+        File journal = new File(mStateDirectory, JOURNAL_FILE_NAME);
+
+        try (FileOutputStream fos = new FileOutputStream(journal);
+             DataOutputStream dos = new DataOutputStream(fos)) {
+            for (String packageName : packages) {
+                dos.writeUTF(packageName);
+            }
+        }
+    }
+}