Merge "[SettingsState] fix boot loop due to corrupted database file" into rvc-dev am: 36ea45baa1
Change-Id: I1c6c69ae1555ea481c69f2ae4eea371cd23b55e6
This commit is contained in:
@@ -21,5 +21,10 @@
|
|||||||
android:singleUser="true"
|
android:singleUser="true"
|
||||||
android:initOrder="100"
|
android:initOrder="100"
|
||||||
android:visibleToInstantApps="true" />
|
android:visibleToInstantApps="true" />
|
||||||
|
|
||||||
|
<service
|
||||||
|
android:name="WriteFallbackSettingsFilesJobService"
|
||||||
|
android:permission="android.permission.BIND_JOB_SERVICE"
|
||||||
|
android:exported="true"/>
|
||||||
</application>
|
</application>
|
||||||
</manifest>
|
</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.provider.Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_MAGNIFICATION_CONTROLLER;
|
||||||
import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_2BUTTON_OVERLAY;
|
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.Manifest;
|
||||||
import android.annotation.NonNull;
|
import android.annotation.NonNull;
|
||||||
import android.annotation.Nullable;
|
import android.annotation.Nullable;
|
||||||
import android.app.ActivityManager;
|
import android.app.ActivityManager;
|
||||||
import android.app.AppGlobals;
|
import android.app.AppGlobals;
|
||||||
import android.app.backup.BackupManager;
|
import android.app.backup.BackupManager;
|
||||||
|
import android.app.job.JobInfo;
|
||||||
|
import android.app.job.JobScheduler;
|
||||||
import android.content.BroadcastReceiver;
|
import android.content.BroadcastReceiver;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.ContentProvider;
|
import android.content.ContentProvider;
|
||||||
@@ -55,12 +59,14 @@ import android.os.Build;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.DropBoxManager;
|
import android.os.DropBoxManager;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.os.FileUtils;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.HandlerThread;
|
import android.os.HandlerThread;
|
||||||
import android.os.IUserRestrictionsListener;
|
import android.os.IUserRestrictionsListener;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.os.PersistableBundle;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
import android.os.RemoteCallback;
|
import android.os.RemoteCallback;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
@@ -95,6 +101,7 @@ import libcore.util.HexEncoding;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
@@ -154,9 +161,11 @@ public class SettingsProvider extends ContentProvider {
|
|||||||
|
|
||||||
private static final String LOG_TAG = "SettingsProvider";
|
private static final String LOG_TAG = "SettingsProvider";
|
||||||
|
|
||||||
private static final String TABLE_SYSTEM = "system";
|
public static final String TABLE_SYSTEM = "system";
|
||||||
private static final String TABLE_SECURE = "secure";
|
public static final String TABLE_SECURE = "secure";
|
||||||
private static final String TABLE_GLOBAL = "global";
|
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.
|
// Old tables no longer exist.
|
||||||
private static final String TABLE_FAVORITES = "favorites";
|
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_ROWS_DELETED = "result_rows_deleted";
|
||||||
public static final String RESULT_SETTINGS_LIST = "result_settings_list";
|
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
|
// 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_GLOBAL_INSTANT_APP_SETTINGS = new ArraySet<>();
|
||||||
private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
|
private static final Set<String> OVERLAY_ALLOWED_SYSTEM_INSTANT_APP_SETTINGS = new ArraySet<>();
|
||||||
@@ -2484,6 +2497,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 {
|
final class SettingsRegistry {
|
||||||
private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
|
private static final String DROPBOX_TAG_USERLOG = "restricted_profile_ssaid";
|
||||||
|
|
||||||
@@ -3431,6 +3506,7 @@ public class SettingsProvider extends ContentProvider {
|
|||||||
|
|
||||||
case MSG_NOTIFY_DATA_CHANGED: {
|
case MSG_NOTIFY_DATA_CHANGED: {
|
||||||
mBackupManager.dataChanged();
|
mBackupManager.dataChanged();
|
||||||
|
scheduleWriteFallbackFilesJob();
|
||||||
} break;
|
} break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import android.content.pm.PackageManager;
|
|||||||
import android.content.pm.Signature;
|
import android.content.pm.Signature;
|
||||||
import android.os.Binder;
|
import android.os.Binder;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.FileUtils;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
@@ -101,6 +102,8 @@ final class SettingsState {
|
|||||||
|
|
||||||
public static final int VERSION_UNDEFINED = -1;
|
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_SETTINGS = "settings";
|
||||||
private static final String TAG_SETTING = "setting";
|
private static final String TAG_SETTING = "setting";
|
||||||
private static final String ATTR_PACKAGE = "package";
|
private static final String ATTR_PACKAGE = "package";
|
||||||
@@ -266,7 +269,7 @@ final class SettingsState {
|
|||||||
public SettingsState(Context context, Object lock, File file, int key,
|
public SettingsState(Context context, Object lock, File file, int key,
|
||||||
int maxBytesPerAppPackage, Looper looper) {
|
int maxBytesPerAppPackage, Looper looper) {
|
||||||
// It is important that we use the same lock as the settings provider
|
// 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.
|
// as the async persistence should be blocked while we make changes.
|
||||||
mContext = context;
|
mContext = context;
|
||||||
mLock = lock;
|
mLock = lock;
|
||||||
@@ -998,24 +1001,56 @@ final class SettingsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GuardedBy("mLock")
|
@GuardedBy("mLock")
|
||||||
private void readStateSyncLocked() {
|
private void readStateSyncLocked() throws IllegalStateException {
|
||||||
FileInputStream in;
|
FileInputStream in;
|
||||||
|
AtomicFile file = new AtomicFile(mStatePersistFile);
|
||||||
try {
|
try {
|
||||||
in = new AtomicFile(mStatePersistFile).openRead();
|
in = file.openRead();
|
||||||
} catch (FileNotFoundException fnfe) {
|
} catch (FileNotFoundException fnfe) {
|
||||||
Slog.i(LOG_TAG, "No settings state " + mStatePersistFile);
|
Slog.w(LOG_TAG, "No settings state " + mStatePersistFile);
|
||||||
logSettingsDirectoryInformation(mStatePersistFile);
|
logSettingsDirectoryInformation(mStatePersistFile);
|
||||||
addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
|
addHistoricalOperationLocked(HISTORICAL_OPERATION_INITIALIZE, null);
|
||||||
return;
|
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 {
|
try {
|
||||||
XmlPullParser parser = Xml.newPullParser();
|
XmlPullParser parser = Xml.newPullParser();
|
||||||
parser.setInput(in, StandardCharsets.UTF_8.name());
|
parser.setInput(in, StandardCharsets.UTF_8.name());
|
||||||
parseStateLocked(parser);
|
parseStateLocked(parser);
|
||||||
|
return true;
|
||||||
} catch (XmlPullParserException | IOException e) {
|
} catch (XmlPullParserException | IOException e) {
|
||||||
String message = "Failed parsing settings file: " + mStatePersistFile;
|
return false;
|
||||||
Slog.wtf(LOG_TAG, message);
|
|
||||||
throw new IllegalStateException(message, e);
|
|
||||||
} finally {
|
} finally {
|
||||||
IoUtils.closeQuietly(in);
|
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