PersistentDataStore.java revision 7ed8901f120b21b4476c37b47791e25d659b21a5
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 File tvDir = new File("/data/system/tv/" + userId); 85 tvDir.mkdirs(); 86 mAtomicFile = new AtomicFile(new File(tvDir, "tv-input-manager-state.xml")); 87 } 88 89 public boolean isParentalControlsEnabled() { 90 loadIfNeeded(); 91 return mParentalControlsEnabled; 92 } 93 94 public void setParentalControlsEnabled(boolean enabled) { 95 loadIfNeeded(); 96 if (mParentalControlsEnabled != enabled) { 97 mParentalControlsEnabled = enabled; 98 mParentalControlsEnabledChanged = true; 99 postSave(); 100 } 101 } 102 103 public boolean isRatingBlocked(TvContentRating rating) { 104 loadIfNeeded(); 105 for (TvContentRating blcokedRating : mBlockedRatings) { 106 if (rating.contains(blcokedRating)) { 107 return true; 108 } 109 } 110 return false; 111 } 112 113 public TvContentRating[] getBlockedRatings() { 114 loadIfNeeded(); 115 return mBlockedRatings.toArray(new TvContentRating[mBlockedRatings.size()]); 116 } 117 118 public void addBlockedRating(TvContentRating rating) { 119 loadIfNeeded(); 120 if (rating != null && !mBlockedRatings.contains(rating)) { 121 mBlockedRatings.add(rating); 122 mBlockedRatingsChanged = true; 123 postSave(); 124 } 125 } 126 127 public void removeBlockedRating(TvContentRating rating) { 128 loadIfNeeded(); 129 if (rating != null && mBlockedRatings.contains(rating)) { 130 mBlockedRatings.remove(rating); 131 mBlockedRatingsChanged = true; 132 postSave(); 133 } 134 } 135 136 private void loadIfNeeded() { 137 if (!mLoaded) { 138 load(); 139 mLoaded = true; 140 } 141 } 142 143 private void clearState() { 144 mBlockedRatings.clear(); 145 mParentalControlsEnabled = false; 146 } 147 148 private void load() { 149 clearState(); 150 151 final InputStream is; 152 try { 153 is = mAtomicFile.openRead(); 154 } catch (FileNotFoundException ex) { 155 return; 156 } 157 158 XmlPullParser parser; 159 try { 160 parser = Xml.newPullParser(); 161 parser.setInput(new BufferedInputStream(is), null); 162 loadFromXml(parser); 163 } catch (IOException | XmlPullParserException ex) { 164 Slog.w(TAG, "Failed to load tv input manager persistent store data.", ex); 165 clearState(); 166 } finally { 167 IoUtils.closeQuietly(is); 168 } 169 } 170 171 private void postSave() { 172 mHandler.removeCallbacks(mSaveRunnable); 173 mHandler.post(mSaveRunnable); 174 } 175 176 /** 177 * Runnable posted when the state needs to be saved. This is used to prevent unnecessary file 178 * operations when multiple settings change in rapid succession. 179 */ 180 private final Runnable mSaveRunnable = new Runnable() { 181 @Override 182 public void run() { 183 save(); 184 } 185 }; 186 187 private void save() { 188 final FileOutputStream os; 189 try { 190 os = mAtomicFile.startWrite(); 191 boolean success = false; 192 try { 193 XmlSerializer serializer = new FastXmlSerializer(); 194 serializer.setOutput(new BufferedOutputStream(os), "utf-8"); 195 saveToXml(serializer); 196 serializer.flush(); 197 success = true; 198 } finally { 199 if (success) { 200 mAtomicFile.finishWrite(os); 201 broadcastChangesIfNeeded(); 202 } else { 203 mAtomicFile.failWrite(os); 204 } 205 } 206 } catch (IOException ex) { 207 Slog.w(TAG, "Failed to save tv input manager persistent store data.", ex); 208 } 209 } 210 211 private void broadcastChangesIfNeeded() { 212 if (mParentalControlsEnabledChanged) { 213 mParentalControlsEnabledChanged = false; 214 mContext.sendBroadcastAsUser(new Intent( 215 TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED), UserHandle.ALL); 216 } 217 if (mBlockedRatingsChanged) { 218 mBlockedRatingsChanged = false; 219 mContext.sendBroadcastAsUser(new Intent(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED), 220 UserHandle.ALL); 221 } 222 } 223 224 private static final String TAG_TV_INPUT_MANAGER_STATE = "tv-input-manager-state"; 225 private static final String TAG_BLOCKED_RATINGS = "blocked-ratings"; 226 private static final String TAG_RATING = "rating"; 227 private static final String TAG_PARENTAL_CONTROLS = "parental-controls"; 228 private static final String ATTR_STRING = "string"; 229 private static final String ATTR_ENABLED = "enabled"; 230 231 private void loadFromXml(XmlPullParser parser) 232 throws IOException, XmlPullParserException { 233 XmlUtils.beginDocument(parser, TAG_TV_INPUT_MANAGER_STATE); 234 final int outerDepth = parser.getDepth(); 235 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 236 if (parser.getName().equals(TAG_BLOCKED_RATINGS)) { 237 loadBlockedRatingsFromXml(parser); 238 } else if (parser.getName().equals(TAG_PARENTAL_CONTROLS)) { 239 String enabled = parser.getAttributeValue(null, ATTR_ENABLED); 240 if (TextUtils.isEmpty(enabled)) { 241 throw new XmlPullParserException( 242 "Missing " + ATTR_ENABLED + " attribute on " + TAG_PARENTAL_CONTROLS); 243 } 244 mParentalControlsEnabled = Boolean.valueOf(enabled); 245 } 246 } 247 } 248 249 private void loadBlockedRatingsFromXml(XmlPullParser parser) 250 throws IOException, XmlPullParserException { 251 final int outerDepth = parser.getDepth(); 252 while (XmlUtils.nextElementWithin(parser, outerDepth)) { 253 if (parser.getName().equals(TAG_RATING)) { 254 String ratingString = parser.getAttributeValue(null, ATTR_STRING); 255 if (TextUtils.isEmpty(ratingString)) { 256 throw new XmlPullParserException( 257 "Missing " + ATTR_STRING + " attribute on " + TAG_RATING); 258 } 259 mBlockedRatings.add(TvContentRating.unflattenFromString(ratingString)); 260 } 261 } 262 } 263 264 private void saveToXml(XmlSerializer serializer) throws IOException { 265 serializer.startDocument(null, true); 266 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 267 serializer.startTag(null, TAG_TV_INPUT_MANAGER_STATE); 268 serializer.startTag(null, TAG_BLOCKED_RATINGS); 269 for (TvContentRating rating : mBlockedRatings) { 270 serializer.startTag(null, TAG_RATING); 271 serializer.attribute(null, ATTR_STRING, rating.flattenToString()); 272 serializer.endTag(null, TAG_RATING); 273 } 274 serializer.endTag(null, TAG_BLOCKED_RATINGS); 275 serializer.startTag(null, TAG_PARENTAL_CONTROLS); 276 serializer.attribute(null, ATTR_ENABLED, Boolean.toString(mParentalControlsEnabled)); 277 serializer.endTag(null, TAG_PARENTAL_CONTROLS); 278 serializer.endTag(null, TAG_TV_INPUT_MANAGER_STATE); 279 serializer.endDocument(); 280 } 281} 282