1/*
2 * Copyright (C) 2012 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
17package android.bordeaux.services;
18
19import android.bordeaux.services.IBordeauxLearner.ModelChangeCallback;
20import android.content.Context;
21import android.os.IBinder;
22import android.util.Log;
23
24import java.lang.NoSuchMethodException;
25import java.lang.InstantiationException;
26import java.util.concurrent.ConcurrentHashMap;
27import java.util.HashMap;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32
33// This class manages the learning sessions from multiple applications.
34// The learning sessions are automatically backed up to the storage.
35//
36class BordeauxSessionManager {
37
38    static private final String TAG = "BordeauxSessionManager";
39    private BordeauxSessionStorage mSessionStorage;
40
41    static class Session {
42        Class learnerClass;
43        IBordeauxLearner learner;
44        boolean modified = false;
45    };
46
47    static class SessionKey {
48        String value;
49    };
50
51    // Thread to periodically save the sessions to storage
52    class PeriodicSave extends Thread implements Runnable {
53        long mSavingInterval = 60000; // 60 seconds
54        boolean mQuit = false;
55        PeriodicSave() {}
56        public void run() {
57            while (!mQuit) {
58                try {
59                    sleep(mSavingInterval);
60                } catch (InterruptedException e) {
61                    // thread waked up.
62                    // ignore
63                }
64                saveSessions();
65            }
66        }
67    }
68
69    PeriodicSave mSavingThread = new PeriodicSave();
70
71    private ConcurrentHashMap<String, Session> mSessions =
72            new ConcurrentHashMap<String, Session>();
73
74    public BordeauxSessionManager(final Context context) {
75        mSessionStorage = new BordeauxSessionStorage(context);
76        mSavingThread.start();
77    }
78
79    class LearningUpdateCallback implements ModelChangeCallback {
80        private String mKey;
81
82        public LearningUpdateCallback(String key) {
83            mKey = key;
84        }
85
86        public void modelChanged(IBordeauxLearner learner) {
87            // Save the session
88            Session session = mSessions.get(mKey);
89            if (session != null) {
90                synchronized(session) {
91                    if (session.learner != learner) {
92                        throw new RuntimeException("Session data corrupted!");
93                    }
94                    session.modified = true;
95                }
96            }
97        }
98    }
99
100    // internal unique key that identifies the learning instance.
101    // Composed by the package id of the calling process, learning class name
102    // and user specified name.
103    public SessionKey getSessionKey(String callingUid, Class learnerClass, String name) {
104        SessionKey key = new SessionKey();
105        key.value = callingUid + "#" + "_" + name + "_" + learnerClass.getName();
106        return key;
107    }
108
109    public IBinder getSessionBinder(Class learnerClass, SessionKey key) {
110        if (mSessions.containsKey(key.value)) {
111            return mSessions.get(key.value).learner.getBinder();
112        }
113        // not in memory cache
114        try {
115            // try to find it in the database
116            Session stored = mSessionStorage.getSession(key.value);
117            if (stored != null) {
118                // set the callback, so that we can save the state
119                stored.learner.setModelChangeCallback(new LearningUpdateCallback(key.value));
120                // found session in the storage, put in the cache
121                mSessions.put(key.value, stored);
122                return stored.learner.getBinder();
123            }
124
125            // if session is not already stored, create a new one.
126            Log.i(TAG, "create a new learning session: " + key.value);
127            IBordeauxLearner learner =
128                    (IBordeauxLearner) learnerClass.getConstructor().newInstance();
129            // set the callback, so that we can save the state
130            learner.setModelChangeCallback(new LearningUpdateCallback(key.value));
131            Session session = new Session();
132            session.learnerClass = learnerClass;
133            session.learner = learner;
134            mSessions.put(key.value, session);
135            return learner.getBinder();
136        } catch (Exception e) {
137            throw new RuntimeException("Can't instantiate class: " +
138                                       learnerClass.getName());
139        }
140    }
141
142    public void saveSessions() {
143        for (Map.Entry<String, Session> session : mSessions.entrySet()) {
144            synchronized(session) {
145                // Save the session if it's modified.
146                if (session.getValue().modified) {
147                    SessionKey skey = new SessionKey();
148                    skey.value = session.getKey();
149                    saveSession(skey);
150                }
151            }
152        }
153    }
154
155    public boolean saveSession(SessionKey key) {
156        Session session = mSessions.get(key.value);
157        if (session != null) {
158            synchronized(session) {
159                byte[] model = session.learner.getModel();
160
161                // write to database
162                boolean res = mSessionStorage.saveSession(key.value, session.learnerClass, model);
163                if (res)
164                    session.modified = false;
165                else {
166                    Log.e(TAG, "Can't save session: " + key.value);
167                }
168                return res;
169            }
170        }
171        Log.e(TAG, "Session not found: " + key.value);
172        return false;
173    }
174
175    // Load all session data into memory.
176    // The session data will be loaded into the memory from the database, even
177    // if this method is not called.
178    public void loadSessions() {
179        synchronized(mSessions) {
180            mSessionStorage.getAllSessions(mSessions);
181            for (Map.Entry<String, Session> session : mSessions.entrySet()) {
182                // set the callback, so that we can save the state
183                session.getValue().learner.setModelChangeCallback(
184                        new LearningUpdateCallback(session.getKey()));
185            }
186        }
187    }
188
189    public void removeAllSessionsFromCaller(String callingUid) {
190        // remove in the hash table
191        ArrayList<String> remove_keys = new ArrayList<String>();
192        for (Map.Entry<String, Session> session : mSessions.entrySet()) {
193            if (session.getKey().startsWith(callingUid + "#")) {
194                remove_keys.add(session.getKey());
195            }
196        }
197        for (String key : remove_keys) {
198            mSessions.remove(key);
199        }
200        // remove all session data from the callingUid in database
201        // % is used as wild match for the rest of the string in sql
202        int nDeleted = mSessionStorage.removeSessions(callingUid + "#%");
203        if (nDeleted > 0)
204            Log.i(TAG, "Successfully deleted " + nDeleted + "sessions");
205    }
206}
207