CookieSyncManager.java revision 99f39771ab15fc13d221ebfb3682741002c5f7b1
154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project/* 254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Copyright (C) 2007 The Android Open Source Project 354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * 454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Licensed under the Apache License, Version 2.0 (the "License"); 554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * you may not use this file except in compliance with the License. 654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * You may obtain a copy of the License at 754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * 854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * http://www.apache.org/licenses/LICENSE-2.0 954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * 1054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Unless required by applicable law or agreed to in writing, software 1154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * distributed under the License is distributed on an "AS IS" BASIS, 1254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * See the License for the specific language governing permissions and 1454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * limitations under the License. 1554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 1654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 1754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectpackage android.webkit; 1854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 1954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectimport android.content.Context; 2054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectimport android.util.Log; 2154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectimport android.webkit.CookieManager.Cookie; 2254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 2354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectimport java.util.ArrayList; 2454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectimport java.util.Iterator; 2554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 2654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project/** 27adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * The CookieSyncManager is used to synchronize the browser cookie store 28adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * between RAM and permanent storage. To get the best performance, browser cookies are 29adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * saved in RAM. A separate thread saves the cookies between, driven by a timer. 3054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * <p> 31adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 3254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * To use the CookieSyncManager, the host application has to call the following 33adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * when the application starts: 3454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * <p> 35adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 36adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * <pre class="prettyprint">CookieSyncManager.createInstance(context)</pre><p> 37adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 38adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * To set up for sync, the host application has to call<p> 39adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * <pre class="prettyprint">CookieSyncManager.getInstance().startSync()</pre><p> 40adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 41adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * in Activity.onResume(), and call 4254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * <p> 43adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 44adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * <pre class="prettyprint"> 4554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * CookieSyncManager.getInstance().stopSync() 46adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * </pre><p> 47adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 48adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * in Activity.onPause().<p> 49adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 5054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * To get instant sync instead of waiting for the timer to trigger, the host can 5154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * call 5254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * <p> 53adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * <pre class="prettyprint">CookieSyncManager.getInstance().sync()</pre><p> 54adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * 55adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * The sync interval is 5 minutes, so you will want to force syncs 56adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * manually anyway, for instance in {@link 57adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * WebViewClient#onPageFinished}. Note that even sync() happens 58adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * asynchronously, so don't do it just as your activity is shutting 59adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * down. 6054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 6154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Projectpublic final class CookieSyncManager extends WebSyncManager { 6254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 6354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project private static CookieSyncManager sRef; 6454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 6554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // time when last update happened 6654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project private long mLastUpdate; 6754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 6854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project private CookieSyncManager(Context context) { 6954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project super(context, "CookieSyncManager"); 7054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 7154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 7254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 7354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Singleton access to a {@link CookieSyncManager}. An 7454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * IllegalStateException will be thrown if 7554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * {@link CookieSyncManager#createInstance(Context)} is not called before. 7654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * 7754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * @return CookieSyncManager 7854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 7954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project public static synchronized CookieSyncManager getInstance() { 8082d98161362750ed280675b704a5ae467091cfbaSteve Block checkInstanceIsCreated(); 8154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return sRef; 8254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 8354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 8454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 8554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Create a singleton CookieSyncManager within a context 8654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * @param context 8754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * @return CookieSyncManager 8854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 8954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project public static synchronized CookieSyncManager createInstance( 9054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project Context context) { 91d89a30af1f37317a51088a207dda865a2f60c83aKristian Monsen JniUtil.setContext(context); 9282d98161362750ed280675b704a5ae467091cfbaSteve Block Context appContext = context.getApplicationContext(); 9354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (sRef == null) { 9482d98161362750ed280675b704a5ae467091cfbaSteve Block sRef = new CookieSyncManager(appContext); 9554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 9654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return sRef; 9754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 9854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 9954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 100adcd2ed8d24deddee528e96260d0ed673eeb261cMike Hearn * Package level api, called from CookieManager. Get all the cookies which 10154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * matches a given base domain. 10254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * @param domain 10354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * @return A list of Cookie 10454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 10554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project ArrayList<Cookie> getCookiesForDomain(String domain) { 10654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // null mDataBase implies that the host application doesn't support 10754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // persistent cookie. No sync needed. 10854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (mDataBase == null) { 10954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return new ArrayList<Cookie>(); 11054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 11154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 11254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return mDataBase.getCookiesForDomain(domain); 11354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 11454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 11554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 11654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Package level api, called from CookieManager Clear all cookies in the 11754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * database 11854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 11954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project void clearAllCookies() { 12054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // null mDataBase implies that the host application doesn't support 12154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // persistent cookie. 12254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (mDataBase == null) { 12354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return; 12454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 12554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 12654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project mDataBase.clearCookies(); 12754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 12854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 12954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 13054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Returns true if there are any saved cookies. 13154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 13254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project boolean hasCookies() { 13354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // null mDataBase implies that the host application doesn't support 13454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // persistent cookie. 13554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (mDataBase == null) { 13654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return false; 13754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 13854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 13954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return mDataBase.hasCookies(); 14054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 14154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 14254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 14354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Package level api, called from CookieManager Clear all session cookies in 14454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * the database 14554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 14654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project void clearSessionCookies() { 14754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // null mDataBase implies that the host application doesn't support 14854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // persistent cookie. 14954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (mDataBase == null) { 15054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return; 15154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 15254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 15354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project mDataBase.clearSessionCookies(); 15454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 15554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 15654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project /** 15754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * Package level api, called from CookieManager Clear all expired cookies in 15854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project * the database 15954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project */ 16054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project void clearExpiredCookies(long now) { 16154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // null mDataBase implies that the host application doesn't support 16254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project // persistent cookie. 16354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (mDataBase == null) { 16454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return; 16554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 16654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 16754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project mDataBase.clearExpiredCookies(now); 16854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 16954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 17054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project protected void syncFromRamToFlash() { 1712e5c150e746647a1ce5c10e1708debbf06c45ea7Derek Sollenberger if (DebugFlags.COOKIE_SYNC_MANAGER) { 17254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash STARTS"); 17354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 17454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 17599f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick CookieManager manager = CookieManager.getInstance(); 17699f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick 17799f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick if (!manager.acceptCookie()) { 17854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project return; 17954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 18054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 18199f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick manager.flushCookieStore(); 18299f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick 18399f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick ArrayList<Cookie> cookieList = manager.getUpdatedCookiesSince(mLastUpdate); 18454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project mLastUpdate = System.currentTimeMillis(); 18554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project syncFromRamToFlash(cookieList); 18654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 18799f39771ab15fc13d221ebfb3682741002c5f7b1Iain Merrick ArrayList<Cookie> lruList = manager.deleteLRUDomain(); 18854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project syncFromRamToFlash(lruList); 18954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 1902e5c150e746647a1ce5c10e1708debbf06c45ea7Derek Sollenberger if (DebugFlags.COOKIE_SYNC_MANAGER) { 19154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project Log.v(LOGTAG, "CookieSyncManager::syncFromRamToFlash DONE"); 19254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 19354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 19454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project 19554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project private void syncFromRamToFlash(ArrayList<Cookie> list) { 19654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project Iterator<Cookie> iter = list.iterator(); 19754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project while (iter.hasNext()) { 19854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project Cookie cookie = iter.next(); 19954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (cookie.mode != Cookie.MODE_NORMAL) { 20054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (cookie.mode != Cookie.MODE_NEW) { 20154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project mDataBase.deleteCookies(cookie.domain, cookie.path, 20254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project cookie.name); 20354b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 20454b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project if (cookie.mode != Cookie.MODE_DELETED) { 20554b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project mDataBase.addCookie(cookie); 20654b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project CookieManager.getInstance().syncedACookie(cookie); 20754b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } else { 20854b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project CookieManager.getInstance().deleteACookie(cookie); 20954b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 21054b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 21154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 21254b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project } 21382d98161362750ed280675b704a5ae467091cfbaSteve Block 21482d98161362750ed280675b704a5ae467091cfbaSteve Block private static void checkInstanceIsCreated() { 21582d98161362750ed280675b704a5ae467091cfbaSteve Block if (sRef == null) { 21682d98161362750ed280675b704a5ae467091cfbaSteve Block throw new IllegalStateException( 21782d98161362750ed280675b704a5ae467091cfbaSteve Block "CookieSyncManager::createInstance() needs to be called " 21882d98161362750ed280675b704a5ae467091cfbaSteve Block + "before CookieSyncManager::getInstance()"); 21982d98161362750ed280675b704a5ae467091cfbaSteve Block } 22082d98161362750ed280675b704a5ae467091cfbaSteve Block } 22154b6cfa9a9e5b861a9930af873580d6dc20f773The Android Open Source Project} 222