/* * Copyright (C) 2012 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.server.display; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import android.hardware.display.WifiDisplay; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import libcore.io.IoUtils; import libcore.util.Objects; /** * Manages persistent state recorded by the display manager service as an XML file. * Caller must acquire lock on the data store before accessing it. * * File format: * * <display-manager-state> * <remembered-wifi-displays> * <wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" /> * >remembered-wifi-displays> * >/display-manager-state> * * * TODO: refactor this to extract common code shared with the input manager's data store */ final class PersistentDataStore { static final String TAG = "DisplayManager"; // Remembered Wifi display devices. private ArrayList mRememberedWifiDisplays = new ArrayList(); // The atomic file used to safely read or write the file. private final AtomicFile mAtomicFile; // True if the data has been loaded. private boolean mLoaded; // True if there are changes to be saved. private boolean mDirty; public PersistentDataStore() { mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml")); } public void saveIfNeeded() { if (mDirty) { save(); mDirty = false; } } public WifiDisplay getRememberedWifiDisplay(String deviceAddress) { loadIfNeeded(); int index = findRememberedWifiDisplay(deviceAddress); if (index >= 0) { return mRememberedWifiDisplays.get(index); } return null; } public WifiDisplay[] getRememberedWifiDisplays() { loadIfNeeded(); return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]); } public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) { if (display != null) { loadIfNeeded(); String alias = null; int index = findRememberedWifiDisplay(display.getDeviceAddress()); if (index >= 0) { alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); } if (!Objects.equal(display.getDeviceAlias(), alias)) { return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), alias, display.isAvailable(), display.canConnect(), display.isRemembered()); } } return display; } public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) { WifiDisplay[] results = displays; if (results != null) { int count = displays.length; for (int i = 0; i < count; i++) { WifiDisplay result = applyWifiDisplayAlias(displays[i]); if (result != displays[i]) { if (results == displays) { results = new WifiDisplay[count]; System.arraycopy(displays, 0, results, 0, count); } results[i] = result; } } } return results; } public boolean rememberWifiDisplay(WifiDisplay display) { loadIfNeeded(); int index = findRememberedWifiDisplay(display.getDeviceAddress()); if (index >= 0) { WifiDisplay other = mRememberedWifiDisplays.get(index); if (other.equals(display)) { return false; // already remembered without change } mRememberedWifiDisplays.set(index, display); } else { mRememberedWifiDisplays.add(display); } setDirty(); return true; } public boolean forgetWifiDisplay(String deviceAddress) { int index = findRememberedWifiDisplay(deviceAddress); if (index >= 0) { mRememberedWifiDisplays.remove(index); setDirty(); return true; } return false; } private int findRememberedWifiDisplay(String deviceAddress) { int count = mRememberedWifiDisplays.size(); for (int i = 0; i < count; i++) { if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) { return i; } } return -1; } private void loadIfNeeded() { if (!mLoaded) { load(); mLoaded = true; } } private void setDirty() { mDirty = true; } private void clearState() { mRememberedWifiDisplays.clear(); } private void load() { clearState(); final InputStream is; try { is = mAtomicFile.openRead(); } catch (FileNotFoundException ex) { return; } XmlPullParser parser; try { parser = Xml.newPullParser(); parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name()); loadFromXml(parser); } catch (IOException ex) { Slog.w(TAG, "Failed to load display manager persistent store data.", ex); clearState(); } catch (XmlPullParserException ex) { Slog.w(TAG, "Failed to load display manager persistent store data.", ex); clearState(); } finally { IoUtils.closeQuietly(is); } } private void save() { final FileOutputStream os; try { os = mAtomicFile.startWrite(); boolean success = false; try { XmlSerializer serializer = new FastXmlSerializer(); serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name()); saveToXml(serializer); serializer.flush(); success = true; } finally { if (success) { mAtomicFile.finishWrite(os); } else { mAtomicFile.failWrite(os); } } } catch (IOException ex) { Slog.w(TAG, "Failed to save display manager persistent store data.", ex); } } private void loadFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { XmlUtils.beginDocument(parser, "display-manager-state"); final int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (parser.getName().equals("remembered-wifi-displays")) { loadRememberedWifiDisplaysFromXml(parser); } } } private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser) throws IOException, XmlPullParserException { final int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { if (parser.getName().equals("wifi-display")) { String deviceAddress = parser.getAttributeValue(null, "deviceAddress"); String deviceName = parser.getAttributeValue(null, "deviceName"); String deviceAlias = parser.getAttributeValue(null, "deviceAlias"); if (deviceAddress == null || deviceName == null) { throw new XmlPullParserException( "Missing deviceAddress or deviceName attribute on wifi-display."); } if (findRememberedWifiDisplay(deviceAddress) >= 0) { throw new XmlPullParserException( "Found duplicate wifi display device address."); } mRememberedWifiDisplays.add( new WifiDisplay(deviceAddress, deviceName, deviceAlias, false, false, false)); } } } private void saveToXml(XmlSerializer serializer) throws IOException { serializer.startDocument(null, true); serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); serializer.startTag(null, "display-manager-state"); serializer.startTag(null, "remembered-wifi-displays"); for (WifiDisplay display : mRememberedWifiDisplays) { serializer.startTag(null, "wifi-display"); serializer.attribute(null, "deviceAddress", display.getDeviceAddress()); serializer.attribute(null, "deviceName", display.getDeviceName()); if (display.getDeviceAlias() != null) { serializer.attribute(null, "deviceAlias", display.getDeviceAlias()); } serializer.endTag(null, "wifi-display"); } serializer.endTag(null, "remembered-wifi-displays"); serializer.endTag(null, "display-manager-state"); serializer.endDocument(); } }