1 /* 2 * Copyright (C) 2015 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 package com.android.settings.dashboard.conditional; 17 18 import android.content.Context; 19 import android.os.AsyncTask; 20 import android.os.PersistableBundle; 21 import android.util.Log; 22 import android.util.Xml; 23 24 import org.xmlpull.v1.XmlPullParser; 25 import org.xmlpull.v1.XmlPullParserException; 26 import org.xmlpull.v1.XmlSerializer; 27 28 import java.io.File; 29 import java.io.FileReader; 30 import java.io.FileWriter; 31 import java.io.IOException; 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.Comparator; 35 import java.util.List; 36 37 public class ConditionManager { 38 39 private static final String TAG = "ConditionManager"; 40 41 private static final boolean DEBUG = false; 42 43 private static final String PKG = "com.android.settings.dashboard.conditional."; 44 45 private static final String FILE_NAME = "condition_state.xml"; 46 private static final String TAG_CONDITIONS = "cs"; 47 private static final String TAG_CONDITION = "c"; 48 private static final String ATTR_CLASS = "cls"; 49 50 private static ConditionManager sInstance; 51 52 private final Context mContext; 53 private final ArrayList<Condition> mConditions; 54 private File mXmlFile; 55 56 private final ArrayList<ConditionListener> mListeners = new ArrayList<>(); 57 58 private ConditionManager(Context context, boolean loadConditionsNow) { 59 mContext = context; 60 mConditions = new ArrayList<>(); 61 if (loadConditionsNow) { 62 ConditionLoader loader = new ConditionLoader(); 63 loader.onPostExecute(loader.doInBackground()); 64 } else { 65 new ConditionLoader().execute(); 66 } 67 } 68 69 public void refreshAll() { 70 final int N = mConditions.size(); 71 for (int i = 0; i < N; i++) { 72 mConditions.get(i).refreshState(); 73 } 74 } 75 76 private void readFromXml(File xmlFile, ArrayList<Condition> conditions) { 77 if (DEBUG) Log.d(TAG, "Reading from " + xmlFile.toString()); 78 try { 79 XmlPullParser parser = Xml.newPullParser(); 80 FileReader in = new FileReader(xmlFile); 81 parser.setInput(in); 82 int state = parser.getEventType(); 83 84 while (state != XmlPullParser.END_DOCUMENT) { 85 if (TAG_CONDITION.equals(parser.getName())) { 86 int depth = parser.getDepth(); 87 String clz = parser.getAttributeValue("", ATTR_CLASS); 88 if (!clz.startsWith(PKG)) { 89 clz = PKG + clz; 90 } 91 Condition condition = createCondition(Class.forName(clz)); 92 PersistableBundle bundle = PersistableBundle.restoreFromXml(parser); 93 if (DEBUG) Log.d(TAG, "Reading " + clz + " -- " + bundle); 94 condition.restoreState(bundle); 95 conditions.add(condition); 96 while (parser.getDepth() > depth) { 97 parser.next(); 98 } 99 } 100 state = parser.next(); 101 } 102 in.close(); 103 } catch (XmlPullParserException | IOException | ClassNotFoundException e) { 104 Log.w(TAG, "Problem reading " + FILE_NAME, e); 105 } 106 } 107 108 private void saveToXml() { 109 if (DEBUG) Log.d(TAG, "Writing to " + mXmlFile.toString()); 110 try { 111 XmlSerializer serializer = Xml.newSerializer(); 112 FileWriter writer = new FileWriter(mXmlFile); 113 serializer.setOutput(writer); 114 115 serializer.startDocument("UTF-8", true); 116 serializer.startTag("", TAG_CONDITIONS); 117 118 final int N = mConditions.size(); 119 for (int i = 0; i < N; i++) { 120 PersistableBundle bundle = new PersistableBundle(); 121 if (mConditions.get(i).saveState(bundle)) { 122 serializer.startTag("", TAG_CONDITION); 123 final String clz = mConditions.get(i).getClass().getSimpleName(); 124 serializer.attribute("", ATTR_CLASS, clz); 125 bundle.saveToXml(serializer); 126 serializer.endTag("", TAG_CONDITION); 127 } 128 } 129 130 serializer.endTag("", TAG_CONDITIONS); 131 serializer.flush(); 132 writer.close(); 133 } catch (XmlPullParserException | IOException e) { 134 Log.w(TAG, "Problem writing " + FILE_NAME, e); 135 } 136 } 137 138 private void addMissingConditions(ArrayList<Condition> conditions) { 139 addIfMissing(AirplaneModeCondition.class, conditions); 140 addIfMissing(HotspotCondition.class, conditions); 141 addIfMissing(DndCondition.class, conditions); 142 addIfMissing(BatterySaverCondition.class, conditions); 143 addIfMissing(CellularDataCondition.class, conditions); 144 addIfMissing(BackgroundDataCondition.class, conditions); 145 addIfMissing(WorkModeCondition.class, conditions); 146 addIfMissing(NightDisplayCondition.class, conditions); 147 Collections.sort(conditions, CONDITION_COMPARATOR); 148 } 149 150 private void addIfMissing(Class<? extends Condition> clz, ArrayList<Condition> conditions) { 151 if (getCondition(clz, conditions) == null) { 152 if (DEBUG) Log.d(TAG, "Adding missing " + clz.getName()); 153 conditions.add(createCondition(clz)); 154 } 155 } 156 157 private Condition createCondition(Class<?> clz) { 158 if (AirplaneModeCondition.class == clz) { 159 return new AirplaneModeCondition(this); 160 } else if (HotspotCondition.class == clz) { 161 return new HotspotCondition(this); 162 } else if (DndCondition.class == clz) { 163 return new DndCondition(this); 164 } else if (BatterySaverCondition.class == clz) { 165 return new BatterySaverCondition(this); 166 } else if (CellularDataCondition.class == clz) { 167 return new CellularDataCondition(this); 168 } else if (BackgroundDataCondition.class == clz) { 169 return new BackgroundDataCondition(this); 170 } else if (WorkModeCondition.class == clz) { 171 return new WorkModeCondition(this); 172 } else if (NightDisplayCondition.class == clz) { 173 return new NightDisplayCondition(this); 174 } 175 throw new RuntimeException("Unexpected Condition " + clz); 176 } 177 178 Context getContext() { 179 return mContext; 180 } 181 182 public <T extends Condition> T getCondition(Class<T> clz) { 183 return getCondition(clz, mConditions); 184 } 185 186 private <T extends Condition> T getCondition(Class<T> clz, List<Condition> conditions) { 187 final int N = conditions.size(); 188 for (int i = 0; i < N; i++) { 189 if (clz.equals(conditions.get(i).getClass())) { 190 return (T) conditions.get(i); 191 } 192 } 193 return null; 194 } 195 196 public List<Condition> getConditions() { 197 return mConditions; 198 } 199 200 public List<Condition> getVisibleConditions() { 201 List<Condition> conditions = new ArrayList<>(); 202 final int N = mConditions.size(); 203 for (int i = 0; i < N; i++) { 204 if (mConditions.get(i).shouldShow()) { 205 conditions.add(mConditions.get(i)); 206 } 207 } 208 return conditions; 209 } 210 211 public void notifyChanged(Condition condition) { 212 saveToXml(); 213 Collections.sort(mConditions, CONDITION_COMPARATOR); 214 final int N = mListeners.size(); 215 for (int i = 0; i < N; i++) { 216 mListeners.get(i).onConditionsChanged(); 217 } 218 } 219 220 public void addListener(ConditionListener listener) { 221 mListeners.add(listener); 222 listener.onConditionsChanged(); 223 } 224 225 public void remListener(ConditionListener listener) { 226 mListeners.remove(listener); 227 } 228 229 private class ConditionLoader extends AsyncTask<Void, Void, ArrayList<Condition>> { 230 @Override 231 protected ArrayList<Condition> doInBackground(Void... params) { 232 ArrayList<Condition> conditions = new ArrayList<>(); 233 mXmlFile = new File(mContext.getFilesDir(), FILE_NAME); 234 if (mXmlFile.exists()) { 235 readFromXml(mXmlFile, conditions); 236 } 237 addMissingConditions(conditions); 238 return conditions; 239 } 240 241 @Override 242 protected void onPostExecute(ArrayList<Condition> conditions) { 243 mConditions.clear(); 244 mConditions.addAll(conditions); 245 refreshAll(); 246 } 247 } 248 249 public static ConditionManager get(Context context) { 250 return get(context, true); 251 } 252 253 public static ConditionManager get(Context context, boolean loadConditionsNow) { 254 if (sInstance == null) { 255 sInstance = new ConditionManager(context.getApplicationContext(), loadConditionsNow); 256 } 257 return sInstance; 258 } 259 260 public interface ConditionListener { 261 void onConditionsChanged(); 262 } 263 264 private static final Comparator<Condition> CONDITION_COMPARATOR = new Comparator<Condition>() { 265 @Override 266 public int compare(Condition lhs, Condition rhs) { 267 return Long.compare(lhs.getLastChange(), rhs.getLastChange()); 268 } 269 }; 270 } 271