1 /* 2 * Copyright (C) 2012 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.display; 18 19 import com.android.internal.util.FastXmlSerializer; 20 import com.android.internal.util.XmlUtils; 21 22 import org.xmlpull.v1.XmlPullParser; 23 import org.xmlpull.v1.XmlPullParserException; 24 import org.xmlpull.v1.XmlSerializer; 25 26 import android.hardware.display.WifiDisplay; 27 import android.util.AtomicFile; 28 import android.util.Slog; 29 import android.util.Xml; 30 31 import java.io.BufferedInputStream; 32 import java.io.BufferedOutputStream; 33 import java.io.File; 34 import java.io.FileNotFoundException; 35 import java.io.FileOutputStream; 36 import java.io.IOException; 37 import java.io.InputStream; 38 import java.nio.charset.StandardCharsets; 39 import java.util.ArrayList; 40 41 import libcore.io.IoUtils; 42 import libcore.util.Objects; 43 44 /** 45 * Manages persistent state recorded by the display manager service as an XML file. 46 * Caller must acquire lock on the data store before accessing it. 47 * 48 * File format: 49 * <code> 50 * <display-manager-state> 51 * <remembered-wifi-displays> 52 * <wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" /> 53 * >remembered-wifi-displays> 54 * >/display-manager-state> 55 * </code> 56 * 57 * TODO: refactor this to extract common code shared with the input manager's data store 58 */ 59 final class PersistentDataStore { 60 static final String TAG = "DisplayManager"; 61 62 // Remembered Wifi display devices. 63 private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>(); 64 65 // The atomic file used to safely read or write the file. 66 private final AtomicFile mAtomicFile; 67 68 // True if the data has been loaded. 69 private boolean mLoaded; 70 71 // True if there are changes to be saved. 72 private boolean mDirty; 73 PersistentDataStore()74 public PersistentDataStore() { 75 mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml")); 76 } 77 saveIfNeeded()78 public void saveIfNeeded() { 79 if (mDirty) { 80 save(); 81 mDirty = false; 82 } 83 } 84 getRememberedWifiDisplay(String deviceAddress)85 public WifiDisplay getRememberedWifiDisplay(String deviceAddress) { 86 loadIfNeeded(); 87 int index = findRememberedWifiDisplay(deviceAddress); 88 if (index >= 0) { 89 return mRememberedWifiDisplays.get(index); 90 } 91 return null; 92 } 93 getRememberedWifiDisplays()94 public WifiDisplay[] getRememberedWifiDisplays() { 95 loadIfNeeded(); 96 return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]); 97 } 98 applyWifiDisplayAlias(WifiDisplay display)99 public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) { 100 if (display != null) { 101 loadIfNeeded(); 102 103 String alias = null; 104 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 105 if (index >= 0) { 106 alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); 107 } 108 if (!Objects.equal(display.getDeviceAlias(), alias)) { 109 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), 110 alias, display.isAvailable(), display.canConnect(), display.isRemembered()); 111 } 112 } 113 return display; 114 } 115 applyWifiDisplayAliases(WifiDisplay[] displays)116 public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) { 117 WifiDisplay[] results = displays; 118 if (results != null) { 119 int count = displays.length; 120 for (int i = 0; i < count; i++) { 121 WifiDisplay result = applyWifiDisplayAlias(displays[i]); 122 if (result != displays[i]) { 123 if (results == displays) { 124 results = new WifiDisplay[count]; 125 System.arraycopy(displays, 0, results, 0, count); 126 } 127 results[i] = result; 128 } 129 } 130 } 131 return results; 132 } 133 rememberWifiDisplay(WifiDisplay display)134 public boolean rememberWifiDisplay(WifiDisplay display) { 135 loadIfNeeded(); 136 137 int index = findRememberedWifiDisplay(display.getDeviceAddress()); 138 if (index >= 0) { 139 WifiDisplay other = mRememberedWifiDisplays.get(index); 140 if (other.equals(display)) { 141 return false; // already remembered without change 142 } 143 mRememberedWifiDisplays.set(index, display); 144 } else { 145 mRememberedWifiDisplays.add(display); 146 } 147 setDirty(); 148 return true; 149 } 150 forgetWifiDisplay(String deviceAddress)151 public boolean forgetWifiDisplay(String deviceAddress) { 152 int index = findRememberedWifiDisplay(deviceAddress); 153 if (index >= 0) { 154 mRememberedWifiDisplays.remove(index); 155 setDirty(); 156 return true; 157 } 158 return false; 159 } 160 findRememberedWifiDisplay(String deviceAddress)161 private int findRememberedWifiDisplay(String deviceAddress) { 162 int count = mRememberedWifiDisplays.size(); 163 for (int i = 0; i < count; i++) { 164 if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) { 165 return i; 166 } 167 } 168 return -1; 169 } 170 loadIfNeeded()171 private void loadIfNeeded() { 172 if (!mLoaded) { 173 load(); 174 mLoaded = true; 175 } 176 } 177 setDirty()178 private void setDirty() { 179 mDirty = true; 180 } 181 clearState()182 private void clearState() { 183 mRememberedWifiDisplays.clear(); 184 } 185 load()186 private void load() { 187 clearState(); 188 189 final InputStream is; 190 try { 191 is = mAtomicFile.openRead(); 192 } catch (FileNotFoundException ex) { 193 return; 194 } 195 196 XmlPullParser parser; 197 try { 198 parser = Xml.newPullParser(); 199 parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name()); 200 loadFromXml(parser); 201 } catch (IOException ex) { 202 Slog.w(TAG, "Failed to load display manager persistent store data.", ex); 203 clearState(); 204 } catch (XmlPullParserException ex) { 205 Slog.w(TAG, "Failed to load display manager persistent store data.", ex); 206 clearState(); 207 } finally { 208 IoUtils.closeQuietly(is); 209 } 210 } 211 save()212 private void save() { 213 final FileOutputStream os; 214 try { 215 os = mAtomicFile.startWrite(); 216 boolean success = false; 217 try { 218 XmlSerializer serializer = new FastXmlSerializer(); 219 serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name()); 220 saveToXml(serializer); 221 serializer.flush(); 222 success = true; 223 } finally { 224 if (success) { 225 mAtomicFile.finishWrite(os); 226 } else { 227 mAtomicFile.failWrite(os); 228 } 229 } 230 } catch (IOException ex) { 231 Slog.w(TAG, "Failed to save display manager persistent store data.", ex); 232 } 233 } 234 loadFromXml(XmlPullParser parser)235 private void loadFromXml(XmlPullParser parser) 236 throws IOException, XmlPullParserException { 237 XmlUtils.beginDocument(parser, "display-manager-state"); 238 final int outerDepth = parser.getDepth(); 239 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 240 if (parser.getName().equals("remembered-wifi-displays")) { 241 loadRememberedWifiDisplaysFromXml(parser); 242 } 243 } 244 } 245 loadRememberedWifiDisplaysFromXml(XmlPullParser parser)246 private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser) 247 throws IOException, XmlPullParserException { 248 final int outerDepth = parser.getDepth(); 249 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 250 if (parser.getName().equals("wifi-display")) { 251 String deviceAddress = parser.getAttributeValue(null, "deviceAddress"); 252 String deviceName = parser.getAttributeValue(null, "deviceName"); 253 String deviceAlias = parser.getAttributeValue(null, "deviceAlias"); 254 if (deviceAddress == null || deviceName == null) { 255 throw new XmlPullParserException( 256 "Missing deviceAddress or deviceName attribute on wifi-display."); 257 } 258 if (findRememberedWifiDisplay(deviceAddress) >= 0) { 259 throw new XmlPullParserException( 260 "Found duplicate wifi display device address."); 261 } 262 263 mRememberedWifiDisplays.add( 264 new WifiDisplay(deviceAddress, deviceName, deviceAlias, 265 false, false, false)); 266 } 267 } 268 } 269 saveToXml(XmlSerializer serializer)270 private void saveToXml(XmlSerializer serializer) throws IOException { 271 serializer.startDocument(null, true); 272 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 273 serializer.startTag(null, "display-manager-state"); 274 serializer.startTag(null, "remembered-wifi-displays"); 275 for (WifiDisplay display : mRememberedWifiDisplays) { 276 serializer.startTag(null, "wifi-display"); 277 serializer.attribute(null, "deviceAddress", display.getDeviceAddress()); 278 serializer.attribute(null, "deviceName", display.getDeviceName()); 279 if (display.getDeviceAlias() != null) { 280 serializer.attribute(null, "deviceAlias", display.getDeviceAlias()); 281 } 282 serializer.endTag(null, "wifi-display"); 283 } 284 serializer.endTag(null, "remembered-wifi-displays"); 285 serializer.endTag(null, "display-manager-state"); 286 serializer.endDocument(); 287 } 288 } 289