package cn.walcl.ulauncher.appdb;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import cn.walcl.ulauncher.IconCache;
import cn.walcl.ulauncher.ItemInfo;
import cn.walcl.ulauncher.ShortcutInfo;
import cn.walcl.ulauncher.Utilities;
import cn.walcl.ulauncher.settings.LauncherSettings;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
public class AppDB extends BroadcastReceiver {
private static final long INVALID_ID = -1;
private static final String PACKAGE_SEPERATOR = "/";
public static final String INTENT_DB_CHANGED = "cn.walcl.ulauncher.app_db_changed";
public static final String EXTRA_ADDED = "added";
public static final String EXTRA_DELETED_PACKAGE = "deleted_package";
public static final String EXTRA_DELETED_COMPONENT_NAMES = "deleted_cnames";
public static final String EXTRA_UPDATED = "updated";
private Context mContext;
private final IconCache mIconCache;
private final HashMap<String, LaunchInfo> mLaunchInfos = new HashMap<String, LaunchInfo>();
public AppDB(Context context, IconCache iconCache) {
mContext = context;
mIconCache = iconCache;
}
@Deprecated
public AppDB() {
mIconCache = null;
// Only for Broadcast reciever!
}
public long getId(ComponentName name) {
ContentResolver cr = mContext.getContentResolver();
Cursor c = cr.query(AppInfos.CONTENT_URI,
new String[] { AppInfos.ID },
AppInfos.COMPONENT_NAME + "=?",
new String[] { name.flattenToString() }, null);
try {
c.moveToFirst();
if (!c.isAfterLast()) {
return c.getLong(0);
}
}
finally {
c.close();
}
return INVALID_ID;
}
public boolean incrementLaunchCounter(Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_MAIN.equals(action) &&
intent.hasCategory(Intent.CATEGORY_LAUNCHER)) {
incrementLaunchCounter(intent.getComponent());
return true;
}
return false;
}
private void incrementLaunchCounter(ComponentName name) {
String cnStr = name.flattenToString();
if (mLaunchInfos.containsKey(cnStr)) {
// Update local
mLaunchInfos.get(cnStr).launched();
}
ContentResolver cr = mContext.getContentResolver();
Cursor c = cr.query( // Update within the DB
AppInfos.APP_LAUNCHED_URI.buildUpon()
.appendEncodedPath(cnStr).build(),
null, null, null, null);
if (c != null)
c.close();
}
public int getLaunchCounter(ShortcutInfo info) {
if (info != null && info.getIntent() != null) {
String action = info.getIntent().getAction();
if (Intent.ACTION_MAIN.equals(action) &&
info.getIntent().hasCategory(Intent.CATEGORY_LAUNCHER))
return getLaunchCounter(info.getIntent().getComponent());
}
return -1;
}
public int getLaunchCounter(ComponentName name) {
LaunchInfo info = getLaunchInfo(name);
if (info != null)
return info.getCount();
return -1;
}
private LaunchInfo getLaunchInfo(ComponentName name) {
String cnStr = name.flattenToString();
if (!mLaunchInfos.containsKey(cnStr)) {
ContentResolver cr = mContext.getContentResolver();
Cursor c = cr.query(AppInfos.CONTENT_URI,
new String[] {
AppInfos.LAUNCH_COUNT,
AppInfos.LAST_LAUNCHED },
AppInfos.COMPONENT_NAME + "=?",
new String[] { cnStr }, null);
try {
c.moveToFirst();
if (!c.isAfterLast()) {
LaunchInfo li = new LaunchInfo(
(int)c.getLong(c.getColumnIndex(AppInfos.LAUNCH_COUNT)),
c.getLong(c.getColumnIndex(AppInfos.LAST_LAUNCHED)));
mLaunchInfos.put(cnStr, li);
return li;
}
else
return null;
}
finally {
c.close();
}
}
return mLaunchInfos.get(cnStr);
}
@Override
public void onReceive(Context context, Intent intent) {
if (mContext == null)
mContext = context;
// This code will run outside of the launcher process!!!!!
final String action = intent.getAction();
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
|| Intent.ACTION_PACKAGE_REMOVED.equals(action)
|| Intent.ACTION_PACKAGE_ADDED.equals(action)) {
final String packageName = intent.getData().getSchemeSpecificPart();
final boolean replacing = intent.getBooleanExtra(Intent.EXTRA_REPLACING, false);
if (packageName == null || packageName.length() == 0) {
// they sent us a bad intent
return;
}
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
PackageChanged(packageName);
} else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
if (!replacing) {
PackageRemoved(packageName);
}
// else, we are replacing the package, so a PACKAGE_ADDED will be sent
// later, we will update the package at this time
} else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
if (!replacing) {
PackageAdded(packageName);
} else {
PackageChanged(packageName);
}
}
}
}
private void PackageAdded(String aPackage) {
final PackageManager packageManager = mContext.getPackageManager();
AddResolveInfos(packageManager, findActivitiesForPackage(packageManager, aPackage));
}
private void PackageChanged(String aPackage) {
final PackageManager packageManager = mContext.getPackageManager();
List<ExtResolveInfo> addedApps = new LinkedList<ExtResolveInfo>();
List<DBInfo> removedApps = new LinkedList<DBInfo>();
HashMap<ExtResolveInfo, DBInfo> updatedApps = new HashMap<ExtResolveInfo, DBInfo>();
List<ExtResolveInfo> pmInfos = toExtInfos(findActivitiesForPackage(packageManager, aPackage));
ContentResolver cr = mContext.getContentResolver();
Cursor c = queryAppsFromPackage(
new String[] {AppInfos.ID, AppInfos.COMPONENT_NAME, AppInfos.TITLE_CHANGED, AppInfos.ICON_CHANGED},
aPackage);
List<DBInfo> dbInfos = toDBInfos(c);
c.close();
// find removed / updated apps
for(DBInfo dbi : dbInfos) {
boolean found = false;
for (ExtResolveInfo pmi : pmInfos) {
if (pmi.getComponentName().equals(dbi.getComponentName())) {
found = true;
updatedApps.put(pmi, dbi); // update dbi from pmi later
break;
}
}
if (!found) {
removedApps.add(dbi); // app is no longer installed!
}
}
for (ExtResolveInfo pmi : pmInfos) {
if (updatedApps.containsKey(pmi))
continue; // alread in updateable apps
addedApps.add(pmi); // not updated, not removed so it must be added ;-)
}
Intent modelIntent = new Intent(INTENT_DB_CHANGED);
boolean sendIntent = removedApps.size() > 0 || updatedApps.size() > 0;
// Ok we got all needed infos so lets start the party:
AddResolveInfos(packageManager, addedApps); // adding is easy!
// removing is a little harder:
DestroyItems(removedApps);
if (sendIntent)
modelIntent.putExtra(EXTRA_DELETED_COMPONENT_NAMES, getPackageNames(removedApps));
// ok then updating is left:
long[] updatedIds = new long[updatedApps.size()];
int i = 0;
for (ExtResolveInfo pmInfo : updatedApps.keySet()) {
DBInfo dbinfo = updatedApps.get(pmInfo);
boolean iconChanged = dbinfo.isIconChanged();
boolean titleChanged = dbinfo.isTitleChanged();
if ( !titleChanged || !iconChanged )
{
ResolveInfo rInfo = pmInfo.getResolveInfo();
Bitmap icon = Utilities.createIconBitmap(
rInfo.loadIcon(packageManager), mContext);
ContentValues values = new ContentValues();
if ( !titleChanged )
{
values.put(AppInfos.TITLE, rInfo.loadLabel(packageManager).toString());
}
if ( !iconChanged )
{
ItemInfo.writeBitmap(values, icon);
}
cr.update(AppInfos.CONTENT_URI, values, AppInfos.ID + " = ?",
new String[] { String.valueOf(dbinfo.getId()) } );
updatedIds[i++] = dbinfo.getId();
}
}
if (i > 0)
modelIntent.putExtra(EXTRA_UPDATED, updatedIds);
// Notify Model:
mContext.sendBroadcast(modelIntent);
}
private List<ExtResolveInfo> toExtInfos(List<ResolveInfo> list) {
List<ExtResolveInfo> result = new ArrayList<ExtResolveInfo>(list.size());
for(ResolveInfo info : list) {
result.add(new ExtResolveInfo(info));
}
return result;
}
private List<DBInfo> toDBInfos(Cursor c) {
List<DBInfo> result = new LinkedList<DBInfo>();
if (c.moveToFirst()) {
while(!c.isAfterLast()) {
result.add(new DBInfo(c));
c.moveToNext();
}
}
return result;
}
private void PackageRemoved(String aPackage) {
List<DBInfo> infos = new LinkedList<DBInfo>();
Cursor c = queryAppsFromPackage(new String[] { AppInfos.ID, AppInfos.COMPONENT_NAME }, aPackage);
try {
c.moveToFirst();
c.getColumnIndex(AppInfos.ID);
c.getColumnIndex(AppInfos.COMPONENT_NAME);
c.getColumnIndex(AppInfos.TITLE_CHANGED);
c.getColumnIndex(AppInfos.ICON_CHANGED);
while(!c.isAfterLast()) {
DBInfo info = new DBInfo(c);
infos.add(info);
c.moveToNext();
}
} finally {
c.close();
}
DestroyItems( infos);
// notify the LauncherModel too!
Intent deleteIntent = new Intent(INTENT_DB_CHANGED);
// remove all items from the package!
deleteIntent.putExtra(EXTRA_DELETED_PACKAGE, aPackage);
mContext.sendBroadcast(deleteIntent);
}
private void DestroyItems(List<DBInfo> infos) {
if (infos.size() > 0) {
String deleteFlt = getAppIdFilter(getIds(infos));
ContentResolver cr = mContext.getContentResolver();
cr.delete(AppInfos.CONTENT_URI, deleteFlt, null);
RemoveShortcutsFromWorkspace(infos);
}
}
public static boolean arrayContains(String[] array, String value) {
for (String itm : array) {
if (itm.equals(value))
return true;
}
return false;
}
private static boolean InfosContains(List<DBInfo> infos, String value) {
for (DBInfo itm : infos) {
if (value.equals(itm.getComponentName()))
return true;
}
return false;
}
private static long[] getIds(List<DBInfo> infos) {
long[] result = new long[infos.size()];
for (int i = 0; i < infos.size(); i++) {
result[i] = infos.get(i).getId();
}
return result;
}
private static String[] getPackageNames(List<DBInfo> infos) {
String[] result = new String[infos.size()];
for (int i = 0; i < infos.size(); i++) {
result[i] = infos.get(i).getComponentName();
}
return result;
}
private void RemoveShortcutsFromWorkspace(List<DBInfo> infos) {
final ContentResolver cr = mContext.getContentResolver();
Cursor c = cr.query(LauncherSettings.Favorites.CONTENT_URI,
new String[] { LauncherSettings.Favorites._ID,
LauncherSettings.Favorites.INTENT },
LauncherSettings.Favorites.INTENT + " is not null", null, null);
long[] ids = null;
try {
if (c != null && c.moveToFirst()) {
// prepare the dirty work!
ids = new long[c.getCount()];
int IDColumnIndex = c.getColumnIndex(LauncherSettings.Favorites._ID);
int IntentColumnIndex = c.getColumnIndex(LauncherSettings.Favorites.INTENT);
int idx = 0;
while (!c.isAfterLast())
{
String intentStr = c.getString(IntentColumnIndex);
try {
Intent intent = Intent.parseUri(intentStr, 0);
if (intent != null) {
ComponentName cname = intent.getComponent();
if (cname != null ) {
String cnameStr = cname.flattenToString();
if (InfosContains(infos, cnameStr)) {
c.getLong(IDColumnIndex);
ids[idx++] = c.getLong(IDColumnIndex);
}else
ids[idx++] = INVALID_ID;
} else {
ids[idx++] = INVALID_ID;
}
} else
ids[idx++] = INVALID_ID;
}
catch(URISyntaxException expt) {
ids[idx++] = INVALID_ID;
}
c.moveToNext();
}
}
} finally {
c.close();
}
if (ids != null) {
for (long id : ids) {
if (id != INVALID_ID)
cr.delete(LauncherSettings.Favorites.getContentUri(id, false), null, null);
}
}
}
private Cursor queryAppsFromPackage(String[] columns, String aPackage) {
aPackage = aPackage + PACKAGE_SEPERATOR;
String pkgflt = "substr("+AppInfos.COMPONENT_NAME + ",1,"+ aPackage.length() +") = ?";
final ContentResolver cr = mContext.getContentResolver();
return cr.query(AppInfos.CONTENT_URI,
columns, pkgflt, new String[] { aPackage }, null);
}
private static Bitmap getIconFromCursor(Cursor c, int iconIndex) {
byte[] data = c.getBlob(iconIndex);
try {
return BitmapFactory.decodeByteArray(data, 0, data.length);
} catch (Exception e) {
return null;
}
}
public List<ShortcutInfo> getApps() {
return getApps(null);
}
private String getAppIdFilter(long[] appIds) {
if (appIds == null)
return null;
StringBuilder sb = new StringBuilder();
for(int i = 0; i < appIds.length; i++) {
if (i > 0)
sb.append(" or ");
sb.append(AppInfos.ID);
sb.append("=");
sb.append(appIds[i]);
}
return sb.toString();
}
public List<ShortcutInfo> getApps(long[] appIds) {
ArrayList<ShortcutInfo> result = new ArrayList<ShortcutInfo>();
ContentResolver cr = mContext.getContentResolver();
Cursor c = cr.query(AppInfos.CONTENT_URI, new String[] {
AppInfos.ID,
AppInfos.COMPONENT_NAME,
AppInfos.ICON,
AppInfos.ICON_CHANGED,
AppInfos.TITLE,
AppInfos.TITLE_CHANGED,
AppInfos.LAST_LAUNCHED,
AppInfos.LAUNCH_COUNT
}, getAppIdFilter(appIds), null, null);
try {
c.moveToFirst();
final int idIdx = c.getColumnIndex(AppInfos.ID);
final int iconIdx = c.getColumnIndex(AppInfos.ICON);
final int iconChangedIdx = c.getColumnIndex(AppInfos.ICON_CHANGED);
final int cnIdx = c.getColumnIndex(AppInfos.COMPONENT_NAME);
final int titleIdx = c.getColumnIndex(AppInfos.TITLE);
final int titleChangedIdx = c.getColumnIndex(AppInfos.TITLE_CHANGED);
final int launchcntIdx = c.getColumnIndex(AppInfos.LAUNCH_COUNT);
final int lastlaunchIdx = c.getColumnIndex(AppInfos.LAST_LAUNCHED);
while(!c.isAfterLast()) {
Bitmap icon = getIconFromCursor(c, iconIdx);
String cnStr = c.getString(cnIdx);
String title = c.getString(titleIdx);
if (mLaunchInfos.containsKey(cnStr))
mLaunchInfos.remove(cnStr);
mLaunchInfos.put(cnStr,
new LaunchInfo(
(int)c.getLong(launchcntIdx),
c.getLong(lastlaunchIdx)));
ComponentName cname = ComponentName.unflattenFromString(cnStr);
if (mIconCache != null)
mIconCache.addToCache(cname, title, icon);
ShortcutInfo info = new ShortcutInfo(c.getLong(idIdx), cname);
info.setTitleInAppsDb(c.getInt( titleChangedIdx ) == 1);
info.setIconInAppsDb( c.getInt( iconChangedIdx ) == 1 );
result.add(info);
c.moveToNext();
}
}
finally {
c.close();
}
return result;
}
public void updateLocale(String newLocale) {
ContentResolver resolver = mContext.getContentResolver();
PackageManager pm = mContext.getPackageManager();
// Query all infos with a different locale:
final Cursor c = resolver.query(AppInfos.CONTENT_URI,
new String[] { AppInfos.ID, AppInfos.COMPONENT_NAME }, AppInfos.LOCALE + " <> ?",
new String[] { newLocale }, null);
try {
if (c.moveToFirst()) {
long[] updatedIds = new long[c.getCount()];
int idx = 0;
int idCol = c.getColumnIndex(AppInfos.ID);
int cnCol = c.getColumnIndex(AppInfos.COMPONENT_NAME);
while(!c.isAfterLast()) {
long id = c.getLong(idCol);
ComponentName cn = ComponentName.unflattenFromString(c.getString(cnCol));
if (cn != null) {
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mainIntent.setComponent(cn);
ResolveInfo ri = pm.resolveActivity(mainIntent, 0);
ContentValues cv = new ContentValues();
String title = ri.loadLabel(pm).toString();
cv.put(AppInfos.TITLE, title);
cv.put(AppInfos.LOCALE, newLocale);
resolver.update(AppInfos.getContentUri(id), cv, null, null);
updatedIds[idx++] = id;
}
c.moveToNext();
}
Intent updateIntent = new Intent(INTENT_DB_CHANGED);
updateIntent.putExtra(EXTRA_UPDATED, updatedIds);
mContext.sendBroadcast(updateIntent);
}
} finally {
c.close();
}
}
private List<ResolveInfo> findActivitiesForPackage(PackageManager packageManager, String packageName) {
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
mainIntent.setPackage(packageName);
final List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
return apps != null ? apps : new ArrayList<ResolveInfo>();
}
static ContentValues[] ResolveInfosToContentValues(Context context, List<?> infos) {
PackageManager packageManager = context.getPackageManager();
String curLocale = Locale.getDefault().toString();
ContentValues[] result = new ContentValues[infos.size()];
int i = 0;
for(Object oinfo : infos) {
final ResolveInfo info;
if (oinfo instanceof ResolveInfo)
info = (ResolveInfo)oinfo;
else if (oinfo instanceof ExtResolveInfo)
info = ((ExtResolveInfo)oinfo).getResolveInfo();
else
continue;
ComponentName componentName = new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name);
String title = info.loadLabel(packageManager).toString();
if (title == null) {
title = info.activityInfo.name;
}
Bitmap icon = Utilities.createIconBitmap(
info.activityInfo.loadIcon(packageManager), context);
ContentValues values = new ContentValues();
values.put(AppInfos.TITLE, title);
values.put(AppInfos.LOCALE, curLocale);
ItemInfo.writeBitmap(values, icon);
values.put(AppInfos.COMPONENT_NAME, componentName.flattenToString());
values.put(AppInfos.LAUNCH_COUNT, 0);
result[i++] = values;
}
return result;
}
private void AddResolveInfos(PackageManager packageManager, List<?> infos) {
ContentResolver cr = mContext.getContentResolver();
long[] added = new long[infos.size()];
int i = 0;
for(ContentValues values : ResolveInfosToContentValues(mContext, infos)) {
added[i++] = ContentUris.parseId(cr.insert(AppInfos.CONTENT_URI, values));
}
Intent updateIntent = new Intent(INTENT_DB_CHANGED);
updateIntent.putExtra("added", added);
mContext.sendBroadcast(updateIntent);
}
private class ExtResolveInfo {
private final ResolveInfo mResolveInfo;
private final String mComponentName;
public ExtResolveInfo(ResolveInfo info) {
mResolveInfo = info;
mComponentName = new ComponentName(
info.activityInfo.applicationInfo.packageName,
info.activityInfo.name).flattenToString();
}
public String getComponentName() {
return mComponentName;
}
public ResolveInfo getResolveInfo() {
return mResolveInfo;
}
}
private class DBInfo {
private final long mId;
private final String mComponentName;
private final boolean mTitleChanged;
private final boolean mIconChanged;
public DBInfo(Cursor c) {
mId = c.getLong(c.getColumnIndex(AppInfos.ID));
mComponentName = c.getString(c.getColumnIndex(AppInfos.COMPONENT_NAME));
int tcidx = c.getColumnIndex(AppInfos.TITLE_CHANGED);
mTitleChanged = (tcidx >= 0) && (c.getInt(tcidx) == 1);
int icidx = c.getColumnIndex(AppInfos.ICON_CHANGED);
mIconChanged = (icidx >= 0) && (c.getInt(icidx) == 1);
}
public long getId(){
return mId;
}
public String getComponentName() {
return mComponentName;
}
public boolean isTitleChanged()
{
return mTitleChanged;
}
public boolean isIconChanged()
{
return mIconChanged;
}
}
public static final String APPINFOS = "appinfos";
public static class AppInfos {
public static final String ID = "_id";
public static final String COMPONENT_NAME = "componentname";
public static final String LAUNCH_COUNT = "launchcount";
public static final String LAST_LAUNCHED = "lastlaunched";
public static final String TITLE = "title";
public static final String TITLE_CHANGED = "titlechanged";
public static final String ICON = "icon";
public static final String ICON_CHANGED = "iconchanged";
public static final String LOCALE = "locale";
static final Uri CONTENT_URI = Uri.parse("content://" +
AppDBProvider.AUTHORITY + "/" + APPINFOS);
static final Uri APP_LAUNCHED_URI = Uri.parse("content://"+
AppDBProvider.AUTHORITY + "/launched");
/**
* The content:// style URL for a given row, identified by its id.
*
* @param id The row id.
* @param notify True to send a notification is the content changes.
*
* @return The unique content URL for the specified row.
*/
static Uri getContentUri(long id) {
return Uri.parse("content://" + AppDBProvider.AUTHORITY +
"/" + APPINFOS + "/" + id);
}
}
public void updateAppDisplay(long id, String title, Bitmap icon) {
ContentResolver cr = mContext.getContentResolver();
final Uri uri = AppInfos.getContentUri(id);
final ContentValues values = new ContentValues();
byte[] data = null;
if ( icon != null )
{
data = Utilities.flattenBitmap(icon);
}
values.put(AppInfos.ICON, data);
values.put(AppInfos.ICON_CHANGED, data == null?0:1);
values.put(AppInfos.TITLE, title);
values.put(AppInfos.TITLE_CHANGED, title == null?0:1);
cr.update(uri, values, null, null);
}
}