PersistentDataStore.java revision 783645e99f909ffc7a2d5d2fca9324cc0e9b7362
1/* 2 * Copyright (C) 2014 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.tv; 18 19import android.content.Context; 20import android.content.Intent; 21import android.media.tv.TvContentRating; 22import android.media.tv.TvInputManager; 23import android.os.Handler; 24import android.os.UserHandle; 25import android.text.TextUtils; 26import android.util.AtomicFile; 27import android.util.Slog; 28import android.util.Xml; 29 30import com.android.internal.util.FastXmlSerializer; 31import com.android.internal.util.XmlUtils; 32 33import libcore.io.IoUtils; 34 35import org.xmlpull.v1.XmlPullParser; 36import org.xmlpull.v1.XmlPullParserException; 37import org.xmlpull.v1.XmlSerializer; 38 39import java.io.BufferedInputStream; 40import java.io.BufferedOutputStream; 41import java.io.File; 42import java.io.FileNotFoundException; 43import java.io.FileOutputStream; 44import java.io.IOException; 45import java.io.InputStream; 46import java.util.ArrayList; 47import java.util.List; 48 49/** 50 * Manages persistent state recorded by the TV input manager service as an XML file. This class is 51 * not thread-safe thus caller must acquire lock on the data store before accessing it. File format: 52 * <code> 53 * <tv-input-manager-state> 54 * <blocked-ratings> 55 * <rating string="XXXX" /> 56 * </blocked-ratings> 57 * <parental-control enabled="YYYY" /> 58 * </tv-input-manager-state> 59 * </code> 60 */ 61final class PersistentDataStore { 62 private static final String TAG = "TvInputManagerService"; 63 64 private final Context mContext; 65 66 private final Handler mHandler = new Handler(); 67 68 // The atomic file used to safely read or write the file. 69 private final AtomicFile mAtomicFile; 70 71 private final List<TvContentRating> mBlockedRatings = new ArrayList<TvContentRating>(); 72 73 private boolean mBlockedRatingsChanged; 74 75 private boolean mParentalControlsEnabled; 76 77 private boolean mParentalControlsEnabledChanged; 78 79 // True if the data has been loaded. 80 private boolean mLoaded; 81 82 public PersistentDataStore(Context context, int userId) { 83 mContext = context; 84 mAtomicFile = new AtomicFile(new File("/data/system/tv/" + userId 85 + "/tv-input-manager-state.xml")); 86 } 87 88 public boolean isParentalControlsEnabled() { 89 loadIfNeeded(); 90 return mParentalControlsEnabled; 91 } 92 93 public void setParentalControlsEnabled(boolean enabled) { 94 if (mParentalControlsEnabled != enabled) { 95 mParentalControlsEnabled = enabled; 96 mParentalControlsEnabledChanged = true; 97 postSave(); 98 } 99 } 100 101 public boolean isRatingBlocked(TvContentRating rating) { 102 loadIfNeeded(); 103 for (TvContentRating blcokedRating : mBlockedRatings) { 104 if (rating.contains(blcokedRating)) { 105 return true; 106 } 107 } 108 return false; 109 } 110 111 public TvContentRating[] getBlockedRatings() { 112 loadIfNeeded(); 113 return mBlockedRatings.toArray(new TvContentRating[mBlockedRatings.size()]); 114 } 115 116 public void addBlockedRating(TvContentRating rating) { 117 if (rating != null && !mBlockedRatings.contains(rating)) { 118 mBlockedRatings.add(rating); 119 mBlockedRatingsChanged = true; 120 postSave(); 121 } 122 } 123 124 public void removeBlockedRating(TvContentRating rating) { 125 if (rating != null && mBlockedRatings.contains(rating)) { 126 mBlockedRatings.remove(rating); 127 mBlockedRatingsChanged = true; 128 postSave(); 129 } 130 } 131 132 private void loadIfNeeded() { 133 if (!mLoaded) { 134 load(); 135 mLoaded = true; 136 } 137 } 138 139 private void clearState() { 140 mBlockedRatings.clear(); 141 mParentalControlsEnabled = false; 142 } 143 144 private void load() { 145 clearState(); 146 147 final InputStream is; 148 try { 149 is = mAtomicFile.openRead(); 150 } catch (FileNotFoundException ex) { 151 return; 152 } 153 154 XmlPullParser parser; 155 try { 156 parser = Xml.newPullParser(); 157 parser.setInput(new BufferedInputStream(is), null); 158 loadFromXml(parser); 159 } catch (IOException | XmlPullParserException ex) { 160 Slog.w(TAG, "Failed to load tv input manager persistent store data.", ex); 161 clearState(); 162 } finally { 163 IoUtils.closeQuietly(is); 164 } 165 } 166 167 private void postSave() { 168 mHandler.removeCallbacks(mSaveRunnable); 169 mHandler.post(mSaveRunnable); 170 } 171 172 /** 173 * Runnable posted when the state needs to be saved. This is used to prevent unnecessary file 174 * operations when multiple settings change in rapid succession. 175 */ 176 private final Runnable mSaveRunnable = new Runnable() { 177 @Override 178 public void run() { 179 save(); 180 } 181 }; 182 183 private void save() { 184 final FileOutputStream os; 185 try { 186 os = mAtomicFile.startWrite(); 187 boolean success = false; 188 try { 189 XmlSerializer serializer = new FastXmlSerializer(); 190 serializer.setOutput(new BufferedOutputStream(os), "utf-8"); 191 saveToXml(serializer); 192 serializer.flush(); 193 success = true; 194 } finally { 195 if (success) { 196 mAtomicFile.finishWrite(os); 197 broadcastChangesIfNeeded(); 198 } else { 199 mAtomicFile.failWrite(os); 200 } 201 } 202 } catch (IOException ex) { 203 Slog.w(TAG, "Failed to save tv input manager persistent store data.", ex); 204 } 205 } 206 207 private void broadcastChangesIfNeeded() { 208 if (mParentalControlsEnabledChanged) { 209 mParentalControlsEnabledChanged = false; 210 mContext.sendBroadcastAsUser(new Intent( 211 TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED), UserHandle.ALL); 212 } 213 if (mBlockedRatingsChanged) { 214 mBlockedRatingsChanged = false; 215 mContext.sendBroadcastAsUser(new Intent(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED), 216 UserHandle.ALL); 217 } 218 } 219 220 private static final String TAG_TV_INPUT_MANAGER_STATE = "tv-input-manager-state"; 221 private static final String TAG_BLOCKED_RATINGS = "blocked-ratings"; 222 private static final String TAG_RATING = "rating"; 223 private static final String TAG_PARENTAL_CONTROLS = "parental-controls"; 224 private static final String ATTR_STRING = "string"; 225 private static final String ATTR_ENABLED = "enabled"; 226 227 private void loadFromXml(XmlPullParser parser) 228 throws IOException, XmlPullParserException { 229 XmlUtils.beginDocument(parser, TAG_TV_INPUT_MANAGER_STATE); 230 final int outerDepth = parser.getDepth(); 231 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 232 if (parser.getName().equals(TAG_BLOCKED_RATINGS)) { 233 loadBlockedRatingsFromXml(parser); 234 } else if (parser.getName().equals(TAG_PARENTAL_CONTROLS)) { 235 String enabled = parser.getAttributeValue(null, ATTR_ENABLED); 236 if (TextUtils.isEmpty(enabled)) { 237 throw new XmlPullParserException( 238 "Missing " + ATTR_ENABLED + " attribute on " + TAG_PARENTAL_CONTROLS); 239 } 240 mParentalControlsEnabled = Boolean.valueOf(enabled); 241 } 242 } 243 } 244 245 private void loadBlockedRatingsFromXml(XmlPullParser parser) 246 throws IOException, XmlPullParserException { 247 final int outerDepth = parser.getDepth(); 248 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 249 if (parser.getName().equals(TAG_RATING)) { 250 String ratingString = parser.getAttributeValue(null, ATTR_STRING); 251 if (TextUtils.isEmpty(ratingString)) { 252 throw new XmlPullParserException( 253 "Missing " + ATTR_STRING + " attribute on " + TAG_RATING); 254 } 255 mBlockedRatings.add(TvContentRating.unflattenFromString(ratingString)); 256 } 257 } 258 } 259 260 private void saveToXml(XmlSerializer serializer) throws IOException { 261 serializer.startDocument(null, true); 262 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 263 serializer.startTag(null, TAG_TV_INPUT_MANAGER_STATE); 264 serializer.startTag(null, TAG_BLOCKED_RATINGS); 265 for (TvContentRating rating : mBlockedRatings) { 266 serializer.startTag(null, TAG_RATING); 267 serializer.attribute(null, ATTR_STRING, rating.flattenToString()); 268 serializer.endTag(null, TAG_RATING); 269 } 270 serializer.endTag(null, TAG_BLOCKED_RATINGS); 271 serializer.startTag(null, TAG_PARENTAL_CONTROLS); 272 serializer.attribute(null, ATTR_ENABLED, Boolean.toString(mParentalControlsEnabled)); 273 serializer.endTag(null, TAG_PARENTAL_CONTROLS); 274 serializer.endTag(null, TAG_TV_INPUT_MANAGER_STATE); 275 serializer.endDocument(); 276 } 277} 278