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 */
16package com.android.settings.dashboard.conditional;
17
18import android.content.Context;
19import android.os.AsyncTask;
20import android.os.PersistableBundle;
21import android.util.Log;
22import android.util.Xml;
23
24import com.android.settingslib.core.lifecycle.LifecycleObserver;
25import com.android.settingslib.core.lifecycle.events.OnPause;
26import com.android.settingslib.core.lifecycle.events.OnResume;
27import org.xmlpull.v1.XmlPullParser;
28import org.xmlpull.v1.XmlPullParserException;
29import org.xmlpull.v1.XmlSerializer;
30
31import java.io.File;
32import java.io.FileReader;
33import java.io.FileWriter;
34import java.io.IOException;
35import java.util.ArrayList;
36import java.util.Collections;
37import java.util.Comparator;
38import java.util.List;
39
40public 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