Merge "[SettingsState] fix boot loop due to corrupted database file" into rvc-dev am: 36ea45baa1 am: 6353c1a316 am: 5c565b2a45 am: 2f30242a5e

Change-Id: Iadc3f065ef6a25a9e28ab5dd096addb62c3aca7e
This commit is contained in:
TreeHugger Robot
2020-04-28 05:23:15 +00:00
committed by Automerger Merge Worker
4 changed files with 184 additions and 10 deletions

View File

@@ -21,5 +21,10 @@
android:singleUser="true"
android:initOrder="100"
android:visibleToInstantApps="true" />
<service
android:name="WriteFallbackSettingsFilesJobService"
android:permission="android.permission.BIND_JOB_SERVICE"
android:exported="true"/>
</application>
</manifest>

View File

@@ -23,12 +23,16 @@ import static android.os.Process.SYSTEM_UID;
import static android.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
import static com.android.providers.settings.SettingsState.FALLBACK_FILE_SUFFIX;
import android.Manifest;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.AppGlobals;
import android.app.backup.BackupManager;
import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProvider;
@@ -55,12 +59,14 @@ import android.os.Build;
import android.os.Bundle;
import android.os.DropBoxManager;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IUserRestrictionsListener;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.os.Process;
import android.os.RemoteCallback;
import android.os.RemoteException;
@@ -95,6 +101,7 @@ import libcore.util.HexEncoding;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
@@ -154,9 +161,11 @@ public class SettingsProvider extends ContentProvider {
private static final String LOG_TAG = "SettingsProvider";
private static final String TABLE_SYSTEM = "system";
private static final String TABLE_SECURE = "secure";
private static final String TABLE_GLOBAL = "global";
public static final String TABLE_SYSTEM = "system";
public static final String TABLE_SECURE = "secure";
public static final String TABLE_GLOBAL = "global";
public static final String TABLE_SSAID = "ssaid";
public static final String TABLE_CONFIG = "config";
// Old tables no longer exist.
private static final String TABLE_FAVORITES = "favorites";
@@ -205,6 +214,10 @@ public class SettingsProvider extends ContentProvider {
public static final String RESULT_ROWS_DELETED = "result_rows_deleted";
public static final String RESULT_SETTINGS_LIST = "result_settings_list";
// Used for scheduling jobs to make a copy for the settings files
public static final int WRITE_FALLBACK_SETTINGS_FILES_JOB_ID = 1;
public static final long ONE_DAY_INTERVAL_MILLIS = 24 * 60 * 60 * 1000L;
// Overlay specified settings whitelisted for Instant Apps
private static final Set<String> OVERLAY_ALLOWED_GLOBAL_INSTANT_APP_SETTINGS = new ArraySet<>();
private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
@@ -2385,6 +2398,68 @@ public class SettingsProvider extends ContentProvider {
}
}
/**
* Schedule the job service to make a copy of all the settings files.
*/
public void scheduleWriteFallbackFilesJob() {
final Context context = getContext();
final JobScheduler jobScheduler =
(JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
if (jobScheduler == null) {
// Might happen: SettingsProvider is created before JobSchedulerService in system server
return;
}
// Check if the job is already scheduled. If so, skip scheduling another one
if (jobScheduler.getPendingJob(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID) != null) {
return;
}
// Back up all settings files
final PersistableBundle bundle = new PersistableBundle();
final File globalSettingsFile = mSettingsRegistry.getSettingsFile(
makeKey(SETTINGS_TYPE_GLOBAL, UserHandle.USER_SYSTEM));
final File systemSettingsFile = mSettingsRegistry.getSettingsFile(
makeKey(SETTINGS_TYPE_SYSTEM, UserHandle.USER_SYSTEM));
final File secureSettingsFile = mSettingsRegistry.getSettingsFile(
makeKey(SETTINGS_TYPE_SECURE, UserHandle.USER_SYSTEM));
final File ssaidSettingsFile = mSettingsRegistry.getSettingsFile(
makeKey(SETTINGS_TYPE_SSAID, UserHandle.USER_SYSTEM));
final File configSettingsFile = mSettingsRegistry.getSettingsFile(
makeKey(SETTINGS_TYPE_CONFIG, UserHandle.USER_SYSTEM));
bundle.putString(TABLE_GLOBAL, globalSettingsFile.getAbsolutePath());
bundle.putString(TABLE_SYSTEM, systemSettingsFile.getAbsolutePath());
bundle.putString(TABLE_SECURE, secureSettingsFile.getAbsolutePath());
bundle.putString(TABLE_SSAID, ssaidSettingsFile.getAbsolutePath());
bundle.putString(TABLE_CONFIG, configSettingsFile.getAbsolutePath());
// Schedule the job to write the fallback files, once daily when phone is charging
jobScheduler.schedule(new JobInfo.Builder(WRITE_FALLBACK_SETTINGS_FILES_JOB_ID,
new ComponentName(context, WriteFallbackSettingsFilesJobService.class))
.setExtras(bundle)
.setPeriodic(ONE_DAY_INTERVAL_MILLIS)
.setRequiresCharging(true)
.setPersisted(true)
.build());
}
/**
* For each file in the given list, if it exists, copy it to a back up file. Ignore failures.
* @param filePaths List of paths of files that need to be backed up
*/
public static void writeFallBackSettingsFiles(List<String> filePaths) {
final int numFiles = filePaths.size();
for (int i = 0; i < numFiles; i++) {
final String filePath = filePaths.get(i);
final File originalFile = new File(filePath);
if (SettingsState.stateFileExists(originalFile)) {
final File fallBackFile = new File(filePath + FALLBACK_FILE_SUFFIX);
try {
FileUtils.copy(originalFile, fallBackFile);
} catch (IOException ex) {
Slog.w(LOG_TAG, "Failed to write fallback file for: " + filePath);
}
}
}
}
final class SettingsRegistry {
private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
@@ -3332,6 +3407,7 @@ public class SettingsProvider extends ContentProvider {
case MSG_NOTIFY_DATA_CHANGED: {
mBackupManager.dataChanged();
scheduleWriteFallbackFilesJob();
} break;
}
}

View File

@@ -27,6 +27,7 @@ import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Binder;
import android.os.Build;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -101,6 +102,8 @@ final class SettingsState {
public static final int VERSION_UNDEFINED = -1;
public static final String FALLBACK_FILE_SUFFIX = ".fallback";
private static final String TAG_SETTINGS = "settings";
private static final String TAG_SETTING = "setting";
private static final String ATTR_PACKAGE = "package";
@@ -266,7 +269,7 @@ final class SettingsState {
public SettingsState(Context context, Object lock, File file, int key,
int maxBytesPerAppPackage, Looper looper) {
// It is important that we use the same lock as the settings provider
// to ensure multiple mutations on this state are atomicaly persisted
// to ensure multiple mutations on this state are atomically persisted
// as the async persistence should be blocked while we make changes.
mContext = context;
mLock = lock;
@@ -998,24 +1001,56 @@ final class SettingsState {
}
@GuardedBy("mLock")
private void readStateSyncLocked() {
private void readStateSyncLocked() throws IllegalStateException {
FileInputStream in;
AtomicFile file = new AtomicFile(mStatePersistFile);
try {
in = new AtomicFile(mStatePersistFile).openRead();
in = file.openRead();
} catch (FileNotFoundException fnfe) {
Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
Slog.w(LOG_TAG, "No settings state " + mStatePersistFile);
logSettingsDirectoryInformation(mStatePersistFile);
addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
return;
}
if (parseStateFromXmlStreamLocked(in)) {
return;
}
// Settings file exists but is corrupted. Retry with the fallback file
final File statePersistFallbackFile = new File(
mStatePersistFile.getAbsolutePath() + FALLBACK_FILE_SUFFIX);
Slog.i(LOG_TAG, "Failed parsing settings file: " + mStatePersistFile
+ ", retrying with fallback file: " + statePersistFallbackFile);
try {
in = new AtomicFile(statePersistFallbackFile).openRead();
} catch (FileNotFoundException fnfe) {
final String message = "No fallback file found for: " + mStatePersistFile;
Slog.wtf(LOG_TAG, message);
throw new IllegalStateException(message);
}
if (parseStateFromXmlStreamLocked(in)) {
// Parsed state from fallback file. Restore original file with fallback file
try {
FileUtils.copy(statePersistFallbackFile, mStatePersistFile);
} catch (IOException ignored) {
// Failed to copy, but it's okay because we already parsed states from fallback file
}
} else {
final String message = "Failed parsing settings file: " + mStatePersistFile;
Slog.wtf(LOG_TAG, message);
throw new IllegalStateException(message);
}
}
@GuardedBy("mLock")
private boolean parseStateFromXmlStreamLocked(FileInputStream in) {
try {
XmlPullParser parser = Xml.newPullParser();
parser.setInput(in, StandardCharsets.UTF_8.name());
parseStateLocked(parser);
return true;
} catch (XmlPullParserException | IOException e) {
String message = "Failed parsing settings file: " + mStatePersistFile;
Slog.wtf(LOG_TAG, message);
throw new IllegalStateException(message, e);
return false;
} finally {
IoUtils.closeQuietly(in);
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2020 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.providers.settings;
import static com.android.providers.settings.SettingsProvider.TABLE_CONFIG;
import static com.android.providers.settings.SettingsProvider.TABLE_GLOBAL;
import static com.android.providers.settings.SettingsProvider.TABLE_SECURE;
import static com.android.providers.settings.SettingsProvider.TABLE_SSAID;
import static com.android.providers.settings.SettingsProvider.TABLE_SYSTEM;
import static com.android.providers.settings.SettingsProvider.WRITE_FALLBACK_SETTINGS_FILES_JOB_ID;
import android.app.job.JobParameters;
import android.app.job.JobService;
import java.util.ArrayList;
import java.util.List;
/**
* JobService to make a copy of a list of files, given their paths.
*/
public class WriteFallbackSettingsFilesJobService extends JobService {
@Override
public boolean onStartJob(final JobParameters params) {
switch (params.getJobId()) {
case WRITE_FALLBACK_SETTINGS_FILES_JOB_ID:
final List<String> settingsFiles = new ArrayList<>();
settingsFiles.add(params.getExtras().getString(TABLE_GLOBAL, ""));
settingsFiles.add(params.getExtras().getString(TABLE_SYSTEM, ""));
settingsFiles.add(params.getExtras().getString(TABLE_SECURE, ""));
settingsFiles.add(params.getExtras().getString(TABLE_SSAID, ""));
settingsFiles.add(params.getExtras().getString(TABLE_CONFIG, ""));
SettingsProvider.writeFallBackSettingsFiles(settingsFiles);
return true;
default:
return false;
}
}
@Override
public boolean onStopJob(JobParameters params) {
return false;
}
}