1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.wifi; 18 19 import android.util.Log; 20 import android.util.Xml; 21 22 import com.android.internal.util.FastXmlSerializer; 23 import com.android.server.wifi.util.XmlUtil; 24 25 import org.xmlpull.v1.XmlPullParser; 26 import org.xmlpull.v1.XmlPullParserException; 27 import org.xmlpull.v1.XmlSerializer; 28 29 import java.io.ByteArrayInputStream; 30 import java.io.ByteArrayOutputStream; 31 import java.io.FileDescriptor; 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.io.UnsupportedEncodingException; 35 import java.nio.charset.StandardCharsets; 36 import java.text.SimpleDateFormat; 37 import java.util.Date; 38 39 /** 40 * Class used to backup/restore data. 41 */ 42 public class BackupRestoreController { 43 private static final String TAG = "BackupRestoreController"; 44 45 private final WifiSettingsBackupRestore mWifiSettingsBackupRestore; 46 private final Clock mClock; 47 48 private static final String XML_TAG_DOCUMENT_HEADER = "WifiSettingsBackupData"; 49 private static final SimpleDateFormat FORMATTER = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); 50 51 /** 52 * Verbose logging flag. 53 */ 54 private boolean mVerboseLoggingEnabled = false; 55 /** 56 * Store the dump of the backup/restore data for debugging. This is only stored when verbose 57 * logging is enabled. 58 */ 59 private byte[] mDebugLastBackupDataRetrieved; 60 private long mLastBackupDataRetrievedTimestamp = 0; 61 private byte[] mDebugLastBackupDataRestored; 62 private long mLastBackupDataRestoredTimestamp = 0; 63 BackupRestoreController(WifiSettingsBackupRestore wifiSettingsBackupRestore, Clock clock)64 public BackupRestoreController(WifiSettingsBackupRestore wifiSettingsBackupRestore, 65 Clock clock) { 66 mWifiSettingsBackupRestore = wifiSettingsBackupRestore; 67 mClock = clock; 68 } 69 70 /** 71 * Retrieve an XML byte stream representing the data that needs to be backed up. 72 * 73 * @return Raw byte stream of XML that needs to be backed up. 74 */ retrieveBackupData()75 public byte[] retrieveBackupData() { 76 try { 77 final XmlSerializer out = new FastXmlSerializer(); 78 final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 79 out.setOutput(outputStream, StandardCharsets.UTF_8.name()); 80 XmlUtil.writeDocumentStart(out, XML_TAG_DOCUMENT_HEADER); 81 82 mWifiSettingsBackupRestore.retrieveBackupDataFromSettingsConfigStore(out, outputStream); 83 84 XmlUtil.writeDocumentEnd(out, XML_TAG_DOCUMENT_HEADER); 85 byte[] backupData = outputStream.toByteArray(); 86 if (mVerboseLoggingEnabled) { 87 mDebugLastBackupDataRetrieved = backupData; 88 } 89 mLastBackupDataRetrievedTimestamp = mClock.getWallClockMillis(); 90 return backupData; 91 } catch (IOException e) { 92 Log.e(TAG, "Error retrieving the backup data: " + e); 93 } 94 return new byte[0]; 95 } 96 distpatchBackupData(String sectionName, XmlPullParser in, int depth)97 private void distpatchBackupData(String sectionName, XmlPullParser in, int depth) 98 throws XmlPullParserException, IOException { 99 switch (sectionName) { 100 case WifiSettingsBackupRestore.XML_TAG_SECTION_HEADER_WIFI_SETTINGS_DATA: 101 mWifiSettingsBackupRestore.restoreSettingsFromBackupData(in, depth); 102 break; 103 default: 104 Log.i(TAG, "unknown tag: (backed up from newer version?)" + sectionName); 105 } 106 } 107 108 /** 109 * Split the back up data to retrieve each back up session. 110 * 111 * @param data raw byte stream representing the XML data. 112 */ parserBackupDataAndDispatch(byte[] data)113 public void parserBackupDataAndDispatch(byte[] data) { 114 if (data == null || data.length == 0) { 115 Log.e(TAG, "Invalid backup data received"); 116 return; 117 } 118 if (mVerboseLoggingEnabled) { 119 mDebugLastBackupDataRestored = data; 120 } 121 try { 122 final XmlPullParser in = Xml.newPullParser(); 123 ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 124 in.setInput(inputStream, StandardCharsets.UTF_8.name()); 125 // Start parsing the XML stream. 126 XmlUtil.gotoDocumentStart(in, XML_TAG_DOCUMENT_HEADER); 127 int sectionDepth = in.getDepth(); 128 String[] sectionName = new String[1]; 129 while (XmlUtil.gotoNextSectionOrEnd(in, sectionName, sectionDepth)) { 130 try { 131 if (sectionName[0] == null) { 132 throw new XmlPullParserException("Missing value name"); 133 } 134 distpatchBackupData(sectionName[0], in, sectionDepth); 135 } catch (XmlPullParserException | IOException ex) { 136 Log.e(TAG, "Error to parser tag: " + sectionName[0]); 137 } 138 } 139 mLastBackupDataRestoredTimestamp = mClock.getWallClockMillis(); 140 } catch (XmlPullParserException | IOException ex) { 141 Log.e(TAG, "Error :" + ex); 142 } 143 144 } 145 146 /** 147 * Enable verbose logging. 148 * 149 * @param verboseEnabled whether or not verbosity log level is enabled. 150 */ enableVerboseLogging(boolean verboseEnabled)151 public void enableVerboseLogging(boolean verboseEnabled) { 152 mVerboseLoggingEnabled = verboseEnabled; 153 } 154 155 /** 156 * Dump out the last backup/restore data if verbose logging is enabled. 157 * 158 * @param fd unused 159 * @param pw PrintWriter for writing dump to 160 * @param args unused 161 */ dump(FileDescriptor fd, PrintWriter pw, String[] args)162 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 163 pw.println("Dump of " + TAG); 164 if (mDebugLastBackupDataRetrieved != null) { 165 pw.println("Last backup data retrieved: " 166 + createLogFromBackupData(mDebugLastBackupDataRetrieved)); 167 168 } 169 pw.println("mLastBackupDataRetrievedTimestamp: " 170 + (mLastBackupDataRetrievedTimestamp != 0 171 ? FORMATTER.format(new Date(mLastBackupDataRetrievedTimestamp)) : "N/A")); 172 if (mDebugLastBackupDataRestored != null) { 173 pw.println("Last backup data restored: " 174 + createLogFromBackupData(mDebugLastBackupDataRestored)); 175 } 176 pw.println("mLastBackupDataRestoredTimestamp: " 177 + (mLastBackupDataRestoredTimestamp != 0 178 ? FORMATTER.format(new Date(mLastBackupDataRestoredTimestamp)) : "N/A")); 179 } 180 createLogFromBackupData(byte[] data)181 private String createLogFromBackupData(byte[] data) { 182 if (data != null) { 183 StringBuilder sb = new StringBuilder(); 184 try { 185 String xmlString = new String(data, StandardCharsets.UTF_8.name()); 186 for (String line : xmlString.split("\n")) { 187 sb.append(line).append("\n"); 188 } 189 return sb.toString(); 190 } catch (UnsupportedEncodingException e) { 191 Log.e(TAG, "fail to create log from backup data. " + e); 192 } 193 } 194 return ""; 195 } 196 } 197