Home | History | Annotate | Download | only in display
      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 import android.view.Display;
     31 
     32 import java.io.BufferedInputStream;
     33 import java.io.BufferedOutputStream;
     34 import java.io.File;
     35 import java.io.FileNotFoundException;
     36 import java.io.FileOutputStream;
     37 import java.io.IOException;
     38 import java.io.InputStream;
     39 import java.io.PrintWriter;
     40 import java.nio.charset.StandardCharsets;
     41 import java.util.ArrayList;
     42 import java.util.HashMap;
     43 import java.util.Map;
     44 
     45 import libcore.io.IoUtils;
     46 import libcore.util.Objects;
     47 
     48 /**
     49  * Manages persistent state recorded by the display manager service as an XML file.
     50  * Caller must acquire lock on the data store before accessing it.
     51  *
     52  * File format:
     53  * <code>
     54  * &lt;display-manager-state>
     55  *   &lt;remembered-wifi-displays>
     56  *     &lt;wifi-display deviceAddress="00:00:00:00:00:00" deviceName="XXXX" deviceAlias="YYYY" />
     57  *   &lt;remembered-wifi-displays>
     58  *   &lt;display-states>
     59  *      &lt;display>
     60  *          &lt;color-mode>0&lt;/color-mode>
     61  *      &lt;/display>
     62  *  &lt;/display-states>
     63  * &lt;/display-manager-state>
     64  * </code>
     65  *
     66  * TODO: refactor this to extract common code shared with the input manager's data store
     67  */
     68 final class PersistentDataStore {
     69     static final String TAG = "DisplayManager";
     70 
     71     // Remembered Wifi display devices.
     72     private ArrayList<WifiDisplay> mRememberedWifiDisplays = new ArrayList<WifiDisplay>();
     73 
     74     // Display state by unique id.
     75     private final HashMap<String, DisplayState> mDisplayStates =
     76             new HashMap<String, DisplayState>();
     77 
     78     // The atomic file used to safely read or write the file.
     79     private final AtomicFile mAtomicFile;
     80 
     81     // True if the data has been loaded.
     82     private boolean mLoaded;
     83 
     84     // True if there are changes to be saved.
     85     private boolean mDirty;
     86 
     87     public PersistentDataStore() {
     88         mAtomicFile = new AtomicFile(new File("/data/system/display-manager-state.xml"));
     89     }
     90 
     91     public void saveIfNeeded() {
     92         if (mDirty) {
     93             save();
     94             mDirty = false;
     95         }
     96     }
     97 
     98     public WifiDisplay getRememberedWifiDisplay(String deviceAddress) {
     99         loadIfNeeded();
    100         int index = findRememberedWifiDisplay(deviceAddress);
    101         if (index >= 0) {
    102             return mRememberedWifiDisplays.get(index);
    103         }
    104         return null;
    105     }
    106 
    107     public WifiDisplay[] getRememberedWifiDisplays() {
    108         loadIfNeeded();
    109         return mRememberedWifiDisplays.toArray(new WifiDisplay[mRememberedWifiDisplays.size()]);
    110     }
    111 
    112     public WifiDisplay applyWifiDisplayAlias(WifiDisplay display) {
    113         if (display != null) {
    114             loadIfNeeded();
    115 
    116             String alias = null;
    117             int index = findRememberedWifiDisplay(display.getDeviceAddress());
    118             if (index >= 0) {
    119                 alias = mRememberedWifiDisplays.get(index).getDeviceAlias();
    120             }
    121             if (!Objects.equal(display.getDeviceAlias(), alias)) {
    122                 return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(),
    123                         alias, display.isAvailable(), display.canConnect(), display.isRemembered());
    124             }
    125         }
    126         return display;
    127     }
    128 
    129     public WifiDisplay[] applyWifiDisplayAliases(WifiDisplay[] displays) {
    130         WifiDisplay[] results = displays;
    131         if (results != null) {
    132             int count = displays.length;
    133             for (int i = 0; i < count; i++) {
    134                 WifiDisplay result = applyWifiDisplayAlias(displays[i]);
    135                 if (result != displays[i]) {
    136                     if (results == displays) {
    137                         results = new WifiDisplay[count];
    138                         System.arraycopy(displays, 0, results, 0, count);
    139                     }
    140                     results[i] = result;
    141                 }
    142             }
    143         }
    144         return results;
    145     }
    146 
    147     public boolean rememberWifiDisplay(WifiDisplay display) {
    148         loadIfNeeded();
    149 
    150         int index = findRememberedWifiDisplay(display.getDeviceAddress());
    151         if (index >= 0) {
    152             WifiDisplay other = mRememberedWifiDisplays.get(index);
    153             if (other.equals(display)) {
    154                 return false; // already remembered without change
    155             }
    156             mRememberedWifiDisplays.set(index, display);
    157         } else {
    158             mRememberedWifiDisplays.add(display);
    159         }
    160         setDirty();
    161         return true;
    162     }
    163 
    164     public boolean forgetWifiDisplay(String deviceAddress) {
    165         int index = findRememberedWifiDisplay(deviceAddress);
    166         if (index >= 0) {
    167             mRememberedWifiDisplays.remove(index);
    168             setDirty();
    169             return true;
    170         }
    171         return false;
    172     }
    173 
    174     private int findRememberedWifiDisplay(String deviceAddress) {
    175         int count = mRememberedWifiDisplays.size();
    176         for (int i = 0; i < count; i++) {
    177             if (mRememberedWifiDisplays.get(i).getDeviceAddress().equals(deviceAddress)) {
    178                 return i;
    179             }
    180         }
    181         return -1;
    182     }
    183 
    184     public int getColorMode(DisplayDevice device) {
    185         if (!device.hasStableUniqueId()) {
    186             return Display.COLOR_MODE_INVALID;
    187         }
    188         DisplayState state = getDisplayState(device.getUniqueId(), false);
    189         if (state == null) {
    190             return Display.COLOR_MODE_INVALID;
    191         }
    192         return state.getColorMode();
    193     }
    194 
    195     public boolean setColorMode(DisplayDevice device, int colorMode) {
    196         if (!device.hasStableUniqueId()) {
    197             return false;
    198         }
    199         DisplayState state = getDisplayState(device.getUniqueId(), true);
    200         if (state.setColorMode(colorMode)) {
    201             setDirty();
    202             return true;
    203         }
    204         return false;
    205     }
    206 
    207     private DisplayState getDisplayState(String uniqueId, boolean createIfAbsent) {
    208         loadIfNeeded();
    209         DisplayState state = mDisplayStates.get(uniqueId);
    210         if (state == null && createIfAbsent) {
    211             state = new DisplayState();
    212             mDisplayStates.put(uniqueId, state);
    213             setDirty();
    214         }
    215         return state;
    216     }
    217 
    218     public void loadIfNeeded() {
    219         if (!mLoaded) {
    220             load();
    221             mLoaded = true;
    222         }
    223     }
    224 
    225     private void setDirty() {
    226         mDirty = true;
    227     }
    228 
    229     private void clearState() {
    230         mRememberedWifiDisplays.clear();
    231     }
    232 
    233     private void load() {
    234         clearState();
    235 
    236         final InputStream is;
    237         try {
    238             is = mAtomicFile.openRead();
    239         } catch (FileNotFoundException ex) {
    240             return;
    241         }
    242 
    243         XmlPullParser parser;
    244         try {
    245             parser = Xml.newPullParser();
    246             parser.setInput(new BufferedInputStream(is), StandardCharsets.UTF_8.name());
    247             loadFromXml(parser);
    248         } catch (IOException ex) {
    249             Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
    250             clearState();
    251         } catch (XmlPullParserException ex) {
    252             Slog.w(TAG, "Failed to load display manager persistent store data.", ex);
    253             clearState();
    254         } finally {
    255             IoUtils.closeQuietly(is);
    256         }
    257     }
    258 
    259     private void save() {
    260         final FileOutputStream os;
    261         try {
    262             os = mAtomicFile.startWrite();
    263             boolean success = false;
    264             try {
    265                 XmlSerializer serializer = new FastXmlSerializer();
    266                 serializer.setOutput(new BufferedOutputStream(os), StandardCharsets.UTF_8.name());
    267                 saveToXml(serializer);
    268                 serializer.flush();
    269                 success = true;
    270             } finally {
    271                 if (success) {
    272                     mAtomicFile.finishWrite(os);
    273                 } else {
    274                     mAtomicFile.failWrite(os);
    275                 }
    276             }
    277         } catch (IOException ex) {
    278             Slog.w(TAG, "Failed to save display manager persistent store data.", ex);
    279         }
    280     }
    281 
    282     private void loadFromXml(XmlPullParser parser)
    283             throws IOException, XmlPullParserException {
    284         XmlUtils.beginDocument(parser, "display-manager-state");
    285         final int outerDepth = parser.getDepth();
    286         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    287             if (parser.getName().equals("remembered-wifi-displays")) {
    288                 loadRememberedWifiDisplaysFromXml(parser);
    289             }
    290             if (parser.getName().equals("display-states")) {
    291                 loadDisplaysFromXml(parser);
    292             }
    293         }
    294     }
    295 
    296     private void loadRememberedWifiDisplaysFromXml(XmlPullParser parser)
    297             throws IOException, XmlPullParserException {
    298         final int outerDepth = parser.getDepth();
    299         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    300             if (parser.getName().equals("wifi-display")) {
    301                 String deviceAddress = parser.getAttributeValue(null, "deviceAddress");
    302                 String deviceName = parser.getAttributeValue(null, "deviceName");
    303                 String deviceAlias = parser.getAttributeValue(null, "deviceAlias");
    304                 if (deviceAddress == null || deviceName == null) {
    305                     throw new XmlPullParserException(
    306                             "Missing deviceAddress or deviceName attribute on wifi-display.");
    307                 }
    308                 if (findRememberedWifiDisplay(deviceAddress) >= 0) {
    309                     throw new XmlPullParserException(
    310                             "Found duplicate wifi display device address.");
    311                 }
    312 
    313                 mRememberedWifiDisplays.add(
    314                         new WifiDisplay(deviceAddress, deviceName, deviceAlias,
    315                                 false, false, false));
    316             }
    317         }
    318     }
    319 
    320     private void loadDisplaysFromXml(XmlPullParser parser)
    321             throws IOException, XmlPullParserException {
    322         final int outerDepth = parser.getDepth();
    323         while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    324             if (parser.getName().equals("display")) {
    325                 String uniqueId = parser.getAttributeValue(null, "unique-id");
    326                 if (uniqueId == null) {
    327                     throw new XmlPullParserException(
    328                             "Missing unique-id attribute on display.");
    329                 }
    330                 if (mDisplayStates.containsKey(uniqueId)) {
    331                     throw new XmlPullParserException("Found duplicate display.");
    332                 }
    333 
    334                 DisplayState state = new DisplayState();
    335                 state.loadFromXml(parser);
    336                 mDisplayStates.put(uniqueId, state);
    337             }
    338         }
    339     }
    340 
    341     private void saveToXml(XmlSerializer serializer) throws IOException {
    342         serializer.startDocument(null, true);
    343         serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    344         serializer.startTag(null, "display-manager-state");
    345         serializer.startTag(null, "remembered-wifi-displays");
    346         for (WifiDisplay display : mRememberedWifiDisplays) {
    347             serializer.startTag(null, "wifi-display");
    348             serializer.attribute(null, "deviceAddress", display.getDeviceAddress());
    349             serializer.attribute(null, "deviceName", display.getDeviceName());
    350             if (display.getDeviceAlias() != null) {
    351                 serializer.attribute(null, "deviceAlias", display.getDeviceAlias());
    352             }
    353             serializer.endTag(null, "wifi-display");
    354         }
    355         serializer.endTag(null, "remembered-wifi-displays");
    356         serializer.startTag(null, "display-states");
    357         for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
    358             final String uniqueId = entry.getKey();
    359             final DisplayState state = entry.getValue();
    360             serializer.startTag(null, "display");
    361             serializer.attribute(null, "unique-id", uniqueId);
    362             state.saveToXml(serializer);
    363             serializer.endTag(null, "display");
    364         }
    365         serializer.endTag(null, "display-states");
    366         serializer.endTag(null, "display-manager-state");
    367         serializer.endDocument();
    368     }
    369 
    370     public void dump(PrintWriter pw) {
    371         pw.println("PersistentDataStore");
    372         pw.println("  mLoaded=" + mLoaded);
    373         pw.println("  mDirty=" + mDirty);
    374         pw.println("  RememberedWifiDisplays:");
    375         int i = 0;
    376         for (WifiDisplay display : mRememberedWifiDisplays) {
    377             pw.println("    " + i++ + ": " + display);
    378         }
    379         pw.println("  DisplayStates:");
    380         i = 0;
    381         for (Map.Entry<String, DisplayState> entry : mDisplayStates.entrySet()) {
    382             pw.println("    " + i++ + ": " + entry.getKey());
    383             entry.getValue().dump(pw, "      ");
    384         }
    385     }
    386 
    387     private static final class DisplayState {
    388         private int mColorMode;
    389 
    390         public boolean setColorMode(int colorMode) {
    391             if (colorMode == mColorMode) {
    392                 return false;
    393             }
    394             mColorMode = colorMode;
    395             return true;
    396         }
    397 
    398         public int getColorMode() {
    399             return mColorMode;
    400         }
    401 
    402         public void loadFromXml(XmlPullParser parser)
    403                 throws IOException, XmlPullParserException {
    404             final int outerDepth = parser.getDepth();
    405 
    406             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
    407                 if (parser.getName().equals("color-mode")) {
    408                     String value = parser.nextText();
    409                     mColorMode = Integer.parseInt(value);
    410                 }
    411             }
    412         }
    413 
    414         public void saveToXml(XmlSerializer serializer) throws IOException {
    415             serializer.startTag(null, "color-mode");
    416             serializer.text(Integer.toString(mColorMode));
    417             serializer.endTag(null, "color-mode");
    418         }
    419 
    420         private void dump(final PrintWriter pw, final String prefix) {
    421             pw.println(prefix + "ColorMode=" + mColorMode);
    422         }
    423     }
    424 }
    425