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