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 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 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 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 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 if (condition != null) { 100 condition.restoreState(bundle); 101 conditions.add(condition); 102 } else { 103 Log.e(TAG, "failed to add condition: " + clz); 104 } 105 while (parser.getDepth() > depth) { 106 parser.next(); 107 } 108 } 109 state = parser.next(); 110 } 111 in.close(); 112 } catch (XmlPullParserException | IOException | ClassNotFoundException e) { 113 Log.w(TAG, "Problem reading " + FILE_NAME, e); 114 } 115 } 116 117 private void saveToXml() { 118 if (DEBUG) Log.d(TAG, "Writing to " + mXmlFile.toString()); 119 try { 120 XmlSerializer serializer = Xml.newSerializer(); 121 FileWriter writer = new FileWriter(mXmlFile); 122 serializer.setOutput(writer); 123 124 serializer.startDocument("UTF-8", true); 125 serializer.startTag("", TAG_CONDITIONS); 126 127 final int N = mConditions.size(); 128 for (int i = 0; i < N; i++) { 129 PersistableBundle bundle = new PersistableBundle(); 130 if (mConditions.get(i).saveState(bundle)) { 131 serializer.startTag("", TAG_CONDITION); 132 final String clz = mConditions.get(i).getClass().getSimpleName(); 133 serializer.attribute("", ATTR_CLASS, clz); 134 bundle.saveToXml(serializer); 135 serializer.endTag("", TAG_CONDITION); 136 } 137 } 138 139 serializer.endTag("", TAG_CONDITIONS); 140 serializer.flush(); 141 writer.close(); 142 } catch (XmlPullParserException | IOException e) { 143 Log.w(TAG, "Problem writing " + FILE_NAME, e); 144 } 145 } 146 147 private void addMissingConditions(ArrayList<Condition> conditions) { 148 addIfMissing(AirplaneModeCondition.class, conditions); 149 addIfMissing(HotspotCondition.class, conditions); 150 addIfMissing(DndCondition.class, conditions); 151 addIfMissing(BatterySaverCondition.class, conditions); 152 addIfMissing(CellularDataCondition.class, conditions); 153 addIfMissing(BackgroundDataCondition.class, conditions); 154 addIfMissing(WorkModeCondition.class, conditions); 155 addIfMissing(NightDisplayCondition.class, conditions); 156 Collections.sort(conditions, CONDITION_COMPARATOR); 157 } 158 159 private void addIfMissing(Class<? extends Condition> clz, ArrayList<Condition> conditions) { 160 if (getCondition(clz, conditions) == null) { 161 if (DEBUG) Log.d(TAG, "Adding missing " + clz.getName()); 162 Condition condition = createCondition(clz); 163 if (condition != null) { 164 conditions.add(condition); 165 } 166 } 167 } 168 169 private Condition createCondition(Class<?> clz) { 170 if (AirplaneModeCondition.class == clz) { 171 return new AirplaneModeCondition(this); 172 } else if (HotspotCondition.class == clz) { 173 return new HotspotCondition(this); 174 } else if (DndCondition.class == clz) { 175 return new DndCondition(this); 176 } else if (BatterySaverCondition.class == clz) { 177 return new BatterySaverCondition(this); 178 } else if (CellularDataCondition.class == clz) { 179 return new CellularDataCondition(this); 180 } else if (BackgroundDataCondition.class == clz) { 181 return new BackgroundDataCondition(this); 182 } else if (WorkModeCondition.class == clz) { 183 return new WorkModeCondition(this); 184 } else if (NightDisplayCondition.class == clz) { 185 return new NightDisplayCondition(this); 186 } 187 Log.e(TAG, "unknown condition class: " + clz.getSimpleName()); 188 return null; 189 } 190 191 Context getContext() { 192 return mContext; 193 } 194 195 public <T extends Condition> T getCondition(Class<T> clz) { 196 return getCondition(clz, mConditions); 197 } 198 199 private <T extends Condition> T getCondition(Class<T> clz, List<Condition> conditions) { 200 final int N = conditions.size(); 201 for (int i = 0; i < N; i++) { 202 if (clz.equals(conditions.get(i).getClass())) { 203 return (T) conditions.get(i); 204 } 205 } 206 return null; 207 } 208 209 public List<Condition> getConditions() { 210 return mConditions; 211 } 212 213 public List<Condition> getVisibleConditions() { 214 List<Condition> conditions = new ArrayList<>(); 215 final int N = mConditions.size(); 216 for (int i = 0; i < N; i++) { 217 if (mConditions.get(i).shouldShow()) { 218 conditions.add(mConditions.get(i)); 219 } 220 } 221 return conditions; 222 } 223 224 public void notifyChanged(Condition condition) { 225 saveToXml(); 226 Collections.sort(mConditions, CONDITION_COMPARATOR); 227 final int N = mListeners.size(); 228 for (int i = 0; i < N; i++) { 229 mListeners.get(i).onConditionsChanged(); 230 } 231 } 232 233 public void addListener(ConditionListener listener) { 234 mListeners.add(listener); 235 listener.onConditionsChanged(); 236 } 237 238 public void remListener(ConditionListener listener) { 239 mListeners.remove(listener); 240 } 241 242 @Override 243 public void onResume() { 244 for (int i = 0, size = mConditions.size(); i < size; i++) { 245 mConditions.get(i).onResume(); 246 } 247 } 248 249 @Override 250 public void onPause() { 251 for (int i = 0, size = mConditions.size(); i < size; i++) { 252 mConditions.get(i).onPause(); 253 } 254 } 255 256 private class ConditionLoader extends AsyncTask<Void, Void, ArrayList<Condition>> { 257 @Override 258 protected ArrayList<Condition> doInBackground(Void... params) { 259 Log.d(TAG, "loading conditions from xml"); 260 ArrayList<Condition> conditions = new ArrayList<>(); 261 mXmlFile = new File(mContext.getFilesDir(), FILE_NAME); 262 if (mXmlFile.exists()) { 263 readFromXml(mXmlFile, conditions); 264 } 265 addMissingConditions(conditions); 266 return conditions; 267 } 268 269 @Override 270 protected void onPostExecute(ArrayList<Condition> conditions) { 271 Log.d(TAG, "conditions loaded from xml, refreshing conditions"); 272 mConditions.clear(); 273 mConditions.addAll(conditions); 274 refreshAll(); 275 } 276 } 277 278 public static ConditionManager get(Context context) { 279 return get(context, true); 280 } 281 282 public static ConditionManager get(Context context, boolean loadConditionsNow) { 283 if (sInstance == null) { 284 sInstance = new ConditionManager(context.getApplicationContext(), loadConditionsNow); 285 } 286 return sInstance; 287 } 288 289 public interface ConditionListener { 290 void onConditionsChanged(); 291 } 292 293 private static final Comparator<Condition> CONDITION_COMPARATOR = new Comparator<Condition>() { 294 @Override 295 public int compare(Condition lhs, Condition rhs) { 296 return Long.compare(lhs.getLastChange(), rhs.getLastChange()); 297 } 298 }; 299 } 300