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:
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user