From 8040a06300c25bbe7a913df63e05879fe6a86b5d Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 8 Jan 2024 11:51:19 +0100 Subject: Prepare db for caching bundle + logic to pass/get data --- .../mastodon/client/entities/app/CachedBundle.java | 228 +++++++++++++++++++++ .../fedilab/android/mastodon/helper/Helper.java | 2 + .../timeline/FragmentMastodonTimeline.java | 192 ++++++++--------- .../java/app/fedilab/android/sqlite/Sqlite.java | 14 +- 4 files changed, 343 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/mastodon/client/entities/app/CachedBundle.java (limited to 'app') diff --git a/app/src/main/java/app/fedilab/android/mastodon/client/entities/app/CachedBundle.java b/app/src/main/java/app/fedilab/android/mastodon/client/entities/app/CachedBundle.java new file mode 100644 index 000000000..87e7163ce --- /dev/null +++ b/app/src/main/java/app/fedilab/android/mastodon/client/entities/app/CachedBundle.java @@ -0,0 +1,228 @@ +package app.fedilab.android.mastodon.client.entities.app; +/* Copyright 2024 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Parcel; +import android.util.Base64; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Date; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import app.fedilab.android.mastodon.exception.DBException; +import app.fedilab.android.mastodon.helper.Helper; +import app.fedilab.android.sqlite.Sqlite; + +/** + * Class that manages Bundle of Intent from database + */ +public class CachedBundle { + + public String id; + public Bundle bundle; + public Date created_at; + + private SQLiteDatabase db; + + private transient Context context; + + public CachedBundle() {} + public CachedBundle(Context context) { + //Creation of the DB with tables + this.context = context; + this.db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + } + + + /** + * Insert a bundle in db + * + * @param bundle {@link Bundle} + * @return long - db id + * @throws DBException exception with database + */ + private long insertIntent(Bundle bundle) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + ContentValues values = new ContentValues(); + values.put(Sqlite.COL_BUNDLE, serializeBundle(bundle)); + values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(new Date())); + //Inserts token + try { + return db.insertOrThrow(Sqlite.TABLE_INTENT, null, values); + } catch (Exception e) { + e.printStackTrace(); + return -1; + } + } + + public interface BundleCallback{ + public void get(Bundle bundle); + } + + public interface BundleInsertCallback{ + public void inserted(long bundleId); + } + + public void getBundle(long id, BundleCallback callback) { + new Thread(()->{ + Bundle bundle = null; + try { + CachedBundle cachedBundle = getCachedBundle(String.valueOf(id)); + if (cachedBundle != null) { + bundle = cachedBundle.bundle; + } + removeIntent(String.valueOf(id)); + } catch (DBException ignored) {} + Handler mainHandler = new Handler(Looper.getMainLooper()); + Bundle finalBundle = bundle; + Runnable myRunnable = () -> callback.get(finalBundle); + mainHandler.post(myRunnable); + }).start(); + } + + public void insertBundle(Bundle bundle, BundleInsertCallback callback) { + new Thread(()->{ + long dbBundleId = -1; + try { + dbBundleId = insertIntent(bundle); + } catch (DBException ignored) {} + Handler mainHandler = new Handler(Looper.getMainLooper()); + long finalDbBundleId = dbBundleId; + Runnable myRunnable = () -> callback.inserted(finalDbBundleId); + mainHandler.post(myRunnable); + }).start(); + } + + /** + * Returns a bundle by its ID + * + * @param id String + * @return CachedBundle {@link CachedBundle} + */ + private CachedBundle getCachedBundle(String id) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + try { + Cursor c = db.query(Sqlite.TABLE_INTENT, null, Sqlite.COL_ID + " = \"" + id + "\"", null, null, null, null, "1"); + return cursorToCachedBundle(c); + } catch (Exception e) { + return null; + } + } + + /** + * Remove a bundle from db + * + * @param id - intent id + */ + private void removeIntent(String id) throws DBException { + if (db == null) { + throw new DBException("db is null. Wrong initialization."); + } + db.delete(Sqlite.TABLE_INTENT, Sqlite.COL_ID + " = '" + id + "'", null); + } + + + + /*** + * Method to hydrate an CachedBundle from database + * @param c Cursor + * @return CachedBundle {@link CachedBundle} + */ + private CachedBundle cursorToCachedBundle(Cursor c) { + //No element found + if (c.getCount() == 0) { + c.close(); + return null; + } + //Take the first element + c.moveToFirst(); + //New user + CachedBundle account = convertCursorToCachedBundle(c); + //Close the cursor + c.close(); + return account; + } + + /** + * Read cursor and hydrate without closing it + * + * @param c - Cursor + * @return BaseAccount + */ + private CachedBundle convertCursorToCachedBundle(Cursor c) { + CachedBundle cachedBundle = new CachedBundle(); + cachedBundle.id = c.getString(c.getColumnIndexOrThrow(Sqlite.COL_ID)); + cachedBundle.bundle = deserializeBundle(c.getString(c.getColumnIndexOrThrow(Sqlite.COL_BUNDLE))); + cachedBundle.created_at = Helper.stringToDate(context, c.getString(c.getColumnIndexOrThrow(Sqlite.COL_CREATED_AT))); + return cachedBundle; + } + + private String serializeBundle(final Bundle bundle) { + String base64 = null; + final Parcel parcel = Parcel.obtain(); + try { + parcel.writeBundle(bundle); + final ByteArrayOutputStream bos = new ByteArrayOutputStream(); + final GZIPOutputStream zos = new GZIPOutputStream(new BufferedOutputStream(bos)); + zos.write(parcel.marshall()); + zos.close(); + base64 = Base64.encodeToString(bos.toByteArray(), 0); + } catch(IOException e) { + e.printStackTrace(); + } finally { + parcel.recycle(); + } + return base64; + } + + private Bundle deserializeBundle(final String base64) { + Bundle bundle = null; + final Parcel parcel = Parcel.obtain(); + try { + final ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + final byte[] buffer = new byte[1024]; + final GZIPInputStream zis = new GZIPInputStream(new ByteArrayInputStream(Base64.decode(base64, 0))); + int len; + while ((len = zis.read(buffer)) != -1) { + byteBuffer.write(buffer, 0, len); + } + zis.close(); + parcel.unmarshall(byteBuffer.toByteArray(), 0, byteBuffer.size()); + parcel.setDataPosition(0); + bundle = parcel.readBundle(getClass().getClassLoader()); + } catch (IOException e) { + e.printStackTrace(); + } finally { + parcel.recycle(); + } + return bundle; + } + +} diff --git a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java index b2069db7c..e2e7a122e 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/mastodon/helper/Helper.java @@ -210,6 +210,8 @@ public class Helper { public static final String RECEIVE_REDRAW_PROFILE = "RECEIVE_REDRAW_PROFILE"; public static final String ARG_TIMELINE_TYPE = "ARG_TIMELINE_TYPE"; + + public static final String ARG_INTENT_ID = "ARG_INTENT_ID"; public static final String ARG_PEERTUBE_NAV_REMOTE = "ARG_PEERTUBE_NAV_REMOTE"; public static final String ARG_REMOTE_INSTANCE_STRING = "ARG_REMOTE_INSTANCE_STRING"; diff --git a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java index 64ee72688..90e1166ae 100644 --- a/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java +++ b/app/src/main/java/app/fedilab/android/mastodon/ui/fragment/timeline/FragmentMastodonTimeline.java @@ -57,10 +57,12 @@ import app.fedilab.android.mastodon.client.entities.api.Pagination; import app.fedilab.android.mastodon.client.entities.api.Status; import app.fedilab.android.mastodon.client.entities.api.Statuses; import app.fedilab.android.mastodon.client.entities.app.BubbleTimeline; +import app.fedilab.android.mastodon.client.entities.app.CachedBundle; import app.fedilab.android.mastodon.client.entities.app.PinnedTimeline; import app.fedilab.android.mastodon.client.entities.app.RemoteInstance; import app.fedilab.android.mastodon.client.entities.app.TagTimeline; import app.fedilab.android.mastodon.client.entities.app.Timeline; +import app.fedilab.android.mastodon.exception.DBException; import app.fedilab.android.mastodon.helper.CrossActionHelper; import app.fedilab.android.mastodon.helper.GlideApp; import app.fedilab.android.mastodon.helper.Helper; @@ -346,29 +348,12 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - timelinesVM = new ViewModelProvider(FragmentMastodonTimeline.this).get(viewModelKey, TimelinesVM.class); - accountsVM = new ViewModelProvider(FragmentMastodonTimeline.this).get(viewModelKey, AccountsVM.class); - initialStatuses = null; - lockForResumeCall = 0; binding.loader.setVisibility(View.VISIBLE); binding.recyclerView.setVisibility(View.GONE); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); - max_id = statusReport != null ? statusReport.id : null; - offset = 0; - - rememberPosition = sharedpreferences.getBoolean(getString(R.string.SET_REMEMBER_POSITION), true); - //Inner marker are only for pinned timelines and main timelines, they have isViewInitialized set to false - if (max_id == null && !isViewInitialized && rememberPosition) { - max_id = sharedpreferences.getString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); - } if (search != null) { binding.swipeContainer.setRefreshing(false); binding.swipeContainer.setEnabled(false); } - //Only fragment in main view pager should not have the view initialized - //AND Only the first fragment will initialize its view - flagLoading = false; - } @Override @@ -378,91 +363,114 @@ public class FragmentMastodonTimeline extends Fragment implements StatusAdapter. public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + timelinesVM = new ViewModelProvider(FragmentMastodonTimeline.this).get(viewModelKey, TimelinesVM.class); + accountsVM = new ViewModelProvider(FragmentMastodonTimeline.this).get(viewModelKey, AccountsVM.class); + initialStatuses = null; + lockForResumeCall = 0; timelineType = Timeline.TimeLineEnum.HOME; canBeFederated = true; retry_for_home_done = false; - if (getArguments() != null) { - timelineType = (Timeline.TimeLineEnum) getArguments().get(Helper.ARG_TIMELINE_TYPE); - lemmy_post_id = getArguments().getString(Helper.ARG_LEMMY_POST_ID, null); - list_id = getArguments().getString(Helper.ARG_LIST_ID, null); - search = getArguments().getString(Helper.ARG_SEARCH_KEYWORD, null); - searchCache = getArguments().getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); - pinnedTimeline = (PinnedTimeline) getArguments().getSerializable(Helper.ARG_REMOTE_INSTANCE); - if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { - if (pinnedTimeline.remoteInstance.type != RemoteInstance.InstanceType.NITTER) { - remoteInstance = pinnedTimeline.remoteInstance.host; - } else { - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); - remoteInstance = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); - canBeFederated = false; - } - } - if (timelineType == Timeline.TimeLineEnum.TREND_MESSAGE_PUBLIC) { - canBeFederated = false; - } - publicTrendsDomain = getArguments().getString(Helper.ARG_REMOTE_INSTANCE_STRING, null); - isViewInitialized = getArguments().getBoolean(Helper.ARG_INITIALIZE_VIEW, true); - isNotPinnedTimeline = isViewInitialized; - tagTimeline = (TagTimeline) getArguments().getSerializable(Helper.ARG_TAG_TIMELINE); - bubbleTimeline = (BubbleTimeline) getArguments().getSerializable(Helper.ARG_BUBBLE_TIMELINE); - accountTimeline = (Account) getArguments().getSerializable(Helper.ARG_ACCOUNT); - exclude_replies = !getArguments().getBoolean(Helper.ARG_SHOW_REPLIES, true); - checkRemotely = getArguments().getBoolean(Helper.ARG_CHECK_REMOTELY, false); - show_pinned = getArguments().getBoolean(Helper.ARG_SHOW_PINNED, false); - exclude_reblogs = !getArguments().getBoolean(Helper.ARG_SHOW_REBLOGS, true); - media_only = getArguments().getBoolean(Helper.ARG_SHOW_MEDIA_ONY, false); - viewModelKey = getArguments().getString(Helper.ARG_VIEW_MODEL_KEY, ""); - minified = getArguments().getBoolean(Helper.ARG_MINIFIED, false); - statusReport = (Status) getArguments().getSerializable(Helper.ARG_STATUS_REPORT); - initialStatus = (Status) getArguments().getSerializable(Helper.ARG_STATUS); - } - - - //When visiting a profile without being authenticated - if (checkRemotely) { - String[] acctArray = accountTimeline.acct.split("@"); - if (acctArray.length > 1) { - remoteInstance = acctArray[1]; - } - if (remoteInstance != null && remoteInstance.equalsIgnoreCase(currentInstance)) { - checkRemotely = false; - } else if (remoteInstance == null) { - checkRemotely = false; - } - } - if (tagTimeline != null) { - ident = "@T@" + tagTimeline.name; - if (tagTimeline.isART) { - timelineType = Timeline.TimeLineEnum.ART; - } - } else if (bubbleTimeline != null) { - ident = "@B@Bubble"; - } else if (list_id != null) { - ident = "@l@" + list_id; - } else if (remoteInstance != null && !checkRemotely) { - if (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { - ident = "@R@" + pinnedTimeline.remoteInstance.host; - } else { - ident = "@R@" + remoteInstance; - } - } else if (search != null) { - ident = "@S@" + search; - } else { - ident = null; - } - if (timelineType != null) { - slug = timelineType != Timeline.TimeLineEnum.ART ? timelineType.getValue() + (ident != null ? "|" + ident : "") : Timeline.TimeLineEnum.TAG.getValue() + (ident != null ? "|" + ident : ""); - } - - - ContextCompat.registerReceiver(requireActivity(), receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED); binding = FragmentPaginationBinding.inflate(inflater, container, false); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); + max_id = statusReport != null ? statusReport.id : null; + offset = 0; + rememberPosition = sharedpreferences.getBoolean(getString(R.string.SET_REMEMBER_POSITION), true); + //Inner marker are only for pinned timelines and main timelines, they have isViewInitialized set to false + if (max_id == null && !isViewInitialized && rememberPosition) { + max_id = sharedpreferences.getString(getString(R.string.SET_INNER_MARKER) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance + slug, null); + } + //Only fragment in main view pager should not have the view initialized + //AND Only the first fragment will initialize its view + flagLoading = false; + if (getArguments() != null) { + long bundleId = getArguments().getLong(Helper.ARG_INTENT_ID, -1); + new CachedBundle(requireActivity()).getBundle(bundleId, this::initializeAfterBundle); + } boolean displayScrollBar = sharedpreferences.getBoolean(getString(R.string.SET_TIMELINE_SCROLLBAR), false); binding.recyclerView.setVerticalScrollBarEnabled(displayScrollBar); return binding.getRoot(); } + private void initializeAfterBundle(Bundle bundle) { + new Thread(()->{ + if (bundle != null) { + timelineType = (Timeline.TimeLineEnum) bundle.get(Helper.ARG_TIMELINE_TYPE); + lemmy_post_id = bundle.getString(Helper.ARG_LEMMY_POST_ID, null); + list_id = bundle.getString(Helper.ARG_LIST_ID, null); + search = bundle.getString(Helper.ARG_SEARCH_KEYWORD, null); + searchCache = bundle.getString(Helper.ARG_SEARCH_KEYWORD_CACHE, null); + pinnedTimeline = (PinnedTimeline) bundle.getSerializable(Helper.ARG_REMOTE_INSTANCE); + if (pinnedTimeline != null && pinnedTimeline.remoteInstance != null) { + if (pinnedTimeline.remoteInstance.type != RemoteInstance.InstanceType.NITTER) { + remoteInstance = pinnedTimeline.remoteInstance.host; + } else { + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); + remoteInstance = sharedpreferences.getString(getString(R.string.SET_NITTER_HOST), getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); + canBeFederated = false; + } + } + if (timelineType == Timeline.TimeLineEnum.TREND_MESSAGE_PUBLIC) { + canBeFederated = false; + } + publicTrendsDomain = bundle.getString(Helper.ARG_REMOTE_INSTANCE_STRING, null); + isViewInitialized = bundle.getBoolean(Helper.ARG_INITIALIZE_VIEW, true); + isNotPinnedTimeline = isViewInitialized; + tagTimeline = (TagTimeline) bundle.getSerializable(Helper.ARG_TAG_TIMELINE); + bubbleTimeline = (BubbleTimeline) bundle.getSerializable(Helper.ARG_BUBBLE_TIMELINE); + accountTimeline = (Account) bundle.getSerializable(Helper.ARG_ACCOUNT); + exclude_replies = !bundle.getBoolean(Helper.ARG_SHOW_REPLIES, true); + checkRemotely = bundle.getBoolean(Helper.ARG_CHECK_REMOTELY, false); + show_pinned = bundle.getBoolean(Helper.ARG_SHOW_PINNED, false); + exclude_reblogs = !bundle.getBoolean(Helper.ARG_SHOW_REBLOGS, true); + media_only = bundle.getBoolean(Helper.ARG_SHOW_MEDIA_ONY, false); + viewModelKey = bundle.getString(Helper.ARG_VIEW_MODEL_KEY, ""); + minified = bundle.getBoolean(Helper.ARG_MINIFIED, false); + statusReport = (Status) bundle.getSerializable(Helper.ARG_STATUS_REPORT); + initialStatus = (Status) bundle.getSerializable(Helper.ARG_STATUS); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + //When visiting a profile without being authenticated + if (checkRemotely) { + String[] acctArray = accountTimeline.acct.split("@"); + if (acctArray.length > 1) { + remoteInstance = acctArray[1]; + } + if (remoteInstance != null && remoteInstance.equalsIgnoreCase(currentInstance)) { + checkRemotely = false; + } else if (remoteInstance == null) { + checkRemotely = false; + } + } + if (tagTimeline != null) { + ident = "@T@" + tagTimeline.name; + if (tagTimeline.isART) { + timelineType = Timeline.TimeLineEnum.ART; + } + } else if (bubbleTimeline != null) { + ident = "@B@Bubble"; + } else if (list_id != null) { + ident = "@l@" + list_id; + } else if (remoteInstance != null && !checkRemotely) { + if (pinnedTimeline.remoteInstance.type == RemoteInstance.InstanceType.NITTER) { + ident = "@R@" + pinnedTimeline.remoteInstance.host; + } else { + ident = "@R@" + remoteInstance; + } + } else if (search != null) { + ident = "@S@" + search; + } else { + ident = null; + } + if (timelineType != null) { + slug = timelineType != Timeline.TimeLineEnum.ART ? timelineType.getValue() + (ident != null ? "|" + ident : "") : Timeline.TimeLineEnum.TAG.getValue() + (ident != null ? "|" + ident : ""); + } + ContextCompat.registerReceiver(requireActivity(), receive_action, new IntentFilter(Helper.RECEIVE_STATUS_ACTION), ContextCompat.RECEIVER_NOT_EXPORTED); + }; + mainHandler.post(myRunnable); + }).start(); + } + /** * Update view and pagination when scrolling down * diff --git a/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java b/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java index bb5029b8d..66cc9d530 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java +++ b/app/src/main/java/app/fedilab/android/sqlite/Sqlite.java @@ -23,7 +23,7 @@ import android.database.sqlite.SQLiteOpenHelper; public class Sqlite extends SQLiteOpenHelper { - public static final int DB_VERSION = 11; + public static final int DB_VERSION = 12; public static final String DB_NAME = "fedilab_db"; //Table of owned accounts @@ -105,6 +105,9 @@ public class Sqlite extends SQLiteOpenHelper { public static final String COL_TAG = "TAG"; public static final String TABLE_TIMELINE_CACHE_LOGS = "TIMELINE_CACHE_LOGS"; + public static final String TABLE_INTENT = "INTENT"; + + public static final String COL_BUNDLE = "BUNDLE"; private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " (" @@ -233,6 +236,12 @@ public class Sqlite extends SQLiteOpenHelper { + COL_TYPE + " TEXT NOT NULL, " + COL_CREATED_AT + " TEXT NOT NULL)"; + private final String CREATE_TABLE_INTENT = "CREATE TABLE " + + TABLE_INTENT + "(" + + COL_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " + + COL_BUNDLE + " TEXT NOT NULL, " + + COL_CREATED_AT + " TEXT NOT NULL)"; + public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); @@ -263,6 +272,7 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_STORED_INSTANCES); db.execSQL(CREATE_TABLE_CACHE_TAGS); db.execSQL(CREATE_TABLE_TIMELINE_CACHE_LOGS); + db.execSQL(CREATE_TABLE_INTENT); } @Override @@ -295,6 +305,8 @@ public class Sqlite extends SQLiteOpenHelper { db.execSQL(CREATE_TABLE_CACHE_TAGS); case 10: db.execSQL(CREATE_TABLE_TIMELINE_CACHE_LOGS); + case 11: + db.execSQL(CREATE_TABLE_INTENT); default: break; } -- cgit v1.2.3