Add support for 'theme packs'

Right now only replacing the icon in the action bar is supported.
That's why the term "Icon Pack" is used in the UI.

Known bug: In a PreferenceActivity the second level still shows the
default app icon in the action bar :(

Icon packs need to include an activity with an intent filter for
the action "org.k9mail.THEME_PACK".

Example:

<activity android:name=".SomeActivity">
  <intent-filter>
    <action android:name="org.k9mail.THEME_PACK" />
  </intent-filter>

  <!-- Version number for the theme pack format -->
  <meta-data android:name="version" android:value="1"/>

  <meta-data android:name="name" android:value="Fancy icon"/>
  <meta-data android:name="author" android:value="cketti"/>
  <meta-data android:name="icon_app"
             android:resource="@drawable/ic_app"/>
</activity>
This commit is contained in:
cketti 2013-06-30 05:22:00 +02:00
parent 85e7d4fa12
commit 93b94f58c8
16 changed files with 463 additions and 6 deletions

View File

@ -432,6 +432,11 @@ otherwise it would make K-9 start at the wrong time
android:name=".activity.UpgradeDatabases"
android:label="@string/upgrade_databases_title">
</activity>
<activity
android:name=".activity.setup.ThemePackActivity"
android:label="@string/global_settings_theme_pack_label">
</activity>
<service
android:name=".service.DatabaseUpgradeService"
android:exported="false">

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/theme_pack_list"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:gravity="center_vertical"
android:paddingLeft="8dp"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight">
<ImageView
android:id="@+id/theme_pack_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="12dp"
android:layout_alignParentLeft="true"/>
<TextView
android:id="@+id/theme_pack_name"
android:layout_toRightOf="@id/theme_pack_icon"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/theme_pack_author"
android:textColor="?android:attr/textColorSecondary"
android:layout_toRightOf="@id/theme_pack_icon"
android:layout_below="@id/theme_pack_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>

View File

@ -71,6 +71,8 @@ Please submit bug reports, contribute new features and ask questions at
<string name="account_recreate_dlg_instructions_fmt">All local data for \"<xliff:g id="account">%s</xliff:g>\" will be removed. Account settings will be retained.</string>
<string name="account_clear_dlg_instructions_fmt">Local copies of messages in \"<xliff:g id="account">%s</xliff:g>\" will be removed. Account settings will be retained.</string>
<string name="insufficient_apg_permissions">K-9 doesn\'t have permission to access APG fully, please reinstall K-9 to fix that.</string>
<string name="default_theme_pack_name">K-9 Mail default</string>
<string name="default_theme_pack_author">The K-9 Dog Walkers</string>
@ -1152,4 +1154,6 @@ Please submit bug reports, contribute new features and ask questions at
<string name="preposition_for_date">on <xliff:g id="date">%s</xliff:g></string>
<string name="mark_all_as_read">Mark all as read</string>
<string name="global_settings_theme_pack_label">Icon Pack</string>
</resources>

View File

@ -73,6 +73,12 @@
android:entryValues="@array/settings_message_theme_values"
android:dialogTitle="@string/settings_compose_theme_label" />
<Preference
android:persistent="false"
android:key="theme_pack"
android:singleLine="true"
android:title="@string/global_settings_theme_pack_label" />
<Preference
android:persistent="false"
android:key="font_size"

View File

@ -16,10 +16,12 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Debug;
import android.os.Environment;
@ -43,6 +45,9 @@ import com.fsck.k9.service.BootReceiver;
import com.fsck.k9.service.MailService;
import com.fsck.k9.service.ShutdownReceiver;
import com.fsck.k9.service.StorageGoneReceiver;
import com.fsck.k9.theme.ThemeData;
import com.fsck.k9.theme.ThemePack;
import com.fsck.k9.theme.ThemePackLoader;
public class K9 extends Application {
/**
@ -260,6 +265,9 @@ public class K9 extends Application {
private static boolean sUseBackgroundAsUnreadIndicator = true;
private static boolean sThreadedViewEnabled = true;
private static SplitViewMode sSplitViewMode = SplitViewMode.NEVER;
private static ThemeData sThemeData;
private static String sThemeAppName;
private static String sThemeActivityName;
/**
* @see #areDatabasesUpToDate()
@ -537,6 +545,8 @@ public class K9 extends Application {
editor.putBoolean("useBackgroundAsUnreadIndicator", sUseBackgroundAsUnreadIndicator);
editor.putBoolean("threadedView", sThreadedViewEnabled);
editor.putString("splitViewMode", sSplitViewMode.name());
editor.putString("themePackAppName", sThemeAppName);
editor.putString("themePackActivityName", sThemeActivityName);
fontSizes.save(editor);
}
@ -768,6 +778,10 @@ public class K9 extends Application {
K9.setK9Theme(Theme.LIGHT);
}
String appName = sprefs.getString("themePackAppName", null);
String activityName = sprefs.getString("themePackActivityName", null);
setThemePackInfo(appName, activityName);
themeValue = sprefs.getInt("messageViewTheme", Theme.USE_GLOBAL.ordinal());
K9.setK9MessageViewThemeSetting(Theme.values()[themeValue]);
themeValue = sprefs.getInt("messageComposeTheme", Theme.USE_GLOBAL.ordinal());
@ -1302,6 +1316,41 @@ public class K9 extends Application {
sShowContactPicture = show;
}
public static ComponentName getThemePackComponentName() {
return (sThemeActivityName == null) ?
null : new ComponentName(sThemeAppName, sThemeActivityName);
}
public static void setThemePackInfo(String appName, String activityName) {
sThemeAppName = appName;
sThemeActivityName = activityName;
setThemeData(K9.app, appName, activityName);
}
public static ThemeData getThemeData() {
return sThemeData;
}
public static void setThemeData(Context context, String appName, String activityName) {
if (activityName == null) {
sThemeData = null;
return;
}
ComponentName componentName = new ComponentName(appName, activityName);
Drawable icon = new ThemePackLoader(context).getIcon(componentName);
ThemeData themeData;
if (icon == null) {
themeData = null;
} else {
themeData = new ThemeData();
themeData.icon = icon;
}
sThemeData = themeData;
}
/**
* Check if we already know whether all databases are using the current database schema.
*

View File

@ -19,6 +19,18 @@ public class K9Activity extends SherlockActivity implements K9ActivityMagic {
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
mBase.onResume(getSupportActionBar());
}
@Override
public void setContentView(int layoutResId) {
super.setContentView(layoutResId);
mBase.setContentView(getSupportActionBar());
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
mBase.preDispatchTouchEvent(event);

View File

@ -2,10 +2,13 @@ package com.fsck.k9.activity;
import java.util.Locale;
import com.actionbarsherlock.app.ActionBar;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.activity.misc.SwipeGestureDetector;
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.theme.ThemeData;
import android.app.Activity;
import android.content.Context;
@ -68,6 +71,7 @@ public class K9ActivityCommon {
private Activity mActivity;
private GestureDetector mGestureDetector;
private ThemeData mThemeData;
private K9ActivityCommon(Activity activity) {
@ -76,6 +80,16 @@ public class K9ActivityCommon {
mActivity.setTheme(K9.getK9ThemeResourceId());
}
public void setContentView(ActionBar actionBar) {
setActionBarIcon(actionBar);
}
public void onResume(ActionBar actionBar) {
if (actionBar != null && mThemeData != K9.getThemeData()) {
setActionBarIcon(actionBar);
}
}
/**
* Call this before calling {@code super.dispatchTouchEvent(MotionEvent)}.
*/
@ -112,4 +126,13 @@ public class K9ActivityCommon {
mGestureDetector = new GestureDetector(mActivity,
new SwipeGestureDetector(mActivity, listener));
}
private void setActionBarIcon(ActionBar actionBar) {
mThemeData = K9.getThemeData();
if (mThemeData == null) {
actionBar.setIcon(R.drawable.icon);
} else {
actionBar.setIcon(mThemeData.icon);
}
}
}

View File

@ -19,6 +19,18 @@ public class K9FragmentActivity extends SherlockFragmentActivity implements K9Ac
super.onCreate(savedInstanceState);
}
@Override
protected void onResume() {
super.onResume();
mBase.onResume(getSupportActionBar());
}
@Override
public void setContentView(int layoutResId) {
super.setContentView(layoutResId);
mBase.setContentView(getSupportActionBar());
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
mBase.preDispatchTouchEvent(event);

View File

@ -22,6 +22,12 @@ public class K9ListActivity extends SherlockListActivity implements K9ActivityMa
super.onCreate(savedInstanceState);
}
@Override
public void setContentView(int layoutResId) {
super.setContentView(layoutResId);
mBase.setContentView(getSupportActionBar());
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
mBase.preDispatchTouchEvent(event);
@ -31,6 +37,7 @@ public class K9ListActivity extends SherlockListActivity implements K9ActivityMa
@Override
public void onResume() {
super.onResume();
mBase.onResume(getSupportActionBar());
}
@Override

View File

@ -10,20 +10,37 @@ import android.preference.Preference;
public class K9PreferenceActivity extends SherlockPreferenceActivity {
@Override
public void onCreate(Bundle icicle) {
K9ActivityCommon.setLanguage(this, K9.getK9Language());
private K9ActivityCommon mBase;
@Override
public void onCreate(Bundle savedInstanceState) {
mBase = K9ActivityCommon.newInstance(this);
super.onCreate(savedInstanceState);
}
@Override
public void setTheme(int resId) {
if (Build.VERSION.SDK_INT >= 6 && Build.VERSION.SDK_INT < 14) {
// There's a display bug in all supported Android versions before 4.0 (SDK 14) which
// causes PreferenceScreens to have a black background.
// http://code.google.com/p/android/issues/detail?id=4611
setTheme(K9.getK9ThemeResourceId(K9.Theme.DARK));
super.setTheme(K9.getK9ThemeResourceId(K9.Theme.DARK));
} else {
setTheme(K9.getK9ThemeResourceId());
super.setTheme(K9.getK9ThemeResourceId());
}
}
super.onCreate(icicle);
@Override
public void setContentView(int layoutResId) {
super.setContentView(layoutResId);
mBase.setContentView(getSupportActionBar());
}
@Override
protected void onResume() {
super.onResume();
mBase.onResume(getSupportActionBar());
}
/**

View File

@ -56,6 +56,7 @@ public class Prefs extends K9PreferenceActivity {
private static final String PREFERENCE_FIXED_MESSAGE_THEME = "fixedMessageViewTheme";
private static final String PREFERENCE_COMPOSER_THEME = "messageComposeTheme";
private static final String PREFERENCE_FONT_SIZE = "font_size";
private static final String PREFERENCE_THEME_PACK = "theme_pack";
private static final String PREFERENCE_ANIMATIONS = "animations";
private static final String PREFERENCE_GESTURES = "gestures";
private static final String PREFERENCE_VOLUME_NAVIGATION = "volumeNavigation";
@ -182,6 +183,14 @@ public class Prefs extends K9PreferenceActivity {
}
});
findPreference(PREFERENCE_THEME_PACK).setOnPreferenceClickListener(
new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
onThemePackSettings();
return true;
}
});
mAnimations = (CheckBoxPreference)findPreference(PREFERENCE_ANIMATIONS);
mAnimations.setChecked(K9.showAnimations());
@ -505,6 +514,10 @@ public class Prefs extends K9PreferenceActivity {
FontSizeSettings.actionEditSettings(this);
}
private void onThemePackSettings() {
ThemePackActivity.actionEditSettings(this);
}
private void onChooseContactNameColor() {
new ColorPickerDialog(this, new ColorPickerDialog.OnColorChangedListener() {
public void colorChanged(int color) {

View File

@ -0,0 +1,164 @@
package com.fsck.k9.activity.setup;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.MenuItem;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.theme.ThemePack;
import com.fsck.k9.theme.ThemePackLoader;
import java.util.ArrayList;
import java.util.List;
public class ThemePackActivity extends K9Activity implements AdapterView.OnItemClickListener {
public static void actionEditSettings(Context context) {
Intent i = new Intent(context, ThemePackActivity.class);
context.startActivity(i);
}
private ThemePackAdapter mAdapter;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.theme_pack_list);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
initializeListView();
}
private void initializeListView() {
ListView listView = (ListView) findViewById(R.id.theme_pack_list);
List<ThemePack> themePacks = new ThemePackLoader(this).loadPacks();
List<ThemePack> myThemePacks = new ArrayList<ThemePack>(themePacks.size() + 1);
myThemePacks.add(createDefaultThemePack());
myThemePacks.addAll(themePacks);
mAdapter = new ThemePackAdapter(this, myThemePacks);
listView.setAdapter(mAdapter);
listView.setOnItemClickListener(this);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
if (position == 0) {
K9.setThemePackInfo(null, null);
} else {
ThemePack themePack = mAdapter.getItem(position);
ComponentName componentName = themePack.componentName;
K9.setThemePackInfo(componentName.getPackageName(), componentName.getClassName());
}
SharedPreferences preferences = Preferences.getPreferences(this).getPreferences();
SharedPreferences.Editor editor = preferences.edit();
K9.save(editor);
editor.commit();
finish();
}
private ThemePack createDefaultThemePack() {
ThemePack themePack = new ThemePack();
themePack.name = getString(R.string.default_theme_pack_name);
themePack.author = getString(R.string.default_theme_pack_author);
themePack.componentName = getComponentName();
themePack.iconIdentifier = R.drawable.icon;
return themePack;
}
static class ThemePackAdapter extends ArrayAdapter<ThemePack> {
private final LayoutInflater mLayoutInflater;
private final PackageManager mPackageManager;
private final ComponentName mCurrentThemePack;
public ThemePackAdapter(Context context, List<ThemePack> themePacks) {
super(context, R.layout.theme_pack_list_item, R.id.theme_pack_name, themePacks);
mLayoutInflater = LayoutInflater.from(context);
mPackageManager = context.getPackageManager();
mCurrentThemePack = K9.getThemePackComponentName();
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view;
ThemePackHolder holder;
if (convertView == null) {
view = mLayoutInflater.inflate(R.layout.theme_pack_list_item, parent, false);
holder = new ThemePackHolder();
holder.name = (TextView) view.findViewById(R.id.theme_pack_name);
holder.author = (TextView) view.findViewById(R.id.theme_pack_author);
holder.icon = (ImageView) view.findViewById(R.id.theme_pack_icon);
view.setTag(holder);
} else {
view = convertView;
holder = (ThemePackHolder) view.getTag();
}
ThemePack themePack = getItem(position);
holder.name.setText(themePack.name);
holder.author.setText(themePack.author);
try {
//TODO: optimize
Resources resources = mPackageManager.getResourcesForActivity(themePack.componentName);
Drawable drawable = resources.getDrawable(themePack.iconIdentifier);
holder.icon.setImageDrawable(drawable);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (mCurrentThemePack == null && position == 0 ||
themePack.componentName.equals(mCurrentThemePack)) {
//view.setBackgroundColor(color);
}
return view;
}
}
static class ThemePackHolder {
public TextView name;
public TextView author;
public ImageView icon;
}
}

View File

@ -0,0 +1,7 @@
package com.fsck.k9.theme;
import android.graphics.drawable.Drawable;
public class ThemeData {
public Drawable icon;
}

View File

@ -0,0 +1,17 @@
package com.fsck.k9.theme;
import android.content.ComponentName;
public class ThemePack {
public static final String INTENT = "org.k9mail.THEME_PACK";
public static final String ATTR_VERSION = "version";
public static final String ATTR_NAME = "name";
public static final String ATTR_AUTHOR = "author";
public static final String ATTR_ICON_APP = "icon_app";
public String name;
public String author;
public int iconIdentifier;
public ComponentName componentName;
}

View File

@ -0,0 +1,77 @@
package com.fsck.k9.theme;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import com.fsck.k9.K9;
import java.util.ArrayList;
import java.util.List;
public class ThemePackLoader {
private final PackageManager mPackageManager;
public ThemePackLoader(Context context) {
mPackageManager = context.getPackageManager();
}
public List<ThemePack> loadPacks() {
List<ThemePack> themePacks = new ArrayList<ThemePack>();
try {
List<ResolveInfo> resolveInfos = mPackageManager.queryIntentActivities(
new Intent(ThemePack.INTENT), PackageManager.GET_META_DATA);
for (ResolveInfo resolveInfo : resolveInfos) {
ComponentName componentName = new ComponentName(
resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name);
Bundle metaData = resolveInfo.activityInfo.metaData;
if (metaData != null) {
int version = metaData.getInt(ThemePack.ATTR_VERSION);
if (version >= 1) {
//TODO: validate attributes
ThemePack themePack = new ThemePack();
themePack.componentName = componentName;
themePack.name = metaData.getString(ThemePack.ATTR_NAME);
themePack.author = metaData.getString(ThemePack.ATTR_AUTHOR);
themePack.iconIdentifier = metaData.getInt(ThemePack.ATTR_ICON_APP);
themePacks.add(themePack);
}
}
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to enumerate theme packs", e);
}
return themePacks;
}
public Drawable getIcon(ComponentName componentName) {
try {
ActivityInfo info = mPackageManager.getActivityInfo(
componentName, PackageManager.GET_META_DATA);
int iconIdentifier = info.metaData.getInt(ThemePack.ATTR_ICON_APP);
Resources resources = mPackageManager.getResourcesForActivity(componentName);
return resources.getDrawable(iconIdentifier);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Failed to load icon / " + componentName, e);
}
return null;
}
}