Clear calling identity in BMS.backupNow

BMS.backupNow is called from GMSCore, which has a different calling
identity than the framework. This causes an IllegalArgumentException due
to uid mismatch when backupNow tries to schedule a job.

This occurs when the following conditions are combined:
1) Battery saver mode enabled
2) Network change detected
3) Backup pass is scheduled for now

Bug: 79441902
Test: 1) m -j RunFrameworksServicesRoboTests
2) Manual: Enabled battery saver, modified code to run backupNow with
each network change and overwrite previously scheduled KeyValueJob.
Then, change wifi to trigger scheduling the KeyValueJob.
Verified:
- IllegalStateException b/c of uid mismatch without change
- No exception and correct calling uid with change

Change-Id: Iac90cd435e3fc32ff5428236aa15507b36aa833d
diff --git a/services/backup/java/com/android/server/backup/BackupManagerConstants.java b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
index dd6e6ab..ec21961 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerConstants.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerConstants.java
@@ -32,7 +32,7 @@
  * <p>The backup manager constants are encoded as a key value list separated by commas and stored as
  * a Settings.Secure.
  */
-class BackupManagerConstants extends KeyValueSettingObserver {
+public class BackupManagerConstants extends KeyValueSettingObserver {
     private static final String TAG = "BackupManagerConstants";
     private static final String SETTING = Settings.Secure.BACKUP_MANAGER_CONSTANTS;
 
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index d7db471..cd1bd67 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -353,6 +353,11 @@
         mAlarmManager = alarmManager;
     }
 
+    @VisibleForTesting
+    void setPowerManager(PowerManager powerManager) {
+        mPowerManager = powerManager;
+    }
+
     public void setBackupManagerBinder(IBackupManager backupManagerBinder) {
         mBackupManagerBinder = backupManagerBinder;
     }
@@ -2410,25 +2415,30 @@
     public void backupNow() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "backupNow");
 
-        final PowerSaveState result =
-                mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
-        if (result.batterySaverEnabled) {
-            if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
-            KeyValueBackupJob.schedule(mContext, mConstants);   // try again in several hours
-        } else {
-            if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
-            synchronized (mQueueLock) {
-                // Fire the intent that kicks off the whole shebang...
-                try {
-                    mRunBackupIntent.send();
-                } catch (PendingIntent.CanceledException e) {
-                    // should never happen
-                    Slog.e(TAG, "run-backup intent cancelled!");
-                }
+        long oldId = Binder.clearCallingIdentity();
+        try {
+            final PowerSaveState result =
+                    mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP);
+            if (result.batterySaverEnabled) {
+                if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode");
+                KeyValueBackupJob.schedule(mContext, mConstants);   // try again in several hours
+            } else {
+                if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass");
+                synchronized (mQueueLock) {
+                    // Fire the intent that kicks off the whole shebang...
+                    try {
+                        mRunBackupIntent.send();
+                    } catch (PendingIntent.CanceledException e) {
+                        // should never happen
+                        Slog.e(TAG, "run-backup intent cancelled!");
+                    }
 
-                // ...and cancel any pending scheduled job, because we've just superseded it
-                KeyValueBackupJob.cancel(mContext);
+                    // ...and cancel any pending scheduled job, because we've just superseded it
+                    KeyValueBackupJob.cancel(mContext);
+                }
             }
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
         }
     }
 
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
index 8399aaf..d16bc26 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -22,9 +22,7 @@
 import static com.android.server.backup.testing.TransportData.localTransport;
 import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport;
 import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -43,9 +41,10 @@
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.os.HandlerThread;
+import android.os.PowerManager;
+import android.os.PowerSaveState;
 import android.platform.test.annotations.Presubmit;
 import android.provider.Settings;
-
 import com.android.server.backup.internal.BackupRequest;
 import com.android.server.backup.testing.TransportData;
 import com.android.server.backup.testing.TransportTestUtils.TransportMock;
@@ -54,8 +53,11 @@
 import com.android.server.testing.SystemLoaderPackages;
 import com.android.server.testing.shadows.ShadowAppBackupUtils;
 import com.android.server.testing.shadows.ShadowBackupPolicyEnforcer;
+import com.android.server.testing.shadows.ShadowBinder;
+import com.android.server.testing.shadows.ShadowKeyValueBackupJob;
 import com.android.server.testing.shadows.ShadowPerformBackupTask;
-
+import java.io.File;
+import java.util.List;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -64,6 +66,7 @@
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowContextWrapper;
 import org.robolectric.shadows.ShadowLog;
 import org.robolectric.shadows.ShadowLooper;
@@ -71,9 +74,6 @@
 import org.robolectric.shadows.ShadowSettings;
 import org.robolectric.shadows.ShadowSystemClock;
 
-import java.io.File;
-import java.util.List;
-
 @RunWith(FrameworkRobolectricTestRunner.class)
 @Config(
         manifest = Config.NONE,
@@ -796,6 +796,34 @@
         tearDownForRequestBackup();
     }
 
+    @Test
+    @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJob.class})
+    public void testBackupNow_clearsCallingIdentityForJobScheduler() {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        BackupManagerService backupManagerService = createInitializedBackupManagerService();
+        setUpPowerManager(backupManagerService);
+        ShadowBinder.setCallingUid(1);
+
+        backupManagerService.backupNow();
+
+        assertThat(ShadowKeyValueBackupJob.getCallingUid()).isEqualTo(ShadowBinder.LOCAL_UID);
+        assertThat(ShadowBinder.getCallingUid()).isEqualTo(1);
+    }
+
+    @Test
+    @Config(shadows = {ShadowBinder.class, ShadowKeyValueBackupJobException.class})
+    public void testBackupNow_whenExceptionThrown_restoresCallingIdentity() {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        BackupManagerService backupManagerService = createInitializedBackupManagerService();
+        setUpPowerManager(backupManagerService);
+        ShadowBinder.setCallingUid(1);
+
+        expectThrows(IllegalArgumentException.class, backupManagerService::backupNow);
+        assertThat(ShadowKeyValueBackupJobException.getCallingUid())
+                .isEqualTo(ShadowBinder.LOCAL_UID);
+        assertThat(ShadowBinder.getCallingUid()).isEqualTo(1);
+    }
+
     private BackupManagerService createBackupManagerServiceForRequestBackup() {
         BackupManagerService backupManagerService = createInitializedBackupManagerService();
         backupManagerService.setEnabled(true);
@@ -853,4 +881,23 @@
         ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime());
         return backupManagerService;
     }
+
+    private void setUpPowerManager(BackupManagerService backupManagerService) {
+        PowerManager powerManagerMock = mock(PowerManager.class);
+        when(powerManagerMock.getPowerSaveState(anyInt()))
+                .thenReturn(new PowerSaveState.Builder().setBatterySaverEnabled(true).build());
+        backupManagerService.setPowerManager(powerManagerMock);
+    }
+
+    /**
+     * We can't mock the void method {@link #schedule(Context, long, BackupManagerConstants)} so we
+     * extend {@link ShadowKeyValueBackupJob} and throw an exception at the end of the method.
+     */
+    @Implements(KeyValueBackupJob.class)
+    public static class ShadowKeyValueBackupJobException extends ShadowKeyValueBackupJob {
+        public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+            ShadowKeyValueBackupJob.schedule(ctx, delay, constants);
+            throw new IllegalArgumentException();
+        }
+    }
 }
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
new file mode 100644
index 0000000..043d44b
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBinder.java
@@ -0,0 +1,44 @@
+/*
+ * 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.testing.shadows;
+
+import android.os.Binder;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+/**
+ * Extends {@link org.robolectric.shadows.ShadowBinder} with {@link Binder#clearCallingIdentity()}
+ * and {@link Binder#restoreCallingIdentity(long)}. Uses a hardcoded default {@link #LOCAL_UID} to
+ * mimic the local process uid.
+ */
+@Implements(Binder.class)
+public class ShadowBinder extends org.robolectric.shadows.ShadowBinder {
+    public static final Integer LOCAL_UID = 1000;
+    private static Integer originalCallingUid;
+
+    @Implementation
+    public static long clearCallingIdentity() {
+        originalCallingUid = getCallingUid();
+        setCallingUid(LOCAL_UID);
+        return 1L;
+    }
+
+    @Implementation
+    public static void restoreCallingIdentity(long token) {
+        setCallingUid(originalCallingUid);
+    }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
new file mode 100644
index 0000000..3941f17
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java
@@ -0,0 +1,38 @@
+/*
+ * 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.testing.shadows;
+
+import android.content.Context;
+import android.os.Binder;
+import com.android.server.backup.BackupManagerConstants;
+import com.android.server.backup.KeyValueBackupJob;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(KeyValueBackupJob.class)
+public class ShadowKeyValueBackupJob {
+    private static int callingUid;
+
+    public static int getCallingUid() {
+        return callingUid;
+    }
+
+    @Implementation
+    public static void schedule(Context ctx, long delay, BackupManagerConstants constants) {
+        callingUid = Binder.getCallingUid();
+    }
+}