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