1/*
2 * Copyright (C) 2013 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
17package com.android.server.wm;
18
19import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
20import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
21
22import android.graphics.Rect;
23import android.os.Environment;
24import android.util.AtomicFile;
25import android.util.Slog;
26import android.util.Xml;
27
28import com.android.internal.util.FastXmlSerializer;
29import com.android.internal.util.XmlUtils;
30
31import java.io.File;
32import java.io.FileInputStream;
33import java.io.FileNotFoundException;
34import java.io.FileOutputStream;
35import java.io.IOException;
36import java.nio.charset.StandardCharsets;
37import java.util.HashMap;
38
39import org.xmlpull.v1.XmlPullParser;
40import org.xmlpull.v1.XmlPullParserException;
41import org.xmlpull.v1.XmlSerializer;
42
43/**
44 * Current persistent settings about a display
45 */
46public class DisplaySettings {
47    private static final String TAG = TAG_WITH_CLASS_NAME ? "DisplaySettings" : TAG_WM;
48
49    private final AtomicFile mFile;
50    private final HashMap<String, Entry> mEntries = new HashMap<String, Entry>();
51
52    public static class Entry {
53        public final String name;
54        public int overscanLeft;
55        public int overscanTop;
56        public int overscanRight;
57        public int overscanBottom;
58
59        public Entry(String _name) {
60            name = _name;
61        }
62    }
63
64    public DisplaySettings() {
65        File dataDir = Environment.getDataDirectory();
66        File systemDir = new File(dataDir, "system");
67        mFile = new AtomicFile(new File(systemDir, "display_settings.xml"));
68    }
69
70    public void getOverscanLocked(String name, String uniqueId, Rect outRect) {
71        // Try to get the entry with the unique if possible.
72        // Else, fall back on the display name.
73        Entry entry;
74        if (uniqueId == null || (entry = mEntries.get(uniqueId)) == null) {
75            entry = mEntries.get(name);
76        }
77        if (entry != null) {
78            outRect.left = entry.overscanLeft;
79            outRect.top = entry.overscanTop;
80            outRect.right = entry.overscanRight;
81            outRect.bottom = entry.overscanBottom;
82        } else {
83            outRect.set(0, 0, 0, 0);
84        }
85    }
86
87    public void setOverscanLocked(String uniqueId, String name, int left, int top, int right,
88            int bottom) {
89        if (left == 0 && top == 0 && right == 0 && bottom == 0) {
90            // Right now all we are storing is overscan; if there is no overscan,
91            // we have no need for the entry.
92            mEntries.remove(uniqueId);
93            // Legacy name might have been in used, so we need to clear it.
94            mEntries.remove(name);
95            return;
96        }
97        Entry entry = mEntries.get(uniqueId);
98        if (entry == null) {
99            entry = new Entry(uniqueId);
100            mEntries.put(uniqueId, entry);
101        }
102        entry.overscanLeft = left;
103        entry.overscanTop = top;
104        entry.overscanRight = right;
105        entry.overscanBottom = bottom;
106    }
107
108    public void readSettingsLocked() {
109        FileInputStream stream;
110        try {
111            stream = mFile.openRead();
112        } catch (FileNotFoundException e) {
113            Slog.i(TAG, "No existing display settings " + mFile.getBaseFile()
114                    + "; starting empty");
115            return;
116        }
117        boolean success = false;
118        try {
119            XmlPullParser parser = Xml.newPullParser();
120            parser.setInput(stream, StandardCharsets.UTF_8.name());
121            int type;
122            while ((type = parser.next()) != XmlPullParser.START_TAG
123                    && type != XmlPullParser.END_DOCUMENT) {
124                // Do nothing.
125            }
126
127            if (type != XmlPullParser.START_TAG) {
128                throw new IllegalStateException("no start tag found");
129            }
130
131            int outerDepth = parser.getDepth();
132            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
133                    && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
134                if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
135                    continue;
136                }
137
138                String tagName = parser.getName();
139                if (tagName.equals("display")) {
140                    readDisplay(parser);
141                } else {
142                    Slog.w(TAG, "Unknown element under <display-settings>: "
143                            + parser.getName());
144                    XmlUtils.skipCurrentTag(parser);
145                }
146            }
147            success = true;
148        } catch (IllegalStateException e) {
149            Slog.w(TAG, "Failed parsing " + e);
150        } catch (NullPointerException e) {
151            Slog.w(TAG, "Failed parsing " + e);
152        } catch (NumberFormatException e) {
153            Slog.w(TAG, "Failed parsing " + e);
154        } catch (XmlPullParserException e) {
155            Slog.w(TAG, "Failed parsing " + e);
156        } catch (IOException e) {
157            Slog.w(TAG, "Failed parsing " + e);
158        } catch (IndexOutOfBoundsException e) {
159            Slog.w(TAG, "Failed parsing " + e);
160        } finally {
161            if (!success) {
162                mEntries.clear();
163            }
164            try {
165                stream.close();
166            } catch (IOException e) {
167            }
168        }
169    }
170
171    private int getIntAttribute(XmlPullParser parser, String name) {
172        try {
173            String str = parser.getAttributeValue(null, name);
174            return str != null ? Integer.parseInt(str) : 0;
175        } catch (NumberFormatException e) {
176            return 0;
177        }
178    }
179
180    private void readDisplay(XmlPullParser parser) throws NumberFormatException,
181            XmlPullParserException, IOException {
182        String name = parser.getAttributeValue(null, "name");
183        if (name != null) {
184            Entry entry = new Entry(name);
185            entry.overscanLeft = getIntAttribute(parser, "overscanLeft");
186            entry.overscanTop = getIntAttribute(parser, "overscanTop");
187            entry.overscanRight = getIntAttribute(parser, "overscanRight");
188            entry.overscanBottom = getIntAttribute(parser, "overscanBottom");
189            mEntries.put(name, entry);
190        }
191        XmlUtils.skipCurrentTag(parser);
192    }
193
194    public void writeSettingsLocked() {
195        FileOutputStream stream;
196        try {
197            stream = mFile.startWrite();
198        } catch (IOException e) {
199            Slog.w(TAG, "Failed to write display settings: " + e);
200            return;
201        }
202
203        try {
204            XmlSerializer out = new FastXmlSerializer();
205            out.setOutput(stream, StandardCharsets.UTF_8.name());
206            out.startDocument(null, true);
207            out.startTag(null, "display-settings");
208
209            for (Entry entry : mEntries.values()) {
210                out.startTag(null, "display");
211                out.attribute(null, "name", entry.name);
212                if (entry.overscanLeft != 0) {
213                    out.attribute(null, "overscanLeft", Integer.toString(entry.overscanLeft));
214                }
215                if (entry.overscanTop != 0) {
216                    out.attribute(null, "overscanTop", Integer.toString(entry.overscanTop));
217                }
218                if (entry.overscanRight != 0) {
219                    out.attribute(null, "overscanRight", Integer.toString(entry.overscanRight));
220                }
221                if (entry.overscanBottom != 0) {
222                    out.attribute(null, "overscanBottom", Integer.toString(entry.overscanBottom));
223                }
224                out.endTag(null, "display");
225            }
226
227            out.endTag(null, "display-settings");
228            out.endDocument();
229            mFile.finishWrite(stream);
230        } catch (IOException e) {
231            Slog.w(TAG, "Failed to write display settings, restoring backup.", e);
232            mFile.failWrite(stream);
233        }
234    }
235}
236