Add backup/restore for slices access

Test: various "adb shell bmgr" commands to verify behavior
Change-Id: Ic4439e0e17516462acdb8d28fe49095209a2ed6f
Fixes: 68751119
diff --git a/core/java/android/app/slice/ISliceManager.aidl b/core/java/android/app/slice/ISliceManager.aidl
index 4461b16..38d9025 100644
--- a/core/java/android/app/slice/ISliceManager.aidl
+++ b/core/java/android/app/slice/ISliceManager.aidl
@@ -31,4 +31,7 @@
     SliceSpec[] getPinnedSpecs(in Uri uri, String pkg);
     int checkSlicePermission(in Uri uri, String pkg, int pid, int uid);
     void grantPermissionFromUser(in Uri uri, String pkg, String callingPkg, boolean allSlices);
+
+    byte[] getBackupPayload(int user);
+    void applyRestore(in byte[] payload, int user);
 }
diff --git a/core/java/com/android/server/backup/SliceBackupHelper.java b/core/java/com/android/server/backup/SliceBackupHelper.java
new file mode 100644
index 0000000..8e5a5ee
--- /dev/null
+++ b/core/java/com/android/server/backup/SliceBackupHelper.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2018 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 android.app.backup.BlobBackupHelper;
+import android.app.slice.ISliceManager;
+import android.content.Context;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+public class SliceBackupHelper extends BlobBackupHelper {
+    static final String TAG = "SliceBackupHelper";
+    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // Current version of the blob schema
+    static final int BLOB_VERSION = 1;
+
+    // Key under which the payload blob is stored
+    static final String KEY_SLICES = "slices";
+
+    public SliceBackupHelper(Context context) {
+        super(BLOB_VERSION, KEY_SLICES);
+        // context is currently unused
+    }
+
+    @Override
+    protected byte[] getBackupPayload(String key) {
+        byte[] newPayload = null;
+        if (KEY_SLICES.equals(key)) {
+            try {
+                ISliceManager sm = ISliceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.SLICE_SERVICE));
+                // TODO: http://b/22388012
+                newPayload = sm.getBackupPayload(UserHandle.USER_SYSTEM);
+            } catch (Exception e) {
+                // Treat as no data
+                Slog.e(TAG, "Couldn't communicate with slice manager");
+                newPayload = null;
+            }
+        }
+        return newPayload;
+    }
+
+    @Override
+    protected void applyRestoredPayload(String key, byte[] payload) {
+        if (DEBUG) Slog.v(TAG, "Got restore of " + key);
+
+        if (KEY_SLICES.equals(key)) {
+            try {
+                ISliceManager sm = ISliceManager.Stub.asInterface(
+                        ServiceManager.getService(Context.SLICE_SERVICE));
+                // TODO: http://b/22388012
+                sm.applyRestore(payload, UserHandle.USER_SYSTEM);
+            } catch (Exception e) {
+                Slog.e(TAG, "Couldn't communicate with slice manager");
+            }
+        }
+    }
+
+}
diff --git a/core/java/com/android/server/backup/SystemBackupAgent.java b/core/java/com/android/server/backup/SystemBackupAgent.java
index a96b5dd..47e7a0e7 100644
--- a/core/java/com/android/server/backup/SystemBackupAgent.java
+++ b/core/java/com/android/server/backup/SystemBackupAgent.java
@@ -51,6 +51,7 @@
     private static final String USAGE_STATS_HELPER = "usage_stats";
     private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
     private static final String ACCOUNT_MANAGER_HELPER = "account_manager";
+    private static final String SLICES_HELPER = "slices";
 
     // These paths must match what the WallpaperManagerService uses.  The leaf *_FILENAME
     // are also used in the full-backup file format, so must not change unless steps are
@@ -88,6 +89,7 @@
         addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
         addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
         addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
+        addHelper(SLICES_HELPER, new SliceBackupHelper(this));
         super.onBackup(oldState, data, newState);
     }
 
@@ -116,6 +118,7 @@
         addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
         addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
         addHelper(ACCOUNT_MANAGER_HELPER, new AccountManagerBackupHelper());
+        addHelper(SLICES_HELPER, new SliceBackupHelper(this));
 
         super.onRestore(data, appVersionCode, newState);
     }
diff --git a/services/core/java/com/android/server/slice/SliceFullAccessList.java b/services/core/java/com/android/server/slice/SliceFullAccessList.java
index 5e0cd03..6f5afa2 100644
--- a/services/core/java/com/android/server/slice/SliceFullAccessList.java
+++ b/services/core/java/com/android/server/slice/SliceFullAccessList.java
@@ -16,9 +16,9 @@
 
 import android.content.Context;
 import android.content.pm.UserInfo;
+import android.os.UserHandle;
 import android.os.UserManager;
 import android.util.ArraySet;
-import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.util.XmlUtils;
@@ -72,7 +72,7 @@
         pkgs.remove(pkg);
     }
 
-    public void writeXml(XmlSerializer out) throws IOException {
+    public void writeXml(XmlSerializer out, int user) throws IOException {
         out.startTag(null, TAG_LIST);
         out.attribute(null, ATT_VERSION, String.valueOf(DB_VERSION));
 
@@ -80,6 +80,9 @@
         for (int i = 0 ; i < N; i++) {
             final int userId = mFullAccessPkgs.keyAt(i);
             final ArraySet<String> pkgs = mFullAccessPkgs.valueAt(i);
+            if (user != UserHandle.USER_ALL && user != userId) {
+                continue;
+            }
             out.startTag(null, TAG_USER);
             out.attribute(null, ATT_USER_ID, Integer.toString(userId));
             if (pkgs != null) {
@@ -88,7 +91,6 @@
                         out.startTag(null, TAG_PKG);
                         out.text(pkgs.valueAt(j));
                         out.endTag(null, TAG_PKG);
-
                 }
             }
             out.endTag(null, TAG_USER);
diff --git a/services/core/java/com/android/server/slice/SliceManagerService.java b/services/core/java/com/android/server/slice/SliceManagerService.java
index c4871df..a1def44 100644
--- a/services/core/java/com/android/server/slice/SliceManagerService.java
+++ b/services/core/java/com/android/server/slice/SliceManagerService.java
@@ -21,6 +21,7 @@
 import static android.content.ContentProvider.maybeAddUserId;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
 
 import android.Manifest.permission;
 import android.app.ActivityManager;
@@ -68,8 +69,9 @@
 import org.xmlpull.v1.XmlPullParserFactory;
 import org.xmlpull.v1.XmlSerializer;
 
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.File;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -93,6 +95,7 @@
     private final ArraySet<SliceGrant> mUserGrants = new ArraySet<>();
     private final Handler mHandler;
     private final ContentObserver mObserver;
+    @GuardedBy("mSliceAccessFile")
     private final AtomicFile mSliceAccessFile;
     @GuardedBy("mAccessList")
     private final SliceFullAccessList mAccessList;
@@ -257,6 +260,63 @@
         }
     }
 
+    // Backup/restore interface
+    @Override
+    public byte[] getBackupPayload(int user) {
+        if (Binder.getCallingUid() != SYSTEM_UID) {
+            throw new SecurityException("Caller must be system");
+        }
+        //TODO: http://b/22388012
+        if (user != UserHandle.USER_SYSTEM) {
+            Slog.w(TAG, "getBackupPayload: cannot backup policy for user " + user);
+            return null;
+        }
+        synchronized(mSliceAccessFile) {
+            final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            try {
+                XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
+                out.setOutput(baos, Encoding.UTF_8.name());
+                synchronized (mAccessList) {
+                    mAccessList.writeXml(out, user);
+                }
+                out.flush();
+                return baos.toByteArray();
+            } catch (IOException | XmlPullParserException e) {
+                Slog.w(TAG, "getBackupPayload: error writing payload for user " + user, e);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public void applyRestore(byte[] payload, int user) {
+        if (Binder.getCallingUid() != SYSTEM_UID) {
+            throw new SecurityException("Caller must be system");
+        }
+        if (payload == null) {
+            Slog.w(TAG, "applyRestore: no payload to restore for user " + user);
+            return;
+        }
+        //TODO: http://b/22388012
+        if (user != UserHandle.USER_SYSTEM) {
+            Slog.w(TAG, "applyRestore: cannot restore policy for user " + user);
+            return;
+        }
+        synchronized(mSliceAccessFile) {
+            final ByteArrayInputStream bais = new ByteArrayInputStream(payload);
+            try {
+                XmlPullParser parser = XmlPullParserFactory.newInstance().newPullParser();
+                parser.setInput(bais, Encoding.UTF_8.name());
+                synchronized (mAccessList) {
+                    mAccessList.readXml(parser);
+                }
+                mHandler.post(mSaveAccessList);
+            } catch (NumberFormatException | XmlPullParserException | IOException e) {
+                Slog.w(TAG, "applyRestore: error reading payload", e);
+            }
+        }
+    }
+
     ///  ----- internal code -----
     private void removeFullAccess(String pkg, int userId) {
         synchronized (mAccessList) {
@@ -492,7 +552,7 @@
                     XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
                     out.setOutput(stream, Encoding.UTF_8.name());
                     synchronized (mAccessList) {
-                        mAccessList.writeXml(out);
+                        mAccessList.writeXml(out, UserHandle.USER_ALL);
                     }
                     out.flush();
                     mSliceAccessFile.finishWrite(stream);
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java
index b784c60..bc28150 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceFullAccessListTest.java
@@ -18,6 +18,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
+import android.os.UserHandle;
 import android.support.test.filters.SmallTest;
 import android.util.Xml.Encoding;
 
@@ -85,7 +86,7 @@
         ByteArrayOutputStream output = new ByteArrayOutputStream();
         XmlSerializer out = XmlPullParserFactory.newInstance().newSerializer();
         out.setOutput(output, Encoding.UTF_8.name());
-        mAccessList.writeXml(out);
+        mAccessList.writeXml(out, UserHandle.USER_ALL);
         out.flush();
 
         assertEquals(TEST_XML, output.toString(Encoding.UTF_8.name()));