/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.bordeaux.services;
import android.bordeaux.services.IBordeauxLearner.ModelChangeCallback;
import android.content.Context;
import android.os.IBinder;
import android.util.Log;
import java.lang.NoSuchMethodException;
import java.lang.InstantiationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
// This class manages the learning sessions from multiple applications.
// The learning sessions are automatically backed up to the storage.
//
class BordeauxSessionManager {
static private final String TAG = "BordeauxSessionManager";
private BordeauxSessionStorage mSessionStorage;
static class Session {
Class learnerClass;
IBordeauxLearner learner;
boolean modified = false;
};
static class SessionKey {
String value;
};
// Thread to periodically save the sessions to storage
class PeriodicSave extends Thread implements Runnable {
long mSavingInterval = 60000; // 60 seconds
boolean mQuit = false;
PeriodicSave() {}
public void run() {
while (!mQuit) {
try {
sleep(mSavingInterval);
} catch (InterruptedException e) {
// thread waked up.
// ignore
}
saveSessions();
}
}
}
PeriodicSave mSavingThread = new PeriodicSave();
private ConcurrentHashMap<String, Session> mSessions =
new ConcurrentHashMap<String, Session>();
public BordeauxSessionManager(final Context context) {
mSessionStorage = new BordeauxSessionStorage(context);
mSavingThread.start();
}
class LearningUpdateCallback implements ModelChangeCallback {
private String mKey;
public LearningUpdateCallback(String key) {
mKey = key;
}
public void modelChanged(IBordeauxLearner learner) {
// Save the session
Session session = mSessions.get(mKey);
if (session != null) {
synchronized(session) {
if (session.learner != learner) {
throw new RuntimeException("Session data corrupted!");
}
session.modified = true;
}
}
}
}
// internal unique key that identifies the learning instance.
// Composed by the package id of the calling process, learning class name
// and user specified name.
public SessionKey getSessionKey(String callingUid, Class learnerClass, String name) {
SessionKey key = new SessionKey();
key.value = callingUid + "#" + "_" + name + "_" + learnerClass.getName();
return key;
}
public IBinder getSessionBinder(Class learnerClass, SessionKey key) {
if (mSessions.containsKey(key.value)) {
return mSessions.get(key.value).learner.getBinder();
}
// not in memory cache
try {
// try to find it in the database
Session stored = mSessionStorage.getSession(key.value);
if (stored != null) {
// set the callback, so that we can save the state
stored.learner.setModelChangeCallback(new LearningUpdateCallback(key.value));
// found session in the storage, put in the cache
mSessions.put(key.value, stored);
return stored.learner.getBinder();
}
// if session is not already stored, create a new one.
Log.i(TAG, "create a new learning session: " + key.value);
IBordeauxLearner learner =
(IBordeauxLearner) learnerClass.getConstructor().newInstance();
// set the callback, so that we can save the state
learner.setModelChangeCallback(new LearningUpdateCallback(key.value));
Session session = new Session();
session.learnerClass = learnerClass;
session.learner = learner;
mSessions.put(key.value, session);
return learner.getBinder();
} catch (Exception e) {
throw new RuntimeException("Can't instantiate class: " +
learnerClass.getName());
}
}
public void saveSessions() {
for (Map.Entry<String, Session> session : mSessions.entrySet()) {
synchronized(session) {
// Save the session if it's modified.
if (session.getValue().modified) {
SessionKey skey = new SessionKey();
skey.value = session.getKey();
saveSession(skey);
}
}
}
}
public boolean saveSession(SessionKey key) {
Session session = mSessions.get(key.value);
if (session != null) {
synchronized(session) {
byte[] model = session.learner.getModel();
// write to database
boolean res = mSessionStorage.saveSession(key.value, session.learnerClass, model);
if (res)
session.modified = false;
else {
Log.e(TAG, "Can't save session: " + key.value);
}
return res;
}
}
Log.e(TAG, "Session not found: " + key.value);
return false;
}
// Load all session data into memory.
// The session data will be loaded into the memory from the database, even
// if this method is not called.
public void loadSessions() {
synchronized(mSessions) {
mSessionStorage.getAllSessions(mSessions);
for (Map.Entry<String, Session> session : mSessions.entrySet()) {
// set the callback, so that we can save the state
session.getValue().learner.setModelChangeCallback(
new LearningUpdateCallback(session.getKey()));
}
}
}
public void removeAllSessionsFromCaller(String callingUid) {
// remove in the hash table
ArrayList<String> remove_keys = new ArrayList<String>();
for (Map.Entry<String, Session> session : mSessions.entrySet()) {
if (session.getKey().startsWith(callingUid + "#")) {
remove_keys.add(session.getKey());
}
}
for (String key : remove_keys) {
mSessions.remove(key);
}
// remove all session data from the callingUid in database
// % is used as wild match for the rest of the string in sql
int nDeleted = mSessionStorage.removeSessions(callingUid + "#%");
if (nDeleted > 0)
Log.i(TAG, "Successfully deleted " + nDeleted + "sessions");
}
}