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