diff options
author | Josh Guilfoyle <Josh.Guilfoyle@T-Mobile.com> | 2009-11-19 17:43:25 -0800 |
---|---|---|
committer | Josh Guilfoyle <Josh.Guilfoyle@T-Mobile.com> | 2009-11-24 13:29:47 -0800 |
commit | 6fff487e464c7cb044ffac0e1dd73d353c82f2ee (patch) | |
tree | 2dd74850f731150b32759a1ee47c1341e72550bd | |
parent | 9db3d07b9620b4269ab33f78604a36327e536ce1 (diff) | |
parent | 9effa6dbd3c7d95e243fcc694f80f2628eac95ca (diff) | |
download | frameworks_base-6fff487e464c7cb044ffac0e1dd73d353c82f2ee.zip frameworks_base-6fff487e464c7cb044ffac0e1dd73d353c82f2ee.tar.gz frameworks_base-6fff487e464c7cb044ffac0e1dd73d353c82f2ee.tar.bz2 |
Merge branch 'themes-1.5r2' into themes-eclair
Conflicts:
api/current.xml
core/java/android/app/ActivityThread.java
core/java/android/app/ApplicationContext.java
core/java/android/app/Dialog.java
core/java/android/app/SearchDialog.java
core/java/android/content/Context.java
core/java/android/content/Intent.java
core/java/android/content/pm/PackageInfo.java
core/java/android/content/pm/PackageParser.java
core/java/android/content/res/Configuration.java
core/java/android/content/res/Resources.java
core/java/android/widget/RemoteViews.java
core/java/android/widget/TabWidget.java
core/java/com/android/internal/os/ZygoteInit.java
core/jni/android_util_AssetManager.cpp
core/res/AndroidManifest.xml
core/res/res/layout/search_dropdown_item_2line.xml
core/res/res/layout/select_dialog_item.xml
core/res/res/layout/select_dialog_multichoice.xml
core/res/res/layout/select_dialog_singlechoice.xml
core/res/res/layout/tab_indicator.xml
core/res/res/values/attrs.xml
core/res/res/values/public.xml
core/res/res/values/strings.xml
core/res/res/values/themes.xml
libs/utils/Android.mk
libs/utils/AssetManager.cpp
media/java/android/media/RingtoneManager.java
services/java/com/android/server/PackageManagerService.java
services/java/com/android/server/SystemServer.java
services/java/com/android/server/am/ActivityManagerService.java
services/java/com/android/server/status/StatusBarService.java
tools/aapt/Main.cpp
100 files changed, 4868 insertions, 484 deletions
diff --git a/api/current.xml b/api/current.xml index 32d0f1e..4898339 100644 --- a/api/current.xml +++ b/api/current.xml @@ -1758,6 +1758,17 @@ visibility="public" > </field> +<field name="alertDialogTheme" + type="int" + transient="false" + volatile="false" + value="16843448" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="allowBackup" type="int" transient="false" @@ -2110,6 +2121,17 @@ visibility="public" > </field> +<field name="bottomLeftStrip" + type="int" + transient="false" + volatile="false" + value="16843451" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="bottomMedium" type="int" transient="false" @@ -2143,6 +2165,17 @@ visibility="public" > </field> +<field name="bottomRightStrip" + type="int" + transient="false" + volatile="false" + value="16843452" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="bufferType" type="int" transient="false" @@ -2198,6 +2231,28 @@ visibility="public" > </field> +<field name="buttonStyleTimePickerDown" + type="int" + transient="false" + volatile="false" + value="16843457" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="buttonStyleTimePickerUp" + type="int" + transient="false" + volatile="false" + value="16843456" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="buttonStyleToggle" type="int" transient="false" @@ -2913,6 +2968,17 @@ visibility="public" > </field> +<field name="dialogTheme" + type="int" + transient="false" + volatile="false" + value="16843447" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="dialogTitle" type="int" transient="false" @@ -3441,6 +3507,17 @@ visibility="public" > </field> +<field name="expandedMenuTheme" + type="int" + transient="false" + volatile="false" + value="16843459" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="exported" type="int" transient="false" @@ -4145,6 +4222,17 @@ visibility="public" > </field> +<field name="iconMenuTheme" + type="int" + transient="false" + volatile="false" + value="16843460" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="iconPreview" type="int" transient="false" @@ -5289,6 +5377,28 @@ visibility="public" > </field> +<field name="listItemBackground" + type="int" + transient="false" + volatile="false" + value="16843446" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="listItemTextViewStyle" + type="int" + transient="false" + volatile="false" + value="16843450" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="listPreferredItemHeight" type="int" transient="false" @@ -5520,6 +5630,17 @@ visibility="public" > </field> +<field name="menuItemBackground" + type="int" + transient="false" + volatile="false" + value="16843458" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="mimeType" type="int" transient="false" @@ -6983,6 +7104,17 @@ visibility="public" > </field> +<field name="searchDialogTheme" + type="int" + transient="false" + volatile="false" + value="16843449" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="searchMode" type="int" transient="false" @@ -7742,6 +7874,17 @@ visibility="public" > </field> +<field name="tabIndicatorStyle" + type="int" + transient="false" + volatile="false" + value="16843453" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="tabWidgetStyle" type="int" transient="false" @@ -8061,6 +8204,17 @@ visibility="public" > </field> +<field name="textAppearanceTab" + type="int" + transient="false" + volatile="false" + value="16843454" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="textCheckMark" type="int" transient="false" @@ -8424,6 +8578,17 @@ visibility="public" > </field> +<field name="timePickerInputStyle" + type="int" + transient="false" + volatile="false" + value="16843455" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="tint" type="int" transient="false" @@ -13253,6 +13418,17 @@ visibility="public" > </field> +<field name="Widget_EditText_TimePicker" + type="int" + transient="false" + volatile="false" + value="16973926" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="Widget_ExpandableListView" type="int" transient="false" @@ -13495,6 +13671,17 @@ visibility="public" > </field> +<field name="Widget_TabIndicator" + type="int" + transient="false" + volatile="false" + value="16973925" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="Widget_TabWidget" type="int" transient="false" @@ -39818,6 +40005,17 @@ visibility="public" > </field> +<field name="PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME" + type="java.lang.String" + transient="false" + volatile="false" + value=""isThemeable"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="className" type="java.lang.String" transient="false" @@ -40006,6 +40204,234 @@ </parameter> </method> </class> +<class name="BaseThemeInfo" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.os.Parcelable"> +</implements> +<constructor name="BaseThemeInfo" + type="android.content.pm.BaseThemeInfo" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +</constructor> +<constructor name="BaseThemeInfo" + type="android.content.pm.BaseThemeInfo" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="source" type="android.os.Parcel"> +</parameter> +</constructor> +<method name="changeDrmFlagIfNeeded" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="protected" +> +<parameter name="resourcePath" type="java.lang.String"> +</parameter> +</method> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="author" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="copyright" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="favesAppImageName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="favesImageName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="name" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="notificationRingtoneFileName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="notificationRingtoneName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="preview" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ringtoneFileName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ringtoneName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="soundPackName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="styleResourceId" + type="int" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="themeId" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="themeStyleName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="thumbnail" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="wallpaperImageName" + type="java.lang.String" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="ComponentInfo" extends="android.content.pm.PackageItemInfo" abstract="false" @@ -40748,6 +41174,16 @@ visibility="public" > </field> +<field name="isThemeApk" + type="boolean" + transient="false" + volatile="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="packageName" type="java.lang.String" transient="false" @@ -41412,6 +41848,17 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="getInstalledThemePackages" + return="java.util.List<android.content.pm.PackageInfo>" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getInstallerPackageName" return="java.lang.String" abstract="true" @@ -43347,6 +43794,76 @@ > </field> </class> +<class name="SoundsInfo" + extends="android.content.pm.BaseThemeInfo" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="SoundsInfo" + type="android.content.pm.SoundsInfo" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="parser" type="org.xmlpull.v1.XmlPullParser"> +</parameter> +<parameter name="res" type="android.content.res.Resources"> +</parameter> +<parameter name="attrs" type="android.util.AttributeSet"> +</parameter> +<exception name="XmlPullParserException" type="org.xmlpull.v1.XmlPullParserException"> +</exception> +</constructor> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="ThemeInfo" + extends="android.content.pm.BaseThemeInfo" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<constructor name="ThemeInfo" + type="android.content.pm.ThemeInfo" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="parser" type="org.xmlpull.v1.XmlPullParser"> +</parameter> +<parameter name="res" type="android.content.res.Resources"> +</parameter> +<parameter name="attrs" type="android.util.AttributeSet"> +</parameter> +<exception name="XmlPullParserException" type="org.xmlpull.v1.XmlPullParserException"> +</exception> +</constructor> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> </package> <package name="android.content.res" > @@ -132925,6 +133442,17 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="getInstalledThemePackages" + return="java.util.List<android.content.pm.PackageInfo>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getInstallerPackageName" return="java.lang.String" abstract="false" @@ -186340,6 +186868,20 @@ <parameter name="attrs" type="android.util.AttributeSet"> </parameter> </constructor> +<constructor name="LinearLayout" + type="android.widget.LinearLayout" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="attrs" type="android.util.AttributeSet"> +</parameter> +<parameter name="defStyle" type="int"> +</parameter> +</constructor> <method name="getBaselineAlignedChildIndex" return="int" abstract="false" diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index d709deb..969cdd9 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -21,6 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; +import android.content.res.Configuration; import android.graphics.Bitmap; import android.os.Debug; import android.os.RemoteException; @@ -923,4 +924,27 @@ public class ActivityManager { return null; } + /** + * @hide + */ + public Configuration getConfiguration() { + try { + return ActivityManagerNative.getDefault().getConfiguration(); + } catch (RemoteException e) { + return null; + } + } + + /** + * @throws SecurityException Throws SecurityException if the caller does + * not hold the {@link android.Manifest.permission#CHANGE_CONFIGURATION} permission. + * + * @hide + */ + public void updateConfiguration(Configuration values) throws SecurityException { + try { + ActivityManagerNative.getDefault().updateConfiguration(values); + } catch (RemoteException e) { + } + } } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b116bf8..0f5b5f5 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -16,14 +16,24 @@ package android.app; +import com.android.internal.os.BinderInternal; +import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SamplingProfilerIntegration; +import com.android.internal.util.ArrayUtils; + +import dalvik.system.SamplingProfiler; + +import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; + import android.content.BroadcastReceiver; import android.content.ComponentCallbacks; import android.content.ComponentName; import android.content.ContentProvider; import android.content.Context; +import android.content.ContextWrapper; import android.content.IContentProvider; -import android.content.Intent; import android.content.IIntentReceiver; +import android.content.Intent; import android.content.ServiceConnection; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -35,11 +45,13 @@ import android.content.pm.ServiceInfo; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.content.res.Resources; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; import android.graphics.Bitmap; import android.graphics.Canvas; +import android.net.Uri; import android.net.http.AndroidHttpClient; import android.os.Bundle; import android.os.Debug; @@ -53,12 +65,14 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.Config; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.view.Display; +import android.view.InflateException; import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; @@ -66,13 +80,6 @@ import android.view.Window; import android.view.WindowManager; import android.view.WindowManagerImpl; -import com.android.internal.os.BinderInternal; -import com.android.internal.os.RuntimeInit; -import com.android.internal.os.SamplingProfilerIntegration; -import com.android.internal.util.ArrayUtils; - -import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; - import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -88,8 +95,6 @@ import java.util.Map; import java.util.TimeZone; import java.util.regex.Pattern; -import dalvik.system.SamplingProfiler; - final class IntentReceiverLeaked extends AndroidRuntimeException { public IntentReceiverLeaked(String msg) { super(msg); @@ -176,8 +181,14 @@ public final class ActivityThread { * @param resDir the resource directory. * @param compInfo the compability info. It will use the default compatibility info when it's * null. + * + * @deprecated use {@link #getTopLevelResources(String, CompatibilityInfo, boolean)} instead. */ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { + return getTopLevelResources(resDir, compInfo, false); + } + + Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo, boolean isThemable) { synchronized (mPackages) { // Resources is app scale dependent. ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); @@ -201,9 +212,28 @@ public final class ActivityThread { //} AssetManager assets = new AssetManager(); + assets.setThemeSupport(isThemable); if (assets.addAssetPath(resDir) == 0) { return null; } + Configuration config = getConfiguration(); + if (isThemable && config != null) { + if (config.customTheme == null) { + config.customTheme = CustomTheme.getDefault(); + } + + if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { + PackageInfo pi = getPackageInfo(config.customTheme.getThemePackageName(), 0); + if (pi != null) { + String themeResDir = pi.getResDir(); + if (assets.addAssetPath(themeResDir) != 0) { + assets.setThemePackageName(config.customTheme.getThemePackageName()); + } else { + Log.e(TAG, "Unable to add theme resdir=" + themeResDir); + } + } + } + } //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics metrics = getDisplayMetricsLocked(false); @@ -221,11 +251,17 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. + * + * @deprecated {@link #getTopLevelResources(String, PackageInfo, boolean)} */ Resources getTopLevelResources(String resDir, PackageInfo pkgInfo) { return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo); } + Resources getTopLevelResources(String resDir, PackageInfo pkgInfo, boolean themeable) { + return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo, themeable); + } + final Handler getHandler() { return mH; } @@ -233,7 +269,7 @@ public final class ActivityThread { public final static class PackageInfo { private final ActivityThread mActivityThread; - private final ApplicationInfo mApplicationInfo; + /* package */ final ApplicationInfo mApplicationInfo; private final String mPackageName; private final String mAppDir; private final String mResDir; @@ -477,12 +513,18 @@ public final class ActivityThread { public AssetManager getAssets(ActivityThread mainThread) { return getResources(mainThread).getAssets(); } - - public Resources getResources(ActivityThread mainThread) { - if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, this); + + public Resources getResources(ActivityThread mainThread, boolean themeable, + boolean force) { + if (mResources == null || force == true) { + mResources = mainThread.getTopLevelResources(mResDir, this, themeable); } - return mResources; + return mResources; + } + + /** @deprecated use {@link #getResources(ActivityThread, boolean, boolean)} instead. */ + public Resources getResources(ActivityThread mainThread) { + return getResources(mainThread, false, false); } public Application makeApplication(boolean forceDefaultAppClass, @@ -2424,7 +2466,18 @@ public final class ActivityThread { activity.mStartedActivity = false; int theme = r.activityInfo.getThemeResource(); if (theme != 0) { - activity.setTheme(theme); + // Following is a workaround to have those activity managed dialogs to be themed when the theme flag is on. + if (r.activityInfo.isThemeable() && (theme == android.R.style.Theme_Dialog || + theme == com.android.internal.R.style.Theme_Dialog_Alert)) { + if (theme == android.R.style.Theme_Dialog) { + activity.setTheme(Dialog.resolveDefaultTheme(activity, 0, android.R.styleable.Theme_dialogTheme, + com.android.internal.R.style.Theme_Dialog)); + } else if (theme == com.android.internal.R.style.Theme_Dialog_Alert) { + activity.setTheme(AlertDialog.resolveDefaultTheme(activity, 0)); + } + } else { + activity.setTheme(theme); + } } activity.mCalled = false; @@ -2465,6 +2518,16 @@ public final class ActivityThread { } catch (Exception e) { if (!mInstrumentation.onException(activity, e)) { + if (e instanceof InflateException) { + Log.e(TAG, "Failed to inflate", e); + String pkg = null; + if (r.packageInfo != null && !TextUtils.isEmpty(r.packageInfo.getPackageName())) { + pkg = r.packageInfo.getPackageName(); + } + Intent intent = new Intent(Intent.ACTION_APP_LAUNCH_FAILURE, + (pkg != null)? Uri.fromParts("package", pkg, null) : null); + getSystemContext().sendBroadcast(intent); + } throw new RuntimeException( "Unable to start activity " + component + ": " + e.toString(), e); @@ -3707,6 +3770,19 @@ public final class ActivityThread { } } + private String getPackageResDir(String packageName) { + android.content.pm.PackageInfo pi; + try { + pi = getPackageManager().getPackageInfo(packageName, 0); + if (pi == null || pi.applicationInfo == null) + return null; + return pi.applicationInfo.publicSourceDir; + } catch (RemoteException e) { + Log.e("ActivityThread", "Exception in getPackageResDir", e); + } + return null; + } + final void handleConfigurationChanged(Configuration config) { synchronized (mRelaunchingActivities) { @@ -3722,11 +3798,13 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle configuration changed: " + config); + int diff; + synchronized(mPackages) { if (mConfiguration == null) { mConfiguration = new Configuration(); } - mConfiguration.updateFrom(config); + diff = mConfiguration.updateFrom(config); DisplayMetrics dm = getDisplayMetricsLocked(true); // set it for java, this also affects newly created Resources @@ -3747,7 +3825,34 @@ public final class ActivityThread { WeakReference<Resources> v = it.next(); Resources r = v.get(); if (r != null) { + boolean themeChanged = (diff & ActivityInfo.CONFIG_THEME_RESOURCE) != 0; + if (themeChanged) { + AssetManager am = r.getAssets(); + /* + * Dynamically modify the AssetManager object to + * replace the old asset path with the new one. This + * is made possibly by native layer changes made by + * T-Mobile. + */ + if (am.hasThemeSupport()) { + String oldThemePackage = am.getThemePackageName(); + if (!TextUtils.isEmpty(oldThemePackage)) { + am.setThemePackageName(null); + am.removeAssetPath(oldThemePackage, + getPackageResDir(oldThemePackage)); + } + String newThemePackage = config.customTheme.getThemePackageName(); + String resDir = getPackageResDir(newThemePackage); + if (resDir != null) { + am.setThemePackageName(newThemePackage); + am.updateResourcesWithAssetPath(resDir); + } + } + } r.updateConfiguration(config, dm); + if (themeChanged) { + r.updateStringCache(); + } //Log.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { @@ -3762,7 +3867,20 @@ public final class ActivityThread { final int N = callbacks.size(); for (int i=0; i<N; i++) { - performConfigurationChanged(callbacks.get(i), config); + ComponentCallbacks cb = callbacks.get(i); + + // We removed the old resources object from the mActiveResources + // cache, now we need to trigger an update for each application. + if ((diff & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) { + if (cb instanceof Activity || cb instanceof Application) { + Context context = ((ContextWrapper)cb).getBaseContext(); + if (context instanceof ApplicationContext) { + ((ApplicationContext)context).refreshResourcesIfNecessary(); + } + } + } + + performConfigurationChanged(cb, config); } } diff --git a/core/java/android/app/AlertDialog.java b/core/java/android/app/AlertDialog.java index 20a579a..3feee4a 100644 --- a/core/java/android/app/AlertDialog.java +++ b/core/java/android/app/AlertDialog.java @@ -56,19 +56,25 @@ public class AlertDialog extends Dialog implements DialogInterface { private AlertController mAlert; protected AlertDialog(Context context) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert); + this(context, 0); } protected AlertDialog(Context context, int theme) { - super(context, theme); - mAlert = new AlertController(context, this, getWindow()); + super(context, resolveDefaultTheme(context, theme)); + mAlert = new AlertController(getContext(), this, getWindow()); } protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) { - super(context, com.android.internal.R.style.Theme_Dialog_Alert); + super(context, resolveDefaultTheme(context, 0)); setCancelable(cancelable); setOnCancelListener(cancelListener); - mAlert = new AlertController(context, this, getWindow()); + mAlert = new AlertController(getContext(), this, getWindow()); + } + + static int resolveDefaultTheme(Context context, int theme) { + return Dialog.resolveDefaultTheme(context, theme, + android.R.styleable.Theme_alertDialogTheme, + com.android.internal.R.style.Theme_Dialog_Alert); } /** @@ -264,13 +270,15 @@ public class AlertDialog extends Dialog implements DialogInterface { } public static class Builder { + private final Context mContext; private final AlertController.AlertParams P; /** * Constructor using a context for this builder and the {@link AlertDialog} it creates. */ public Builder(Context context) { - P = new AlertController.AlertParams(context); + mContext = context; + P = new AlertController.AlertParams(); } /** @@ -279,7 +287,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * @return This Builder object to allow for chaining of calls to set methods */ public Builder setTitle(int titleId) { - P.mTitle = P.mContext.getText(titleId); + P.mTitle = mContext.getText(titleId); return this; } @@ -315,7 +323,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * @return This Builder object to allow for chaining of calls to set methods */ public Builder setMessage(int messageId) { - P.mMessage = P.mContext.getText(messageId); + P.mMessage = mContext.getText(messageId); return this; } @@ -357,7 +365,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * @return This Builder object to allow for chaining of calls to set methods */ public Builder setPositiveButton(int textId, final OnClickListener listener) { - P.mPositiveButtonText = P.mContext.getText(textId); + P.mPositiveButtonText = mContext.getText(textId); P.mPositiveButtonListener = listener; return this; } @@ -383,7 +391,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNegativeButton(int textId, final OnClickListener listener) { - P.mNegativeButtonText = P.mContext.getText(textId); + P.mNegativeButtonText = mContext.getText(textId); P.mNegativeButtonListener = listener; return this; } @@ -409,7 +417,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * @return This Builder object to allow for chaining of calls to set methods */ public Builder setNeutralButton(int textId, final OnClickListener listener) { - P.mNeutralButtonText = P.mContext.getText(textId); + P.mNeutralButtonText = mContext.getText(textId); P.mNeutralButtonListener = listener; return this; } @@ -465,7 +473,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * @return This Builder object to allow for chaining of calls to set methods */ public Builder setItems(int itemsId, final OnClickListener listener) { - P.mItems = P.mContext.getResources().getTextArray(itemsId); + P.mItems = mContext.getResources().getTextArray(itemsId); P.mOnClickListener = listener; return this; } @@ -538,7 +546,7 @@ public class AlertDialog extends Dialog implements DialogInterface { */ public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems, final OnMultiChoiceClickListener listener) { - P.mItems = P.mContext.getResources().getTextArray(itemsId); + P.mItems = mContext.getResources().getTextArray(itemsId); P.mOnCheckboxClickListener = listener; P.mCheckedItems = checkedItems; P.mIsMultiChoice = true; @@ -617,7 +625,7 @@ public class AlertDialog extends Dialog implements DialogInterface { */ public Builder setSingleChoiceItems(int itemsId, int checkedItem, final OnClickListener listener) { - P.mItems = P.mContext.getResources().getTextArray(itemsId); + P.mItems = mContext.getResources().getTextArray(itemsId); P.mOnClickListener = listener; P.mCheckedItem = checkedItem; P.mIsSingleChoice = true; @@ -783,7 +791,7 @@ public class AlertDialog extends Dialog implements DialogInterface { * to do and want this to be created and displayed. */ public AlertDialog create() { - final AlertDialog dialog = new AlertDialog(P.mContext); + final AlertDialog dialog = new AlertDialog(mContext); P.apply(dialog.mAlert); dialog.setCancelable(P.mCancelable); dialog.setOnCancelListener(P.mOnCancelListener); diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index f48f150..126ddef 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -20,6 +20,23 @@ import com.android.internal.policy.PolicyManager; import com.android.internal.util.XmlUtils; import com.google.android.collect.Maps; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.Set; +import java.util.HashSet; +import java.util.Map.Entry; + import org.xmlpull.v1.XmlPullParserException; import android.content.BroadcastReceiver; @@ -52,7 +69,10 @@ import android.content.pm.PermissionInfo; import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.ThemeInfo; import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.database.sqlite.SQLiteDatabase; @@ -80,9 +100,11 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.Vibrator; +import android.os.Process; import android.os.FileUtils.FileStatus; import android.telephony.TelephonyManager; import android.text.ClipboardManager; +import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.Log; import android.view.ContextThemeWrapper; @@ -110,6 +132,10 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.Map.Entry; +import com.android.internal.policy.PolicyManager; +import com.android.internal.util.XmlUtils; +import com.google.android.collect.Maps; + class ReceiverRestrictedContext extends ContextWrapper { ReceiverRestrictedContext(Context base) { super(base); @@ -152,6 +178,7 @@ class ApplicationContext extends Context { private static final Object sSync = new Object(); private static AlarmManager sAlarmManager; + private static PowerManager sPowerManager; private static ConnectivityManager sConnectivityManager; private static WifiManager sWifiManager; @@ -183,6 +210,7 @@ class ApplicationContext extends Context { private boolean mRestricted; private AccountManager mAccountManager; // protected by mSync + private final Object mSync = new Object(); private File mDatabasesDir; @@ -216,6 +244,20 @@ class ApplicationContext extends Context { return mResources; } + /** + * Refresh resources object which may have been changed by a theme + * configuration change. + */ + /* package */ void refreshResourcesIfNecessary() { + if (mResources == Resources.getSystem()) { + return; + } + + if (mPackageInfo.mApplicationInfo.isThemeable) { + mTheme = null; + } + } + @Override public PackageManager getPackageManager() { if (mPackageManager != null) { @@ -250,23 +292,62 @@ class ApplicationContext extends Context { public void setTheme(int resid) { mThemeResource = resid; } + + private int determineDefaultThemeResource() { + if (getResources() != Resources.getSystem() && mPackageInfo.mApplicationInfo.isThemeable) { + try { + Configuration config = ActivityManagerNative.getDefault().getConfiguration(); + if (config.customTheme != null) { + int themeId = CustomTheme.getStyleId(this, + config.customTheme.getThemePackageName(), + config.customTheme.getThemeId()); + if (themeId == -1) { + CustomTheme defaultTheme = CustomTheme.getDefault(); + if (config.customTheme.equals(defaultTheme)) { + return com.android.internal.R.style.Theme; + } else { + themeId = CustomTheme.getStyleId(this, + defaultTheme.getThemePackageName(), + defaultTheme.getThemeId()); + if (themeId == -1) { + return com.android.internal.R.style.Theme; + } else { + return themeId; + } + } + } else { + return themeId; + } + } + } catch (RemoteException e) { + Log.e(TAG, "Unable to access configuration, reverting to original system default theme", e); + } + } + + /* Fallback... */ + return com.android.internal.R.style.Theme; + } @Override public Resources.Theme getTheme() { if (mTheme == null) { + int themeId; if (mThemeResource == 0) { - mThemeResource = com.android.internal.R.style.Theme; + themeId = determineDefaultThemeResource(); + } else { + themeId = mThemeResource; } + mTheme = mResources.newTheme(); - mTheme.applyStyle(mThemeResource, true); + mTheme.applyStyle(themeId, true); } return mTheme; } @Override public ClassLoader getClassLoader() { - return mPackageInfo != null ? - mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader(); + return mPackageInfo != null ? mPackageInfo.getClassLoader() + : ClassLoader.getSystemClassLoader(); } @Override @@ -539,6 +620,40 @@ class ApplicationContext extends Context { getWallpaperManager().setStream(data); } + // If the default theme specifies wallpaper, returns the wallpaper Uri, otherwise returns null + private InputStream getDefaultThemeWallpaperStream() { + CustomTheme defaultTheme = CustomTheme.getDefault(); + String themeId = defaultTheme.getThemeId(); + String packageName = defaultTheme.getThemePackageName(); + if (!TextUtils.isEmpty(themeId) && + !TextUtils.isEmpty(packageName)) { + try { + PackageInfo pi = getPackageManager().getPackageInfo(packageName, 0); + ThemeInfo[] infos = pi.themeInfos; + String wallpaperPath = null; + if (infos != null) { + for (ThemeInfo ti : infos) { + if (ti.themeId.equals(themeId)) { + wallpaperPath = ti.wallpaperImageName; + break; + } + } + } + if (TextUtils.isEmpty(wallpaperPath)) { + return null; + } + // Unfortunately, we can't use ContentProvider and walpaper uri: + // due to timing issue, the uri of interest may still be not + // available by the time launcher needs to render the wallpaper. + Resources res = getPackageManager().getResourcesForApplication(packageName); + return res.getAssets().open(wallpaperPath); + } catch (Exception e) { + Log.e(TAG, "Can't get wallpaper for default theme in clearWallpaper", e); + } + } + return null; + } + @Override public void clearWallpaper() throws IOException { getWallpaperManager().clear(); @@ -932,7 +1047,7 @@ class ApplicationContext extends Context { } return sAlarmManager; } - + private PowerManager getPowerManager() { synchronized (sSync) { if (sPowerManager == null) { @@ -1363,7 +1478,8 @@ class ApplicationContext extends Context { IBinder activityToken, ActivityThread mainThread, Resources container) { mPackageInfo = packageInfo; - mResources = mPackageInfo.getResources(mainThread); + mResources = mPackageInfo.getResources(mainThread, + packageInfo.mApplicationInfo.isThemeable, false); if (container != null && container.getCompatibilityInfo().applicationScale != mResources.getCompatibilityInfo().applicationScale) { @@ -1372,7 +1488,8 @@ class ApplicationContext extends Context { " compatiblity info:" + container.getDisplayMetrics()); } mResources = mainThread.getTopLevelResources( - mPackageInfo.getResDir(), container.getCompatibilityInfo().copy()); + mPackageInfo.getResDir(), container.getCompatibilityInfo().copy(), + packageInfo.mApplicationInfo.isThemeable); } mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread); @@ -1763,6 +1880,16 @@ class ApplicationContext extends Context { } @Override + public List<PackageInfo> getInstalledThemePackages() { + try { + return mPM.getInstalledThemePackages(); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + + @Override public List<ApplicationInfo> getInstalledApplications(int flags) { try { return mPM.getInstalledApplications(flags); @@ -2026,7 +2153,8 @@ class ApplicationContext extends Context { } Resources r = mContext.mMainThread.getTopLevelResources( app.uid == Process.myUid() ? app.sourceDir - : app.publicSourceDir, mContext.mPackageInfo); + : app.publicSourceDir, mContext.mPackageInfo, + app.isThemeable); if (r != null) { return r; } diff --git a/core/java/android/app/DatePickerDialog.java b/core/java/android/app/DatePickerDialog.java index 78bbb4f..08e143b 100644 --- a/core/java/android/app/DatePickerDialog.java +++ b/core/java/android/app/DatePickerDialog.java @@ -80,7 +80,7 @@ public class DatePickerDialog extends AlertDialog implements OnClickListener, int year, int monthOfYear, int dayOfMonth) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert, + this(context, 0, callBack, year, monthOfYear, dayOfMonth); } diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 58e8b32..4ee74d3 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -22,6 +22,7 @@ import android.content.Context; import android.content.DialogInterface; import android.content.ComponentName; import android.content.ContextWrapper; +import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -134,8 +135,9 @@ public class Dialog implements DialogInterface, Window.Callback, * <var>context</var>. If 0, the default dialog theme will be used. */ public Dialog(Context context, int theme) { - mContext = new ContextThemeWrapper( - context, theme == 0 ? com.android.internal.R.style.Theme_Dialog : theme); + mContext = new ContextThemeWrapper(context, + resolveDefaultTheme(context, theme, android.R.styleable.Theme_dialogTheme, + com.android.internal.R.style.Theme_Dialog)); mWindowManager = (WindowManager)context.getSystemService("window"); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; @@ -147,6 +149,23 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * This method is provided to work around the constructor pattern limitation + * present in Dialog. We must resolve theme==0 to the runtime specified + * theme, but this cannot be done by subclasses except through this method. + */ + static int resolveDefaultTheme(Context context, int theme, int themeAttrIndex, + int staticDefault) { + if (theme != 0) { + return theme; + } else { + TypedArray a = context.obtainStyledAttributes(android.R.styleable.Theme); + int newTheme = a.getResourceId(themeAttrIndex, staticDefault); + a.recycle(); + return newTheme; + } + } + + /** * @deprecated * @hide */ diff --git a/core/java/android/app/ProgressDialog.java b/core/java/android/app/ProgressDialog.java index bdea069..03b2c05 100644 --- a/core/java/android/app/ProgressDialog.java +++ b/core/java/android/app/ProgressDialog.java @@ -73,13 +73,13 @@ public class ProgressDialog extends AlertDialog { private Handler mViewUpdateHandler; public ProgressDialog(Context context) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert); + this(context, 0); } public ProgressDialog(Context context, int theme) { - super(context, theme); + super(context, resolveDefaultTheme(context, theme)); } - + public static ProgressDialog show(Context context, CharSequence title, CharSequence message) { return show(context, title, message, false); diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index e5a769b..df277e5 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -159,7 +159,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @param context Application Context we can use for system acess */ public SearchDialog(Context context) { - super(context, com.android.internal.R.style.Theme_GlobalSearchBar); + super(context, resolveDefaultTheme(context, 0)); // Save voice intent for later queries/launching mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); @@ -171,6 +171,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } + static int resolveDefaultTheme(Context context, int theme) { + return Dialog.resolveDefaultTheme(context, theme, + android.R.styleable.Theme_searchDialogTheme, + com.android.internal.R.style.Theme_SearchBar); + } + /** * Create the search dialog and any resources that are used for the * entire lifetime of the dialog. diff --git a/core/java/android/app/TimePickerDialog.java b/core/java/android/app/TimePickerDialog.java index 002b01f..2fa2ea1 100644 --- a/core/java/android/app/TimePickerDialog.java +++ b/core/java/android/app/TimePickerDialog.java @@ -73,8 +73,7 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, public TimePickerDialog(Context context, OnTimeSetListener callBack, int hourOfDay, int minute, boolean is24HourView) { - this(context, com.android.internal.R.style.Theme_Dialog_Alert, - callBack, hourOfDay, minute, is24HourView); + this(context, 0, callBack, hourOfDay, minute, is24HourView); } /** @@ -95,7 +94,7 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, mInitialMinute = minute; mIs24HourView = is24HourView; - mDateFormat = DateFormat.getTimeFormat(context); + mDateFormat = DateFormat.getTimeFormat(getContext()); mCalendar = Calendar.getInstance(); updateTitle(mInitialHourOfDay, mInitialMinute); @@ -104,7 +103,7 @@ public class TimePickerDialog extends AlertDialog implements OnClickListener, setIcon(R.drawable.ic_dialog_time); LayoutInflater inflater = - (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.time_picker_dialog, null); setView(view); mTimePicker = (TimePicker) view.findViewById(R.id.timePicker); diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index c4b0807..f87aceb 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -342,6 +342,17 @@ public abstract class ContentResolver { // with sufficient testing. return new FileInputStream(uri.getPath()); } else { + // This is a work around for the following bug. + // If an asset is stored in a separate file, then AssetFileDescriptor.createInputStream + // works. However, if an asset is stored in .apk or .zip file, then + // AssetFileDescriptor.createInputStream returns a stream which does not + // contain valid data (I can repro the bug using SDK 1.1 and "regular" apk). + // To work around the bug, we try to get the input stream from AssetManager + // first and only if that failed, fall back to the "standard" approach. + InputStream stream = openInputStreamEx(uri); + if (stream != null) { + return stream; + } AssetFileDescriptor fd = openAssetFileDescriptor(uri, "r"); try { return fd != null ? fd.createInputStream() : null; @@ -351,6 +362,26 @@ public abstract class ContentResolver { } } + private InputStream openInputStreamEx(Uri uri) { + IContentProvider provider = acquireProvider(uri); + ContentProvider cp = null; + if (provider == null) { + return null; + } + if (provider instanceof ContentProvider.Transport) { + cp = ((ContentProvider.Transport)provider).getContentProvider(); + } else if (provider instanceof ContentProvider) { + cp = (ContentProvider)provider; + } + if (cp == null) { + return null; + } + if (cp instanceof IExtendedContentProvider) { + return ((IExtendedContentProvider)cp).openInputStream(uri); + } + return null; + } + /** * Synonym for {@link #openOutputStream(Uri, String) * openOutputStream(uri, "w")}. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 8f1c671..34d04a3 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -17,11 +17,20 @@ package android.content; import android.content.pm.ApplicationInfo; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; @@ -31,13 +40,6 @@ import android.os.Handler; import android.os.Looper; import android.util.AttributeSet; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; - /** * Interface to global information about an application environment. This is * an abstract class whose implementation is provided by @@ -200,7 +202,7 @@ public abstract class Context { int resid, int[] attrs) throws Resources.NotFoundException { return getTheme().obtainStyledAttributes(resid, attrs); } - + /** * Retrieve styled attribute information in this Context's theme. See * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)} @@ -1291,7 +1293,7 @@ public abstract class Context { * @see android.text.ClipboardManager */ public static final String CLIPBOARD_SERVICE = "clipboard"; - + /** * Use with {@link #getSystemService} to retrieve a * {@link android.view.inputmethod.InputMethodManager} for accessing input diff --git a/core/java/android/content/IExtendedContentProvider.java b/core/java/android/content/IExtendedContentProvider.java new file mode 100644 index 0000000..7cbb19a --- /dev/null +++ b/core/java/android/content/IExtendedContentProvider.java @@ -0,0 +1,22 @@ +package android.content; + +import java.io.InputStream; + +import android.net.Uri; + +/** + * The interface which "extends" IContentProvider. + * ContentProviders may implement the interface + * to work around the following bug. + * If an image asset is stored in a separate file, + * then AssetFileDescriptor.createInputStream returns stream with valid data. + * However, if an image asset is stored in .apk or .zip file, then + * AssetFileDescriptor.createInputStream returns a stream which does not + * contain valid data (the bug repro with SDK 1.1 and "regular" apk). + * To address this issue, ContentProviders may want to implement this interface. + * + * @hide + */ +public interface IExtendedContentProvider { + public InputStream openInputStream(Uri uri); +} diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index b785dbf..e47099e 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1756,6 +1756,26 @@ public class Intent implements Parcelable { public static final String ACTION_PRE_BOOT_COMPLETED = "android.intent.action.PRE_BOOT_COMPLETED"; + /** + * Broadcast Action: Indicate that unrecoverable error happened during app launch. + * Could indicate that curently applied theme is malicious. + * @hide + */ + public static final String ACTION_APP_LAUNCH_FAILURE = "com.tmobile.intent.action.APP_LAUNCH_FAILURE"; + + /** + * Broadcast Action: Request to reset the unrecoverable errors count to 0. + * @hide + */ + public static final String ACTION_APP_LAUNCH_FAILURE_RESET = "com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET"; + + /** + * Activity Action: Pick wallpaper from a list. + * Used instead of SET_WALLPAPER when we only need to pick wallpaper and do not set it. + * @hide + */ + public static final String ACTION_PICK_WALLPAPER = "com.tmobile.intent.action.PICK_WALLPAPER"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -1883,6 +1903,7 @@ public class Intent implements Parcelable { */ public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST = "android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST"; + /** * An activity to run when device is inserted into a car dock. * Used with {@link #ACTION_MAIN} to launch an activity. @@ -1898,6 +1919,14 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_DESK_DOCK = "android.intent.category.DESK_DOCK"; + /** + * Used to indicate that a theme package has been installed or un-installed. + * + * @hide + */ + public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE = + "com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard extra data keys. @@ -2112,6 +2141,31 @@ public class Intent implements Parcelable { public static final String EXTRA_CLIENT_INTENT = "android.intent.extra.client_intent"; + /** + * Used as a string extra field in {@link com.tmobile.intent.action.PICK_WALLPAPER} + * intents to tell the caller how to access bitmap resource. + * This can be either a Uri, a resource id or a relative path for raw resource. + * + * @hide + */ + public static final String EXTRA_WALLPAPER_IMAGE = "com.tmobile.intent.extra.WALLPAPER_IMAGE"; + + /** + * Used as a string extra field in {@link com.tmobile.intent.action.PICK_WALLPAPER} + * intents to tell the caller the bitmap UI name (to be shown to the user). + * + * @hide + */ + public static final String EXTRA_WALLPAPER_IMAGE_NAME = "com.tmobile.intent.extra.WALLPAPER_IMAGE_NAME"; + + /** + * Used as a bitmap extra field in {@link com.tmobile.intent.action.PICK_WALLPAPER} + * intents containing thumbnail image. + * + * @hide + */ + public static final String EXTRA_WALLPAPER_THUMBNAIL = "com.tmobile.intent.extra.EXTRA_WALLPAPER_THUMBNAIL"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Intent flags (see mFlags variable). diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 87da55f..0ffac89 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -243,6 +243,14 @@ public class ActivityInfo extends ComponentInfo * {@link android.R.attr#configChanges} attribute. */ public static final int CONFIG_ORIENTATION = 0x0080; + + /** + * @hide + */ + public static final int CONFIG_THEME_RESOURCE = 0x008000; + + + /** * Bit in {@link #configChanges} that indicates that the activity * can itself handle changes to the screen layout. Set from the @@ -279,7 +287,45 @@ public class ActivityInfo extends ComponentInfo * the mode from the theme will be used. */ public int softInputMode; - + + /** + * isThemeable flag is not explicitly set - use isThemeable value from ApllicationInfo. + */ + private static final int ISTHEMEABLE_INHERITED = 0; + + /** + * isThemeable flag is explicitly set to false. + */ + private static final int ISTHEMEABLE_FALSE = 1; + + /** + * isThemeable flag is explicitly set to true. + */ + private static final int ISTHEMEABLE_TRUE = 2; + + /** + * Is given activity theme agnostic, i.e. behaves properly when default theme is changed. + * {@hide} + */ + private int isThemeable = ISTHEMEABLE_INHERITED; + + /** + * {@hide} + */ + public boolean getIsThemeable() { + if (isThemeable == ISTHEMEABLE_INHERITED) { + return applicationInfo != null && applicationInfo.isThemeable; + } + return isThemeable != ISTHEMEABLE_FALSE; + } + + /** + * {@hide} + */ + public void setIsThemeable(boolean value) { + isThemeable = value? ISTHEMEABLE_TRUE : ISTHEMEABLE_FALSE; + } + public ActivityInfo() { } @@ -294,6 +340,7 @@ public class ActivityInfo extends ComponentInfo screenOrientation = orig.screenOrientation; configChanges = orig.configChanges; softInputMode = orig.softInputMode; + isThemeable = orig.isThemeable; } /** @@ -306,6 +353,22 @@ public class ActivityInfo extends ComponentInfo public final int getThemeResource() { return theme != 0 ? theme : applicationInfo.theme; } + + /** + * @hide + */ + public final boolean isThemeable() { + switch (isThemeable) { + case ISTHEMEABLE_TRUE: + return true; + + case ISTHEMEABLE_INHERITED: + return applicationInfo.isThemeable; + + default: + return false; + } + } public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); @@ -318,6 +381,7 @@ public class ActivityInfo extends ComponentInfo pw.println(prefix + "screenOrientation=" + screenOrientation + " configChanges=0x" + Integer.toHexString(configChanges) + " softInputMode=0x" + Integer.toHexString(softInputMode)); + pw.println(prefix + "isThemeable=" + isThemeable); super.dumpBack(pw, prefix); } @@ -342,6 +406,7 @@ public class ActivityInfo extends ComponentInfo dest.writeInt(screenOrientation); dest.writeInt(configChanges); dest.writeInt(softInputMode); + dest.writeInt(isThemeable); } public static final Parcelable.Creator<ActivityInfo> CREATOR @@ -365,5 +430,6 @@ public class ActivityInfo extends ComponentInfo screenOrientation = source.readInt(); configChanges = source.readInt(); softInputMode = source.readInt(); + isThemeable = source.readInt(); } } diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 7a65af8..dce3806 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -268,6 +268,24 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { */ public boolean enabled = true; + /** + * Is given application theme agnostic, i.e. behaves properly when default theme is changed. + * {@hide} + */ + public boolean isThemeable = false; + + private static final String PLUTO_SCHEMA = "http://www.w3.org/2001/pluto.html"; + + public static final String PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME = "isThemeable"; + + /** + * + * @hide + */ + public static boolean isPlutoNamespace(String namespace) { + return namespace != null && namespace.equalsIgnoreCase(PLUTO_SCHEMA); + } + public void dump(Printer pw, String prefix) { super.dumpFront(pw, prefix); pw.println(prefix + "className=" + className); @@ -331,6 +349,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { enabled = orig.enabled; manageSpaceActivityName = orig.manageSpaceActivityName; descriptionRes = orig.descriptionRes; + isThemeable = orig.isThemeable; } @@ -362,6 +381,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(manageSpaceActivityName); dest.writeString(backupAgentName); dest.writeInt(descriptionRes); + dest.writeInt(isThemeable? 1 : 0); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -392,6 +412,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { manageSpaceActivityName = source.readString(); backupAgentName = source.readString(); descriptionRes = source.readInt(); + isThemeable = source.readInt() != 0; } /** diff --git a/core/java/android/content/pm/BaseThemeInfo.java b/core/java/android/content/pm/BaseThemeInfo.java new file mode 100644 index 0000000..e389af7 --- /dev/null +++ b/core/java/android/content/pm/BaseThemeInfo.java @@ -0,0 +1,297 @@ +package android.content.pm; + +import android.os.Parcelable; +import android.os.Parcel; +import android.util.Log; +import android.util.AttributeSet; +import android.content.res.Resources; + +public class BaseThemeInfo implements Parcelable { + + /** + * The name of the wallpaper image file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see wallpaperImage attribute + */ + public String wallpaperImageName; + + /** + * The name of the favorites background image file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see favesBackground attribute + * + */ + public String favesImageName; + + /** + * The name of the favorite apps background image file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see favesAppBackground attribute + * + */ + public String favesAppImageName; + + /** + * The resource id of theme thumbnail. + * Specifies a theme thumbnail image resource as @drawable/foo. + * + * @see thumbnail attribute + * + */ + public String thumbnail; + + /** + * The theme id, which does not change when the theme is modified. + * Specifies an Android UI Style using style name. + * + * @see themeId attribute + * + */ + public String themeId; + + /** + * The style resource id of Android UI Style, supplied by the resource commpiler. + * Specifies an Android UI Style id. + * + * @see styleId attribute + * + */ + public int styleResourceId = -1; + + /** + * The name of the theme (as displayed by UI). + * + * @see name attribute + * + */ + public String name; + + /** + * The name of the call ringtone audio file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see ringtoneFileName attribute + * + */ + public String ringtoneFileName; + + /** + * The name of the call ringtone as shown to user. + * + * @see ringtoneName attribute + * + */ + public String ringtoneName; + + /** + * The name of the notification ringtone audio file. + * Specifies a relative path in assets subfolder. + * If the parent's name is "locked" - DRM protected. + * + * @see notificationRingtoneFileName attribute + * + */ + public String notificationRingtoneFileName; + + /** + * The name of the notification ringtone as shown to user. + * + * @see notificationRingtoneName attribute + * + */ + public String notificationRingtoneName; + + /** + * The author name of the theme package. + * + * @see author attribute + * + */ + public String author; + + /** + * The copyright text. + * + * @see copyright attribute + * + */ + public String copyright; + + /** + * {@hide} + */ + // There is no corresposponding flag in manifest file + // This flag is set to true iff any media resource is DRM protected + public boolean isDrmProtected = false; + + /** + * The name of the "main" theme style (as displayed by UI). + * + * @see themeStyleName attribute + * + */ + public String themeStyleName; + + /** + * The filename of the preview image. + * Specifies a theme preview image resource as a path into the assets + * subfolder. + * + * @see preview attribute + */ + public String preview; + + /** + * {@hide} + */ + public enum InfoObjectType { + TYPE_THEME, + TYPE_SOUNDPACK, + } + + /** + * {@hide} + */ + public InfoObjectType type; + + /** + * The name of a sound pack. + * + * @see soundpack attribute + * + */ + public String soundPackName; + + + private static final String LOCKED_NAME = "locked/"; + + /* + * Describe the kinds of special objects contained in this Parcelable's + * marshalled representation. + * + * @return a bitmask indicating the set of special object types marshalled + * by the Parcelable. + * + * @see android.os.Parcelable#describeContents() + */ + public int describeContents() { + return 0; + } + + /* + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + * + * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(objectTypeToInt(type)); + dest.writeString(wallpaperImageName); + dest.writeString(favesImageName); + dest.writeString(favesAppImageName); + dest.writeString(thumbnail); + dest.writeString(themeId); + dest.writeInt(styleResourceId); + dest.writeString(name); + dest.writeString(ringtoneFileName); + dest.writeString(notificationRingtoneFileName); + dest.writeString(ringtoneName); + dest.writeString(notificationRingtoneName); + dest.writeString(author); + dest.writeString(copyright); + dest.writeInt(isDrmProtected? 1 : 0); + dest.writeString(soundPackName); + dest.writeString(themeStyleName); + dest.writeString(preview); + } + + /** @hide */ + public static final Parcelable.Creator<BaseThemeInfo> CREATOR + = new Parcelable.Creator<BaseThemeInfo>() { + public BaseThemeInfo createFromParcel(Parcel source) { + return new BaseThemeInfo(source); + } + + public BaseThemeInfo[] newArray(int size) { + return new BaseThemeInfo[size]; + } + }; + + /** @hide */ + public final String getResolvedString(Resources res, AttributeSet attrs, int index) { + int resId = attrs.getAttributeResourceValue(index, 0); + if (resId !=0 ) { + return res.getString(resId); + } + return attrs.getAttributeValue(index); + } + + protected BaseThemeInfo() { + } + + protected BaseThemeInfo(Parcel source) { + type = intToInfoObjectType(source.readInt()); + wallpaperImageName = source.readString(); + favesImageName = source.readString(); + favesAppImageName = source.readString(); + thumbnail = source.readString(); + themeId = source.readString(); + styleResourceId = source.readInt(); + name = source.readString(); + ringtoneFileName = source.readString(); + notificationRingtoneFileName = source.readString(); + ringtoneName = source.readString(); + notificationRingtoneName = source.readString(); + author = source.readString(); + copyright = source.readString(); + isDrmProtected = (source.readInt() != 0); + soundPackName = source.readString(); + themeStyleName = source.readString(); + preview = source.readString(); + } + + protected void changeDrmFlagIfNeeded(String resourcePath) { + if (resourcePath != null && resourcePath.contains(LOCKED_NAME)) { + isDrmProtected = true; + } + } + + private int objectTypeToInt(InfoObjectType type) { + switch (type) { + case TYPE_THEME: + return 0; + + case TYPE_SOUNDPACK: + return 1; + + default: + Log.e("BaseThemeInfo", "unknown type " + type.toString()); + return 0; + } + } + + private InfoObjectType intToInfoObjectType(int value) { + switch (value) { + case 0: + return InfoObjectType.TYPE_THEME; + + case 1: + return InfoObjectType.TYPE_SOUNDPACK; + + default: + Log.e("BaseThemeInfo", "unknown value " + value); + return InfoObjectType.TYPE_THEME; + } + } + +} diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index fc6538f..1758c63 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -34,6 +34,7 @@ import android.content.pm.PermissionGroupInfo; import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.content.pm.ThemeInfo; import android.net.Uri; import android.content.IntentSender; @@ -105,6 +106,8 @@ interface IPackageManager { List<PackageInfo> getInstalledPackages(int flags); + List<PackageInfo> getInstalledThemePackages(); + List<ApplicationInfo> getInstalledApplications(int flags); /** diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index a8ce889..f783b96 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -132,9 +132,72 @@ public class PackageInfo implements Parcelable { */ public FeatureInfo[] reqFeatures; + // Is Theme Apk + public boolean isThemeApk = false; + + // ThemeInfo + /** + * {@hide} + */ + public ThemeInfo [] themeInfos; + + // SoundsInfo + /** + * {@hide} + */ + public SoundsInfo [] soundInfos; + public PackageInfo() { } + /* + * Is Theme Apk is DRM protected (contains DRM-protected resources) + * + */ + private boolean drmProtectedThemeApk = false; + + /** + * @hide + * + * @return Is Theme Apk is DRM protected (contains DRM-protected resources) + */ + public boolean isDrmProtectedThemeApk() { + return drmProtectedThemeApk; + } + + /** + * @hide + * + * @param value if Theme Apk is DRM protected (contains DRM-protected resources) + */ + public void setDrmProtectedThemeApk(boolean value) { + drmProtectedThemeApk = value; + } + + /* + * If isThemeApk and isDrmProtectedThemeApk are true - path to hidden locked zip file + * + */ + private String lockedZipFilePath; + + /** + * @hide + * + * @return path for hidden locked zip file + */ + public String getLockedZipFilePath() { + return lockedZipFilePath; + } + + /** + * @hide + * + * @param value path for hidden locked zip file + */ + public void setLockedZipFilePath(String value) { + lockedZipFilePath = value; + } + public String toString() { return "PackageInfo{" + Integer.toHexString(System.identityHashCode(this)) @@ -168,6 +231,13 @@ public class PackageInfo implements Parcelable { dest.writeTypedArray(signatures, parcelableFlags); dest.writeTypedArray(configPreferences, parcelableFlags); dest.writeTypedArray(reqFeatures, parcelableFlags); + + /* Theme-specific. */ + dest.writeInt((isThemeApk)? 1 : 0); + dest.writeInt((drmProtectedThemeApk)? 1 : 0); + dest.writeTypedArray(themeInfos, parcelableFlags); + dest.writeTypedArray(soundInfos, parcelableFlags); + dest.writeString(lockedZipFilePath); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -202,5 +272,12 @@ public class PackageInfo implements Parcelable { signatures = source.createTypedArray(Signature.CREATOR); configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR); reqFeatures = source.createTypedArray(FeatureInfo.CREATOR); + + /* Theme-specific. */ + isThemeApk = (source.readInt() != 0); + drmProtectedThemeApk = (source.readInt() != 0); + themeInfos = source.createTypedArray(ThemeInfo.CREATOR); + soundInfos = source.createTypedArray(SoundsInfo.CREATOR); + lockedZipFilePath = source.readString(); } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index cd48dcb..c69646e 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -793,6 +793,16 @@ public abstract class PackageManager { public abstract List<PackageInfo> getInstalledPackages(int flags); /** + * Return a List of all theme packages that are installed + * on the device. + * + * @return A List of PackageInfo objects, one for each theme package + * that is installed on the device. + * + */ + public abstract List<PackageInfo> getInstalledThemePackages(); + + /** * Check whether a particular package has been granted a particular * permission. * diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index b798bde..45daea9 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -158,6 +158,17 @@ public class PackageParser { return name.endsWith(".apk"); } + public static String getLockedZipFilePath(String path) { + if (path == null) { + return null; + } + if (isPackageFilename(path)) { + return path.substring(0, path.length() - 4) + ".locked.zip"; + } else { + return path + ".locked.zip"; + } + } + /** * Generate and return the {@link PackageInfo} for a parsed package. * @@ -173,6 +184,32 @@ public class PackageParser { pi.versionName = p.mVersionName; pi.sharedUserId = p.mSharedUserId; pi.sharedUserLabel = p.mSharedUserLabel; + pi.isThemeApk = p.mIsThemeApk; + pi.setDrmProtectedThemeApk(false); + if (pi.isThemeApk) { + int N = p.mThemeInfos.size(); + if (N > 0) { + pi.themeInfos = new ThemeInfo[N]; + for (int i = 0; i < N; i++) { + pi.themeInfos[i] = p.mThemeInfos.get(i); + pi.setDrmProtectedThemeApk(pi.isDrmProtectedThemeApk() || pi.themeInfos[i].isDrmProtected); + } + if (pi.isDrmProtectedThemeApk()) { + pi.setLockedZipFilePath(PackageParser.getLockedZipFilePath(p.mPath)); + } + } + N = p.mSoundInfos.size(); + if (N > 0) { + pi.soundInfos = new SoundsInfo[N]; + for (int i = 0; i < N; i++) { + pi.soundInfos[i] = p.mSoundInfos.get(i); + pi.setDrmProtectedThemeApk(pi.isDrmProtectedThemeApk() || pi.soundInfos[i].isDrmProtected); + } + if (pi.isDrmProtectedThemeApk()) { + pi.setLockedZipFilePath(PackageParser.getLockedZipFilePath(p.mPath)); + } + } + } pi.applicationInfo = p.applicationInfo; if ((flags&PackageManager.GET_GIDS) != 0) { pi.gids = gids; @@ -960,7 +997,14 @@ public class PackageParser { // Just skip this tag XmlUtils.skipCurrentTag(parser); continue; - + } else if (tagName.equals("theme")) { + // this is a theme apk. + pkg.mIsThemeApk = true; + pkg.mThemeInfos.add(new ThemeInfo(parser, res, attrs)); + } else if (tagName.equals("sounds")) { + // this is a theme apk. + pkg.mIsThemeApk = true; + pkg.mSoundInfos.add(new SoundsInfo(parser, res, attrs)); } else if (RIGID_PARSER) { outError[0] = "Bad element under <manifest>: " + parser.getName(); @@ -1303,12 +1347,26 @@ public class PackageParser { return a; } + private void setIsThemeable(XmlPullParser parser, AttributeSet attrs, ApplicationInfo ai) { + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + if (attrs.getAttributeName(i).equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) { + ai.isThemeable = attrs.getAttributeBooleanValue(i, false); + return; + } + } + } + private boolean parseApplication(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError) throws XmlPullParserException, IOException { final ApplicationInfo ai = owner.applicationInfo; final String pkgName = owner.applicationInfo.packageName; + setIsThemeable(parser, attrs, ai); + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestApplication); @@ -1581,6 +1639,18 @@ public class PackageParser { return true; } + private void setIsThemeableForActivity(XmlPullParser parser, AttributeSet attrs, ActivityInfo ai) { + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + if (attrs.getAttributeName(i).equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) { + ai.setIsThemeable(attrs.getAttributeBooleanValue(i, false)); + return; + } + } + } + private Activity parseActivity(Package owner, Resources res, XmlPullParser parser, AttributeSet attrs, int flags, String[] outError, boolean receiver) throws XmlPullParserException, IOException { @@ -1602,6 +1672,8 @@ public class PackageParser { mParseActivityArgs.flags = flags; Activity a = new Activity(mParseActivityArgs, new ActivityInfo()); + setIsThemeableForActivity(parser, attrs, a.info); + if (outError[0] != null) { sa.recycle(); return null; @@ -2546,6 +2618,15 @@ public class PackageParser { // For use by package manager to keep track of where it has done dexopt. public boolean mDidDexOpt; + // Is Theme Apk + public boolean mIsThemeApk = false; + + // Theme info + public final ArrayList<ThemeInfo> mThemeInfos = new ArrayList<ThemeInfo>(0); + + // Sound info + public final ArrayList<SoundsInfo> mSoundInfos = new ArrayList<SoundsInfo>(0); + // Additional data supplied by callers. public Object mExtras; diff --git a/core/java/android/content/pm/SoundsInfo.java b/core/java/android/content/pm/SoundsInfo.java new file mode 100644 index 0000000..c42c5e7 --- /dev/null +++ b/core/java/android/content/pm/SoundsInfo.java @@ -0,0 +1,170 @@ +package android.content.pm; + +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParser; + +import android.os.Parcelable; +import android.os.Parcel; +import android.util.AttributeSet; +import android.content.res.Resources; + +import java.util.Map; +import java.util.HashMap; + +/** + * Overall information about sound pack. This corresponds + * to the information collected from AndroidManifest.xml (sounds tag). + * + * Below is an example of sounds tag + * <sounds + * pluto:name="Pluto Default" + * pluto:thumbnail="@drawable/app_thumbnail" + * pluto:author="John Doe" + * pluto:ringtoneFileName="media/audio/ringtone.mp3" + * pluto:notificationRingtoneFileName="media/audio/locked/notification.mp3" + * pluto:copyright="T-Mobile, 2009" + * /> + * + */ +public class SoundsInfo extends BaseThemeInfo { + + private static final String [] attributes = new String [] { + "name", + "thumbnail", + "author", + "ringtoneFileName", + "notificationRingtoneFileName", + "ringtoneName", + "notificationRingtoneName", + "copyright", + }; + + private static Map<String, Integer> attributesLookupTable; + + static { + attributesLookupTable = new HashMap<String, Integer>(); + for (int i = 0; i < attributes.length; i++) { + attributesLookupTable.put(attributes[i], i); + } + } + + /** + * {@link #name} + * + */ + private static final int NAME_INDEX = 0; + + /** + * {@link #thumbnail} + * + */ + private static final int THUMBNAIL_INDEX = 1; + + /** + * {@link #author} + * + */ + private static final int AUTHOR_INDEX = 2; + + /** + * {@link #ringtoneName} + * + */ + private static final int RINGTONE_FILE_NAME_INDEX = 3; + + /** + * {@link #notificationRingtoneName} + * + */ + private static final int NOTIFICATION_RINGTONE_FILE_NAME_INDEX = 4; + + /** + * {@link #ringtoneName} + * + */ + private static final int RINGTONE_NAME_INDEX = 5; + + /** + * {@link #notificationRingtoneName} + * + */ + private static final int NOTIFICATION_RINGTONE_NAME_INDEX = 6; + + /** + * {@link #copyright} + * + */ + private static final int COPYRIGHT_INDEX = 7; + + + public SoundsInfo(XmlPullParser parser, Resources res, AttributeSet attrs) throws XmlPullParserException { + super(); + + type = InfoObjectType.TYPE_SOUNDPACK; + Map<String, Integer> tempMap = new HashMap<String, Integer>(attributesLookupTable); + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String key = attrs.getAttributeName(i); + if (tempMap.containsKey(key)) { + int index = tempMap.get(key); + tempMap.remove(key); + + switch (index) { + case NAME_INDEX: + name = getResolvedString(res, attrs, i); + break; + + case THUMBNAIL_INDEX: + thumbnail = attrs.getAttributeValue(i); + break; + + case AUTHOR_INDEX: + author = getResolvedString(res, attrs, i); + break; + + case RINGTONE_FILE_NAME_INDEX: + ringtoneFileName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(ringtoneFileName); + break; + + case NOTIFICATION_RINGTONE_FILE_NAME_INDEX: + notificationRingtoneFileName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(notificationRingtoneFileName); + break; + + case RINGTONE_NAME_INDEX: + ringtoneName = attrs.getAttributeValue(i); + break; + + case NOTIFICATION_RINGTONE_NAME_INDEX: + notificationRingtoneName = attrs.getAttributeValue(i); + + case COPYRIGHT_INDEX: + copyright = getResolvedString(res, attrs, i); + break; + } + } + } + if (!tempMap.isEmpty()) { + throw new XmlPullParserException("Not all compulsory attributes are specified in <sounds>"); + } + } + + public static final Parcelable.Creator<SoundsInfo> CREATOR + = new Parcelable.Creator<SoundsInfo>() { + public SoundsInfo createFromParcel(Parcel source) { + return new SoundsInfo(source); + } + + public SoundsInfo[] newArray(int size) { + return new SoundsInfo[size]; + } + }; + + private SoundsInfo(Parcel source) { + super(source); + } + +} diff --git a/core/java/android/content/pm/ThemeInfo.aidl b/core/java/android/content/pm/ThemeInfo.aidl new file mode 100755 index 0000000..acbc85e --- /dev/null +++ b/core/java/android/content/pm/ThemeInfo.aidl @@ -0,0 +1,3 @@ +package android.content.pm; + +parcelable ThemeInfo; diff --git a/core/java/android/content/pm/ThemeInfo.java b/core/java/android/content/pm/ThemeInfo.java new file mode 100644 index 0000000..681fda3 --- /dev/null +++ b/core/java/android/content/pm/ThemeInfo.java @@ -0,0 +1,306 @@ +/** + * + */ +package android.content.pm; + +import java.util.HashMap; +import java.util.Map; + +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlPullParser; + +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.content.res.Resources; + +/** + * Overall information about "theme" package. This corresponds + * to the information collected from AndroidManifest.xml (theme tag). + * + * Below is an example of theme tag + * <theme + * pluto:name="Pluto Default" + * pluto:thumbnail="media/images/app_thumbnail.png" + * pluto:preview="media/images/preview.png" + * pluto:author="John Doe" + * pluto:ringtoneFileName="media/audio/ringtone.mp3" + * pluto:notificationRingtoneFileName="media/audio/locked/notification.mp3" + * pluto:copyright="T-Mobile, 2009" + * pluto:wallpaperImage="media/images/wallpaper.jpg" + * pluto:favesBackground="media/images/locked/background.jpg" + * pluto:soundpackName="<package_name>/<sound_pack_name>" + * /> + * + */ +public final class ThemeInfo extends BaseThemeInfo { + + /** + * {@link #name} + * + */ + private static final int THEME_PACKAGE_INDEX = 0; + + /** + * {@link #thumbnail} + * + */ + private static final int THUMBNAIL_INDEX = 1; + + /** + * {@link #preview} + * + */ + private static final int PREVIEW_INDEX = 2; + + /** + * {@link #author} + * + */ + private static final int AUTHOR_INDEX = 3; + + /** + * {@link #themeId} + * + */ + private static final int THEME_INDEX = 4; + + /** + * {@link #themeStyleName} + * + */ + private static final int THEME_STYLE_NAME_INDEX = 5; + + /** + * {@link #ringtoneName} + * + */ + private static final int RINGTONE_FILE_NAME_INDEX = 6; + + /** + * {@link #notificationRingtoneName} + * + */ + private static final int NOTIFICATION_RINGTONE_FILE_NAME_INDEX = 7; + + /** + * {@link #favesImageName} + * + */ + private static final int FAVES_IMAGE_NAME_INDEX = 8; + + /** + * {@link #favesAppImageName} + * + */ + private static final int FAVES_APP_IMAGE_NAME_INDEX = 9; + + /** + * {@link #wallpaperImageName} + * + */ + private static final int WALLPAPER_IMAGE_NAME_INDEX = 10; + + /** + * {@link #copyright} + * + */ + private static final int COPYRIGHT_INDEX = 11; + + /** + * {@link #ringtoneName} + * + */ + private static final int RINGTONE_NAME_INDEX = 12; + + /** + * {@link #notificationRingtoneName} + * + */ + private static final int NOTIFICATION_RINGTONE_NAME_INDEX = 13; + + /** + * {@link #soundPackName} + * + */ + private static final int SOUNDPACK_NAME_INDEX = 14; + + /** + * {@link #styleResourceId} + * + */ + private static final int STYLE_INDEX = 15; + + + private static final String [] compulsoryAttributes = new String [] { + "name", + "thumbnail", + "preview", + "author", + "themeId", + "styleName", + }; + + private static final String [] optionalAttributes = new String [] { + "ringtoneFileName", + "notificationRingtoneFileName", + "favesBackground", + "favesAppsBackground", + "wallpaperImage", + "copyright", + "ringtoneName", + "notificationRingtoneName", + "soundpackName", + "styleId", + }; + + private static Map<String, Integer> attributesLookupTable; + + static { + attributesLookupTable = new HashMap<String, Integer>(); + for (int i = 0; i < compulsoryAttributes.length; i++) { + attributesLookupTable.put(compulsoryAttributes[i], i); + } + + for (int i = 0; i < optionalAttributes.length; i++) { + attributesLookupTable.put(optionalAttributes[i], compulsoryAttributes.length + i); + } + } + + public ThemeInfo(XmlPullParser parser, Resources res, AttributeSet attrs) throws XmlPullParserException { + super(); + + type = InfoObjectType.TYPE_THEME; + Map<String, Integer> tempMap = new HashMap<String, Integer>(attributesLookupTable); + int numberOfCompulsoryAttributes = 0; + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String key = attrs.getAttributeName(i); + if (tempMap.containsKey(key)) { + int index = tempMap.get(key); + tempMap.remove(key); + + if (index < compulsoryAttributes.length) { + numberOfCompulsoryAttributes++; + } + switch (index) { + case THEME_PACKAGE_INDEX: + // theme name + name = getResolvedString(res, attrs, i); + break; + + case THUMBNAIL_INDEX: + // theme thumbprint + thumbnail = attrs.getAttributeValue(i); + break; + + case AUTHOR_INDEX: + // theme author + author = getResolvedString(res, attrs, i); + break; + + case THEME_INDEX: + // androidUiStyle attribute + themeId = attrs.getAttributeValue(i); + break; + + case THEME_STYLE_NAME_INDEX: + themeStyleName = getResolvedString(res, attrs, i); + break; + + case RINGTONE_FILE_NAME_INDEX: + // ringtone + ringtoneFileName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(ringtoneFileName); + break; + + case NOTIFICATION_RINGTONE_FILE_NAME_INDEX: + // notification ringtone + notificationRingtoneFileName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(notificationRingtoneFileName); + break; + + case FAVES_IMAGE_NAME_INDEX: + // faves background + favesImageName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(favesImageName); + break; + + case FAVES_APP_IMAGE_NAME_INDEX: + // favesAppBackground attribute + favesAppImageName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(favesAppImageName); + break; + + case WALLPAPER_IMAGE_NAME_INDEX: + // wallpaperImage attribute + wallpaperImageName = attrs.getAttributeValue(i); + changeDrmFlagIfNeeded(wallpaperImageName); + break; + + case COPYRIGHT_INDEX: + // themeCopyright attribute + copyright = getResolvedString(res, attrs, i); + break; + + case RINGTONE_NAME_INDEX: + // ringtone UI name + ringtoneName = attrs.getAttributeValue(i); + break; + + case NOTIFICATION_RINGTONE_NAME_INDEX: + // notification ringtone UI name + notificationRingtoneName = attrs.getAttributeValue(i); + break; + + case SOUNDPACK_NAME_INDEX: + soundPackName = attrs.getAttributeValue(i); + break; + + case STYLE_INDEX: + styleResourceId = attrs.getAttributeResourceValue(i, -1); + break; + + case PREVIEW_INDEX: + // theme thumbprint + preview = attrs.getAttributeValue(i); + break; + } + } + } + if (numberOfCompulsoryAttributes < compulsoryAttributes.length) { + throw new XmlPullParserException("Not all compulsory attributes are specified in <theme>"); + } + } + + /* + * Flatten this object in to a Parcel. + * + * @param dest The Parcel in which the object should be written. + * @param flags Additional flags about how the object should be written. + * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}. + * + * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int) + */ + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + } + + public static final Parcelable.Creator<ThemeInfo> CREATOR + = new Parcelable.Creator<ThemeInfo>() { + public ThemeInfo createFromParcel(Parcel source) { + return new ThemeInfo(source); + } + + public ThemeInfo[] newArray(int size) { + return new ThemeInfo[size]; + } + }; + + private ThemeInfo(Parcel source) { + super(source); + } + +} diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 0d43b2a..a4c8ac5 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -74,6 +74,9 @@ public final class AssetManager { private String mAssetDir; private String mAppName; + private boolean mThemeSupport; + private String mThemePackageName; + /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the @@ -237,6 +240,12 @@ public final class AssetManager { } } + /*package*/ final void recreateStringBlocks() { + synchronized (mSync) { + makeStringBlocks(true); + } + } + private final void makeStringBlocks(boolean copyFromSystem) { final int sysNum = copyFromSystem ? mSystem.mStringBlocks.length : 0; final int num = getStringBlockCount(); @@ -443,6 +452,18 @@ public final class AssetManager { /** * {@hide} + * Split a theme package with DRM-protected resources into two files. + * + * @param packageFileName Original theme package file name. + * @param lockedFileName Name of the new "locked" file with DRM resources. + * @param drmProtectedresources Array of names of DRM-protected assets. + */ + public final int splitDrmProtectedThemePackage(String packageFileName, String lockedFileName, String [] drmProtectedresources) { + return splitThemePackage(packageFileName, lockedFileName, drmProtectedresources); + } + + /** + * {@hide} * Retrieve a non-asset as a compiled XML file. Not for use by * applications. * @@ -573,6 +594,68 @@ public final class AssetManager { public native final int addAssetPath(String path); /** + * Delete a set of assets from the asset manager. This can be + * either a directory or ZIP file. Not for use by applications. Returns + * true if succeeded or false on failure. + * {@hide} + */ + public native final boolean removeAssetPath(String packageName, String path); + + /** + * Add an additional set of assets to the asset manager. This can be + * either a directory or ZIP file. Force updating of ResTable object. + * Not for use by applications. + * Returnsthe cookie of the added asset, or 0 on failure. + * {@hide} + */ + public native final int updateResourcesWithAssetPath(String path); + + /** + * Delete a set of assets from the asset manager. This can be + * either a directory or ZIP file. Not for use by applications. Returns + * true if succeeded or false on failure. + * {@hide} + */ + public native final void dumpResources(); + + /** + * Sets a flag indicating that this AssetManager should have themes + * attached, according to the initial request to create it by the + * ApplicationContext. + * + * {@hide} + */ + public final void setThemeSupport(boolean themeSupport) { + mThemeSupport = themeSupport; + } + + /** + * Should this AssetManager have themes attached, according to the initial + * request to create it by the ApplicationContext? + * + * {@hide} + */ + public final boolean hasThemeSupport() { + return mThemeSupport; + } + + /** + * Get package name of current theme (may return null). + * {@hide} + */ + public final String getThemePackageName() { + return mThemePackageName; + } + + /** + * Sets package name for current theme (null is allowed). + * {@hide} + */ + public final void setThemePackageName(String packageName) { + mThemePackageName = packageName; + } + + /** * Determine whether the state in this asset manager is up-to-date with * the files on the filesystem. If false is returned, you need to * instantiate a new AssetManager class to see the new data. @@ -676,6 +759,7 @@ public final class AssetManager { private native final int newTheme(); private native final void deleteTheme(int theme); /*package*/ native static final void applyThemeStyle(int theme, int styleRes, boolean force); + /*package*/ native static final void setAttributeValue(int theme, int attr, int color); /*package*/ native static final void copyTheme(int dest, int source); /*package*/ native static final int loadThemeAttributeValue(int theme, int ident, TypedValue outValue, @@ -688,6 +772,8 @@ public final class AssetManager { private native final int[] getArrayStringInfo(int arrayRes); /*package*/ native final int[] getArrayIntResource(int arrayRes); + private native final int splitThemePackage(String srcFileName, String dstFileName, String [] drmProtectedAssetNames); + private native final void init(); private native final void destroy(); diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 1fe34b5..d8f44af 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -3,6 +3,8 @@ package android.content.res; import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemProperties; +import android.text.TextUtils; import java.util.Locale; @@ -35,6 +37,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration public Locale locale; /** + * @hide + */ + public CustomTheme customTheme; + + /** * Locale should persist on setting. This is hidden because it is really * questionable whether this is the right way to expose the functionality. * @hide @@ -154,7 +161,22 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int ORIENTATION_PORTRAIT = 1; public static final int ORIENTATION_LANDSCAPE = 2; public static final int ORIENTATION_SQUARE = 3; - + + /** + * @hide + */ + public static final int THEME_UNDEFINED = 0; + + /** + * @hide + */ + public static final String THEME_ID_PERSISTENCE_PROPERTY = "persist.sys.themeId"; + + /** + * @hide + */ + public static final String THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY = "persist.sys.themePackageName"; + /** * Overall orientation of the screen. May be one of * {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT}, @@ -189,6 +211,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigationHidden = o.navigationHidden; orientation = o.orientation; screenLayout = o.screenLayout; + if (o.customTheme != null) { + customTheme = (CustomTheme) o.customTheme.clone(); + } else { + customTheme = CustomTheme.getDefault(); + } } public String toString() { @@ -217,6 +244,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration sb.append(orientation); sb.append(" layout="); sb.append(screenLayout); + sb.append(" themeResource="); + sb.append(customTheme); sb.append('}'); return sb.toString(); } @@ -237,6 +266,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigationHidden = NAVIGATIONHIDDEN_UNDEFINED; orientation = ORIENTATION_UNDEFINED; screenLayout = SCREENLAYOUT_SIZE_UNDEFINED; + + String themeResource = SystemProperties.get(THEME_ID_PERSISTENCE_PROPERTY, null); + String themePackageName = SystemProperties.get(THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY, ""); + if (!TextUtils.isEmpty(themeResource) && !TextUtils.isEmpty(themePackageName)) { + customTheme = new CustomTheme(themeResource, themePackageName); + } else { + customTheme = CustomTheme.getDefault(); + } } /** {@hide} */ @@ -317,7 +354,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; screenLayout = delta.screenLayout; } - + if (delta.customTheme != null + && (customTheme == null || !customTheme.equals(delta.customTheme))) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + customTheme = (CustomTheme) delta.customTheme.clone(); + } else if (delta.customTheme == null && customTheme != null) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + customTheme = CustomTheme.getDefault(); + } return changed; } @@ -393,6 +437,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration && screenLayout != delta.screenLayout) { changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; } + if (delta.customTheme != null + && (customTheme == null || !customTheme.equals(delta.customTheme))) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + } else if (delta.customTheme == null && customTheme != null) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + } return changed; } @@ -444,6 +494,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(navigationHidden); dest.writeInt(orientation); dest.writeInt(screenLayout); + + if (customTheme == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeString(customTheme.getThemeId()); + dest.writeString(customTheme.getThemePackageName()); + } } public static final Parcelable.Creator<Configuration> CREATOR @@ -477,6 +535,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration navigationHidden = source.readInt(); orientation = source.readInt(); screenLayout = source.readInt(); + + if (source.readInt() != 0) { + String themeId = source.readString(); + String themePackage = source.readString(); + customTheme = new CustomTheme(themeId, themePackage); + } } public int compareTo(Configuration that) { @@ -511,6 +575,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (n != 0) return n; n = this.screenLayout - that.screenLayout; //if (n != 0) return n; + n = this.customTheme.getThemeId().compareTo(that.customTheme.getThemeId()); + if (n != 0) return n; + n = this.customTheme.getThemePackageName().compareTo(that.customTheme.getThemePackageName()); + return n; } @@ -533,6 +601,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration + this.locale.hashCode() + this.touchscreen + this.keyboard + this.keyboardHidden + this.hardKeyboardHidden + this.navigation + this.navigationHidden - + this.orientation + this.screenLayout; + + this.orientation + this.screenLayout + + this.customTheme.hashCode(); } } diff --git a/core/java/android/content/res/CustomTheme.java b/core/java/android/content/res/CustomTheme.java new file mode 100644 index 0000000..6adc57b --- /dev/null +++ b/core/java/android/content/res/CustomTheme.java @@ -0,0 +1,114 @@ +package android.content.res; + +import android.os.*; +import android.content.Context; +import android.content.pm.*; +import android.util.Log; +import android.util.DisplayMetrics; +import android.R; +import android.app.ActivityManager; +import android.view.WindowManager; +import android.text.TextUtils; + +/** + * @hide + */ +public final class CustomTheme implements Cloneable { + + private String mThemeId; + private String mThemePackageName; + + private static final CustomTheme sDefaultTheme = new CustomTheme(); + + private CustomTheme() { + mThemeId = SystemProperties.get("default_theme.style_id"); + mThemePackageName = SystemProperties.get("default_theme.package_name"); + } + + public CustomTheme(String themeId, String packageName) { + mThemeId = themeId; + mThemePackageName = packageName; + } + + @Override + public Object clone() { + try { + return super.clone(); + } catch (CloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object object) { + if (object == this) { + return true; + } + if (object instanceof CustomTheme) { + CustomTheme o = (CustomTheme) object; + if (!mThemeId.equals(o.mThemeId)) { + return false; + } + String currentPackageName = (mThemePackageName == null)? "" : mThemePackageName; + String newPackageName = (o.mThemePackageName == null)? "" : o.mThemePackageName; + String currentThemeId = (mThemeId == null)? "" : mThemeId; + String newThemeId = (o.mThemeId == null)? "" : o.mThemeId; + return (currentPackageName.trim().equalsIgnoreCase(newPackageName.trim())) && + (currentThemeId.trim().equalsIgnoreCase(newThemeId.trim())); + } + return false; + } + + @Override + public final String toString() { + StringBuilder result = new StringBuilder(); + result.append(mThemeId); + result.append("_"); + if (mThemePackageName != null && mThemePackageName.length() > 0){ + result.append(mThemePackageName); + } + + return result.toString(); + } + + @Override + public synchronized int hashCode() { + return mThemeId.hashCode() + mThemePackageName.hashCode(); + } + + public String getThemeId() { + return mThemeId; + } + + public String getThemePackageName() { + return mThemePackageName; + } + + public void setThemePackageName(String themePackageName) { + mThemePackageName = themePackageName; + } + + public static CustomTheme getDefault() { + return sDefaultTheme; + } + + public static int getStyleId(Context context, String packageName, String styleName) { + if (TextUtils.isEmpty(packageName) || TextUtils.isEmpty(styleName)) { + return R.style.Theme; + } + try { + PackageInfo pi = context.getPackageManager().getPackageInfo(packageName, 0); + ThemeInfo[] infos = pi.themeInfos; + if (infos != null) { + for (ThemeInfo ti : infos) { + if (ti.themeId.equals(styleName)) { + return ti.styleResourceId; + } + } + } + } catch (PackageManager.NameNotFoundException e) { + Log.e("CustomTheme", "Unable to get style resource id", e); + } + return -1; + } +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 1c0ed36..9ebd8e1 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -35,6 +35,7 @@ import android.util.SparseArray; import android.util.TypedValue; import android.util.LongSparseArray; import android.view.Display; +import android.content.Context; import java.io.IOException; import java.io.InputStream; @@ -106,6 +107,8 @@ public class Resources { return (LongSparseArray<T>) EMPTY_ARRAY; } + private Context mContext; + /** * This exception is thrown by the resource APIs when a requested resource * can not be found. @@ -580,7 +583,7 @@ public class Resources { return loadDrawable(value, id); } } - + /** * Return a movie object associated with the particular resource ID. * @param id The desired resource identifier, as generated by the aapt @@ -921,6 +924,14 @@ public class Resources { } throw new NotFoundException("String resource name " + name); } + + Context getContext() { + return mContext; + } + + void setContext(Context context) { + mContext = context; + } /** * This class holds the current attribute values for a particular theme. @@ -959,6 +970,11 @@ public class Resources { AssetManager.applyThemeStyle(mTheme, resid, force); } + /** @hide */ + public void setAttributeValue(int attr, int color) { + AssetManager.setAttributeValue(mTheme, attr, color); + } + /** * Set this theme to hold the same contents as the theme * <var>other</var>. If both of these themes are from the same @@ -1123,33 +1139,40 @@ public class Resources { array.mRsrcs = attrs; array.mXml = parser; - if (false) { + boolean foo = false; + if (foo) { int[] data = array.mData; System.out.println("Attributes:"); String s = " Attrs:"; int i; - for (i=0; i<set.getAttributeCount(); i++) { - s = s + " " + set.getAttributeName(i); - int id = set.getAttributeNameResource(i); - if (id != 0) { - s = s + "(0x" + Integer.toHexString(id) + ")"; + if(set != null){ + for (i=0; i<set.getAttributeCount(); i++) { + s = s + " " + set.getAttributeName(i); + int id = set.getAttributeNameResource(i); + if (id != 0) { + s = s + "(0x" + Integer.toHexString(id) + ")"; + } + s = s + "=" + set.getAttributeValue(i); } - s = s + "=" + set.getAttributeValue(i); } + System.out.println(s); - s = " Found:"; - TypedValue value = new TypedValue(); - for (i=0; i<attrs.length; i++) { - int d = i*AssetManager.STYLE_NUM_ENTRIES; - value.type = data[d+AssetManager.STYLE_TYPE]; - value.data = data[d+AssetManager.STYLE_DATA]; - value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; - value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; - s = s + " 0x" + Integer.toHexString(attrs[i]) - + "=" + value; + + if(true){ + s = " Found:"; + TypedValue value = new TypedValue(); + for (i=0; i<attrs.length; i++) { + int d = i*AssetManager.STYLE_NUM_ENTRIES; + value.type = data[d+AssetManager.STYLE_TYPE]; + value.data = data[d+AssetManager.STYLE_DATA]; + value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; + value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; + s = s + " 0x" + Integer.toHexString(attrs[i]) + + "=" + value; + } + System.out.println(s); } - System.out.println(s); } return array; @@ -1246,6 +1269,7 @@ public class Resources { array.mRsrcs = attrs; array.mXml = parser; + return array; } @@ -1644,7 +1668,16 @@ public class Resources { flushLayoutCache(); } } - + + /** + * {@hide} + */ + public final void updateStringCache() { + synchronized (mTmpValue) { + mAssets.recreateStringBlocks(); + } + } + /*package*/ Drawable loadDrawable(TypedValue value, int id) throws NotFoundException { diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 016ee7f..72fef32 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -5,6 +5,9 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; +import android.content.Context; +import android.content.res.Resources.NotFoundException; + import com.android.internal.util.XmlUtils; import java.util.Arrays; @@ -268,6 +271,9 @@ public class TypedArray { return csl.getDefaultColor(); } return defValue; + } else if (type == TypedValue.TYPE_ATTRIBUTE && mResources.getContext() != null) { + mResources.getContext().getTheme().resolveAttribute(data[index+AssetManager.STYLE_DATA], mValue, true); + return mValue.data; } throw new UnsupportedOperationException("Can't convert to color: type=0x" @@ -528,9 +534,9 @@ public class TypedArray { * gets the resource ID of the selected attribute, and uses * {@link Resources#getDrawable Resources.getDrawable} of the owning * Resources object to retrieve its Drawable. - * + * * @param index Index of attribute to retrieve. - * + * * @return Drawable for the attribute, or null if not defined. */ public Drawable getDrawable(int index) { @@ -551,6 +557,22 @@ public class TypedArray { } /** + * Very crude hack to allow {@link #getColor} to succeed in looking up + * values from the current theme. This method is necessary as we use + * LayerDrawables which use color attribute references. + * + * @hide + */ + public Drawable getDrawableWithContext(Context context, int id) throws NotFoundException { + mResources.setContext(context); + try { + return getDrawable(id); + } finally { + mResources.setContext(null); + } + } + + /** * Retrieve the CharSequence[] for the attribute at <var>index</var>. * This gets the resource ID of the selected attribute, and uses * {@link Resources#getTextArray Resources.getTextArray} of the owning @@ -675,6 +697,11 @@ public class TypedArray { return mResources.mAssets.getPooledString( cookie, data[index+AssetManager.STYLE_DATA]); } + + /* Hack to allow TypedArrayComposite to instantiate a simple wrapper. */ + TypedArray() { + mResources = null; + } /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) { mResources = resources; @@ -686,4 +713,25 @@ public class TypedArray { public String toString() { return Arrays.toString(mData); } -}
\ No newline at end of file + + /** + * @hide + */ + void dump() { + int attrCount = getIndexCount(); + int[] data = mData; + String s = " Found:"; + TypedValue value = new TypedValue(); + for (int i=0; i<attrCount; i++) { + int attr = getIndex(i); + int d = i*AssetManager.STYLE_NUM_ENTRIES; + value.type = data[d+AssetManager.STYLE_TYPE]; + value.data = data[d+AssetManager.STYLE_DATA]; + value.assetCookie = data[d+AssetManager.STYLE_ASSET_COOKIE]; + value.resourceId = data[d+AssetManager.STYLE_RESOURCE_ID]; + s = s + " 0x" + Integer.toHexString(attr) + + "=" + value; + } + System.out.println(s); + } +} diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index 6336678..cd7db85 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -196,6 +196,10 @@ final class XmlBlock { public int getAttributeCount() { return mEventType == START_TAG ? nativeGetAttributeCount(mParseState) : -1; } + /** @hide */ + public int getAttributeDataType(int index) { + return nativeGetAttributeDataType(mParseState, index); + } public String getAttributeValue(int index) { int id = nativeGetAttributeStringValue(mParseState, index); if (DEBUG) System.out.println("getAttributeValue of " + index + " = " + id); diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 4a036ec..9ff014b 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -124,4 +124,49 @@ public class SystemProperties } native_set(key, val); } -} + + /** + * Get the value for the given key. + * @return def string if the key isn't found + */ + public static String getLongString(String key, String def) { + if (key.length() + 1 > PROP_NAME_MAX) { + throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + } + int chunks = getInt(key + '0', 0); + if (chunks == 0) { + return def; + } + StringBuffer sb = new StringBuffer(); + for (int i = 1; i <= chunks; i++) { + sb.append(native_get(key + Integer.toString(i))); + } + return sb.toString(); + } + + /** + * Set the value for the given key. + * @throws IllegalArgumentException if the key exceeds 32 characters + */ + public static void setLongString(String key, String val) { + if (key.length() + 1 > PROP_NAME_MAX) { + throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX); + } + int chunks = 0; + if (val != null && val.length() > 0) { + chunks = 1 + val.length() / (PROP_VALUE_MAX + 1); + } + native_set(key + '0', Integer.toString(chunks)); + if (chunks > 0) { + for (int i = 1, start = 0; i <= chunks; i++) { + int end = start + PROP_VALUE_MAX; + if (end > val.length()) { + end = val.length(); + } + native_set(key + Integer.toString(i), val.substring(start, end)); + start = end; + } + } + } + +}
\ No newline at end of file diff --git a/core/java/android/text/method/CharacterPickerDialog.java b/core/java/android/text/method/CharacterPickerDialog.java index 880e46d..28564b4 100644 --- a/core/java/android/text/method/CharacterPickerDialog.java +++ b/core/java/android/text/method/CharacterPickerDialog.java @@ -60,7 +60,7 @@ public class CharacterPickerDialog extends Dialog mText = text; mOptions = options; mInsert = insert; - mInflater = LayoutInflater.from(context); + mInflater = LayoutInflater.from(getContext()); } @Override diff --git a/core/java/android/util/AttributeSet.java b/core/java/android/util/AttributeSet.java index 82592b9..5dac1af 100644 --- a/core/java/android/util/AttributeSet.java +++ b/core/java/android/util/AttributeSet.java @@ -57,6 +57,8 @@ package android.util; */ public interface AttributeSet { public int getAttributeCount(); + /** @hide */ + public int getAttributeDataType(int index); public String getAttributeName(int index); public String getAttributeValue(int index); public String getAttributeValue(String namespace, String name); diff --git a/core/java/android/util/XmlPullAttributes.java b/core/java/android/util/XmlPullAttributes.java index 12d6dd9..5de7e5f 100644 --- a/core/java/android/util/XmlPullAttributes.java +++ b/core/java/android/util/XmlPullAttributes.java @@ -32,6 +32,11 @@ class XmlPullAttributes implements AttributeSet { public int getAttributeCount() { return mParser.getAttributeCount(); } + + /** @hide */ + public int getAttributeDataType(int index) { + throw new UnsupportedOperationException(); + } public String getAttributeName(int index) { return mParser.getAttributeName(index); diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 2045a98..4e1e833 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -16,9 +16,16 @@ package android.view; +import android.app.ActivityManager; import android.content.Context; import android.content.ContextWrapper; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; +import android.text.TextUtils; +import android.util.DisplayMetrics; /** * A ContextWrapper that allows you to modify the theme from what is in the @@ -29,6 +36,11 @@ public class ContextThemeWrapper extends ContextWrapper { private int mThemeResource; private Resources.Theme mTheme; private LayoutInflater mInflater; + + /* XXX: Hack to allow themes to be applied on a per-activity basis. Used + * by ThemeManager for real-time theme preview. */ + private boolean mUseThemedResources = false; + private Resources mThemedResources = null; public ContextThemeWrapper() { super(null); @@ -44,26 +56,82 @@ public class ContextThemeWrapper extends ContextWrapper { super.attachBaseContext(newBase); mBase = newBase; } + + @Override + public AssetManager getAssets() { + return getResources().getAssets(); + } + + @Override + public Resources getResources() { + if (mUseThemedResources == true) { + return mThemedResources; + } else { + return mBase.getResources(); + } + } + + private String getPackageResDir(String packageName) { + PackageInfo pi; + try { + pi = getPackageManager().getPackageInfo(packageName, 0); + if (pi == null || pi.applicationInfo == null) + return null; + return pi.applicationInfo.publicSourceDir; + } catch (NameNotFoundException e) { + return null; + } + } + + /** + * {@hide} + * XXX: Hack to support theme preview by temporarily overriding the ApplicationContext's + * Resources on a per-activity basis. Very ugly. + */ + public void useThemedResources(String themePackage) { + if (TextUtils.isEmpty(themePackage)) { + mThemedResources = null; + mUseThemedResources = false; + mTheme = null; + } else { + AssetManager assets = new AssetManager(); + assets.addAssetPath(getPackageResDir(getPackageName())); + assets.addAssetPath(getPackageResDir(themePackage)); + assets.setThemeSupport(true); + assets.setThemePackageName(themePackage); + + DisplayMetrics metrics = new DisplayMetrics(); + WindowManager wm = (WindowManager)getSystemService(WINDOW_SERVICE); + wm.getDefaultDisplay().getMetrics(metrics); + + ActivityManager am = (ActivityManager)getSystemService(ACTIVITY_SERVICE); + Configuration config = am.getConfiguration(); + mThemedResources = new Resources(assets, metrics, config); + mUseThemedResources = true; + mTheme = null; + } + } @Override public void setTheme(int resid) { mThemeResource = resid; initializeTheme(); } - + @Override public Resources.Theme getTheme() { if (mTheme != null) { return mTheme; } if (mThemeResource == 0) { - mThemeResource = com.android.internal.R.style.Theme; + return mBase.getTheme(); + } else { + initializeTheme(); + return mTheme; } - initializeTheme(); - - return mTheme; } - @Override public Object getSystemService(String name) { + @Override + public Object getSystemService(String name) { if (LAYOUT_INFLATER_SERVICE.equals(name)) { if (mInflater == null) { mInflater = LayoutInflater.from(mBase).cloneInContext(this); @@ -89,7 +157,7 @@ public class ContextThemeWrapper extends ContextWrapper { } private void initializeTheme() { - final boolean first = mTheme == null; + final boolean first = (mTheme == null); if (first) { mTheme = getResources().newTheme(); Resources.Theme theme = mBase.getTheme(); @@ -97,7 +165,7 @@ public class ContextThemeWrapper extends ContextWrapper { mTheme.setTo(theme); } } + onApplyThemeResource(mTheme, mThemeResource, first); } } - diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 0b87536..b82cdac 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1847,7 +1847,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility int attr = a.getIndex(i); switch (attr) { case com.android.internal.R.styleable.View_background: - background = a.getDrawable(attr); + background = a.getDrawableWithContext(context, attr); break; case com.android.internal.R.styleable.View_padding: padding = a.getDimensionPixelSize(attr, -1); diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 271989a..7b6a463 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -511,7 +511,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.AbsListView, defStyle, 0); - Drawable d = a.getDrawable(com.android.internal.R.styleable.AbsListView_listSelector); + Drawable d = a.getDrawableWithContext(context, com.android.internal.R.styleable.AbsListView_listSelector); if (d != null) { setSelector(d); } diff --git a/core/java/android/widget/CheckedTextView.java b/core/java/android/widget/CheckedTextView.java index fd590ed..0dd67a2 100644 --- a/core/java/android/widget/CheckedTextView.java +++ b/core/java/android/widget/CheckedTextView.java @@ -59,7 +59,7 @@ public class CheckedTextView extends TextView implements Checkable { TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CheckedTextView, defStyle, 0); - Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark); + Drawable d = a.getDrawableWithContext(context, R.styleable.CheckedTextView_checkMark); if (d != null) { setCheckMarkDrawable(d); } diff --git a/core/java/android/widget/CompoundButton.java b/core/java/android/widget/CompoundButton.java index 98b0976..d24ee5b 100644 --- a/core/java/android/widget/CompoundButton.java +++ b/core/java/android/widget/CompoundButton.java @@ -69,7 +69,7 @@ public abstract class CompoundButton extends Button implements Checkable { context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.CompoundButton, defStyle, 0); - Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); + Drawable d = a.getDrawableWithContext(context, com.android.internal.R.styleable.CompoundButton_button); if (d != null) { setButtonDrawable(d); } diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 6cc794b..130cf56 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -89,10 +89,14 @@ public class LinearLayout extends ViewGroup { } public LinearLayout(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); + } + + public LinearLayout(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); TypedArray a = - context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout); + context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.LinearLayout, defStyle, 0); int index = a.getInt(com.android.internal.R.styleable.LinearLayout_orientation, -1); if (index >= 0) { diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 7c8151e..32ba54e 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -123,6 +123,8 @@ public class ListView extends AbsListView { private boolean mHeaderDividersEnabled; private boolean mFooterDividersEnabled; + + private Drawable.ConstantState mDefaultItemBackground; private boolean mAreAllItemsSelectable = true; @@ -178,6 +180,11 @@ public class ListView extends AbsListView { mHeaderDividersEnabled = a.getBoolean(R.styleable.ListView_headerDividersEnabled, true); mFooterDividersEnabled = a.getBoolean(R.styleable.ListView_footerDividersEnabled, true); + + Drawable defaultItemBackground = a.getDrawable(com.android.internal.R.styleable.ListView_listItemBackground); + if (defaultItemBackground != null) { + mDefaultItemBackground = defaultItemBackground.getConstantState(); + } a.recycle(); } @@ -1662,11 +1669,16 @@ public class ListView extends AbsListView { return child; } - } + } // Make a new view for this position, or convert an unused view if possible child = obtainView(position); - + if (child.getBackground() == null && mDefaultItemBackground != null) { + if (mAdapter.getItemViewType(position) != Adapter.IGNORE_ITEM_VIEW_TYPE) { + child.setBackgroundDrawable(mDefaultItemBackground.newDrawable()); + } + } + // This needs to be positioned and measured setupChild(child, position, y, flow, childrenLeft, selected, false); @@ -1762,7 +1774,7 @@ public class ListView extends AbsListView { child.setDrawingCacheEnabled(true); } } - + @Override protected boolean canAnimate() { return super.canAnimate() && mItemCount > 0; @@ -2824,6 +2836,10 @@ public class ListView extends AbsListView { private View addViewAbove(View theView, int position) { int abovePosition = position - 1; View view = obtainView(abovePosition); + if (view.getBackground() == null && mDefaultItemBackground != null) { + if (mAdapter.getItemViewType(position) != Adapter.IGNORE_ITEM_VIEW_TYPE) + view.setBackgroundDrawable(mDefaultItemBackground.newDrawable()); + } int edgeOfNewChild = theView.getTop() - mDividerHeight; setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left, false, false); return view; @@ -2832,6 +2848,10 @@ public class ListView extends AbsListView { private View addViewBelow(View theView, int position) { int belowPosition = position + 1; View view = obtainView(belowPosition); + if (view.getBackground() == null && mDefaultItemBackground != null) { + if (mAdapter.getItemViewType(position) != Adapter.IGNORE_ITEM_VIEW_TYPE) + view.setBackgroundDrawable(mDefaultItemBackground.newDrawable()); + } int edgeOfNewChild = theView.getBottom() + mDividerHeight; setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left, false, false); return view; diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index 2f28d9f..6693fc9 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -174,7 +174,7 @@ public class ProgressBar extends View { mNoInvalidate = true; - Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); + Drawable drawable = a.getDrawableWithContext(context, R.styleable.ProgressBar_progressDrawable); if (drawable != null) { drawable = tileify(drawable, false); setProgressDrawable(drawable); @@ -204,7 +204,7 @@ public class ProgressBar extends View { setSecondaryProgress( a.getInt(R.styleable.ProgressBar_secondaryProgress, mSecondaryProgress)); - drawable = a.getDrawable(R.styleable.ProgressBar_indeterminateDrawable); + drawable = a.getDrawableWithContext(context, R.styleable.ProgressBar_indeterminateDrawable); if (drawable != null) { drawable = tileifyIndeterminate(drawable); setIndeterminateDrawable(drawable); diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 6771711..683a427 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -17,22 +17,24 @@ package android.widget; import android.app.PendingIntent; +import android.app.ActivityManagerNative; +import android.app.PendingIntent.CanceledException; import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.content.res.CustomTheme; import android.graphics.Bitmap; import android.graphics.PorterDuff; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; -import android.view.LayoutInflater; -import android.view.RemotableViewMethod; -import android.view.View; -import android.view.ViewGroup; +import android.view.*; import android.view.LayoutInflater.Filter; import android.view.View.OnClickListener; @@ -861,9 +863,17 @@ public class RemoteViews implements Parcelable, Filter { if (packageName != null) { try { c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); + CustomTheme theme = ActivityManagerNative.getDefault().getConfiguration().customTheme; + int styleId = CustomTheme.getStyleId(c, theme.getThemePackageName(), theme.getThemeId()); + ContextThemeWrapper themeContext = new ContextThemeWrapper(c, styleId); + themeContext.useThemedResources(theme.getThemePackageName()); + c = themeContext; } catch (NameNotFoundException e) { Log.e(LOG_TAG, "Package name " + packageName + " not found"); c = context; + } catch (RemoteException e) { + Log.e(LOG_TAG, "Failed to get current theme", e); + c = context; } } else { c = context; diff --git a/core/java/android/widget/TabWidget.java b/core/java/android/widget/TabWidget.java index 2ba6268..b813088 100644 --- a/core/java/android/widget/TabWidget.java +++ b/core/java/android/widget/TabWidget.java @@ -27,6 +27,7 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.util.AttributeSet; import android.util.Log; +import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.View.OnFocusChangeListener; @@ -65,19 +66,26 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { } public TabWidget(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs); + super(context, attrs, defStyle); initTabWidget(); TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TabWidget, defStyle, 0); - + + mBottomLeftStrip = a.getDrawableWithContext(context, + com.android.internal.R.styleable.TabWidget_bottomLeftStrip); + mBottomRightStrip = a.getDrawableWithContext(context, + com.android.internal.R.styleable.TabWidget_bottomRightStrip); + a.recycle(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mStripMoved = true; + if (mBottomLeftStrip != null || mBottomRightStrip != null) { + mStripMoved = true; + } super.onSizeChanged(w, h, oldw, oldh); } @@ -101,21 +109,22 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { final Context context = mContext; final Resources resources = context.getResources(); - if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { - // Donut apps get old color scheme - mBottomLeftStrip = resources.getDrawable( - com.android.internal.R.drawable.tab_bottom_left_v4); - mBottomRightStrip = resources.getDrawable( - com.android.internal.R.drawable.tab_bottom_right_v4); - } else { - // Use modern color scheme for Eclair and beyond - mBottomLeftStrip = resources.getDrawable( - com.android.internal.R.drawable.tab_bottom_left); - mBottomRightStrip = resources.getDrawable( - com.android.internal.R.drawable.tab_bottom_right); + if (mBottomLeftStrip == null || mBottomRightStrip == null) { + if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.DONUT) { + // Donut apps get old color scheme + mBottomLeftStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_left_v4); + mBottomRightStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_right_v4); + } else { + // Use modern color scheme for Eclair and beyond + mBottomLeftStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_left); + mBottomRightStrip = resources.getDrawable( + com.android.internal.R.drawable.tab_bottom_right); + } } - // Deal with focus, as we don't want the focus to go by default // to a tab other than the current tab setFocusable(true); @@ -203,31 +212,43 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { View selectedChild = getChildTabViewAt(mSelectedTab); - mBottomLeftStrip.setState(selectedChild.getDrawableState()); - mBottomRightStrip.setState(selectedChild.getDrawableState()); + if (mBottomLeftStrip != null) { + mBottomLeftStrip.setState(selectedChild.getDrawableState()); + } + if (mBottomRightStrip != null) { + mBottomRightStrip.setState(selectedChild.getDrawableState()); + } if (mStripMoved) { Rect selBounds = new Rect(); // Bounds of the selected tab indicator selBounds.left = selectedChild.getLeft(); selBounds.right = selectedChild.getRight(); final int myHeight = getHeight(); - mBottomLeftStrip.setBounds( - Math.min(0, selBounds.left - - mBottomLeftStrip.getIntrinsicWidth()), - myHeight - mBottomLeftStrip.getIntrinsicHeight(), - selBounds.left, - getHeight()); - mBottomRightStrip.setBounds( - selBounds.right, - myHeight - mBottomRightStrip.getIntrinsicHeight(), - Math.max(getWidth(), - selBounds.right + mBottomRightStrip.getIntrinsicWidth()), - myHeight); + if (mBottomLeftStrip != null) { + mBottomLeftStrip.setBounds( + Math.min(0, selBounds.left + - mBottomLeftStrip.getIntrinsicWidth()), + myHeight - mBottomLeftStrip.getIntrinsicHeight(), + selBounds.left, + getHeight()); + } + if (mBottomRightStrip != null) { + mBottomRightStrip.setBounds( + selBounds.right, + myHeight - mBottomRightStrip.getIntrinsicHeight(), + Math.max(getWidth(), + selBounds.right + mBottomRightStrip.getIntrinsicWidth()), + myHeight); + } mStripMoved = false; } - mBottomLeftStrip.draw(canvas); - mBottomRightStrip.draw(canvas); + if (mBottomLeftStrip != null) { + mBottomLeftStrip.draw(canvas); + } + if (mBottomRightStrip != null) { + mBottomRightStrip.draw(canvas); + } } /** @@ -265,7 +286,9 @@ public class TabWidget extends LinearLayout implements OnFocusChangeListener { getChildTabViewAt(mSelectedTab).setSelected(false); mSelectedTab = index; getChildTabViewAt(mSelectedTab).setSelected(true); - mStripMoved = true; + if (mBottomLeftStrip != null || mBottomRightStrip != null) { + mStripMoved = true; + } } /** diff --git a/core/java/com/android/internal/app/AlertActivity.java b/core/java/com/android/internal/app/AlertActivity.java index 7251256..a628e3d 100644 --- a/core/java/com/android/internal/app/AlertActivity.java +++ b/core/java/com/android/internal/app/AlertActivity.java @@ -47,7 +47,7 @@ public abstract class AlertActivity extends Activity implements DialogInterface super.onCreate(savedInstanceState); mAlert = new AlertController(this, this, getWindow()); - mAlertParams = new AlertController.AlertParams(this); + mAlertParams = new AlertController.AlertParams(); } public void cancel() { diff --git a/core/java/com/android/internal/app/AlertController.java b/core/java/com/android/internal/app/AlertController.java index 57dbb44..5434b61 100644 --- a/core/java/com/android/internal/app/AlertController.java +++ b/core/java/com/android/internal/app/AlertController.java @@ -59,6 +59,7 @@ public class AlertController { private final Context mContext; private final DialogInterface mDialogInterface; private final Window mWindow; + private final LayoutInflater mInflater; private CharSequence mTitle; @@ -166,6 +167,7 @@ public class AlertController { public AlertController(Context context, DialogInterface di, Window window) { mContext = context; + mInflater = LayoutInflater.from(context); mDialogInterface = di; mWindow = window; mHandler = new ButtonHandler(di); @@ -695,9 +697,6 @@ public class AlertController { } public static class AlertParams { - public final Context mContext; - public final LayoutInflater mInflater; - public int mIconId = -1; public Drawable mIcon; public CharSequence mTitle; @@ -747,10 +746,8 @@ public class AlertController { void onPrepareListView(ListView listView); } - public AlertParams(Context context) { - mContext = context; + public AlertParams() { mCancelable = true; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public void apply(AlertController dialog) { @@ -810,13 +807,13 @@ public class AlertController { private void createListView(final AlertController dialog) { final RecycleListView listView = (RecycleListView) - mInflater.inflate(R.layout.select_dialog, null); + dialog.mInflater.inflate(R.layout.select_dialog, null); ListAdapter adapter; if (mIsMultiChoice) { if (mCursor == null) { adapter = new ArrayAdapter<CharSequence>( - mContext, R.layout.select_dialog_multichoice, R.id.text1, mItems) { + dialog.mContext, R.layout.select_dialog_multichoice, R.id.text1, mItems) { @Override public View getView(int position, View convertView, ViewGroup parent) { View view = super.getView(position, convertView, parent); @@ -830,7 +827,7 @@ public class AlertController { } }; } else { - adapter = new CursorAdapter(mContext, mCursor, false) { + adapter = new CursorAdapter(dialog.mContext, mCursor, false) { private final int mLabelIndex; private final int mIsCheckedIndex; @@ -850,7 +847,7 @@ public class AlertController { @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - return mInflater.inflate(R.layout.select_dialog_multichoice, + return dialog.mInflater.inflate(R.layout.select_dialog_multichoice, parent, false); } @@ -861,9 +858,10 @@ public class AlertController { ? R.layout.select_dialog_singlechoice : R.layout.select_dialog_item; if (mCursor == null) { adapter = (mAdapter != null) ? mAdapter - : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems); + : new ArrayAdapter<CharSequence>(dialog.mContext, + layout, R.id.text1, mItems); } else { - adapter = new SimpleCursorAdapter(mContext, layout, + adapter = new SimpleCursorAdapter(dialog.mContext, layout, mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1}); } } diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java index 5a0fea3..33e7d6f 100644 --- a/core/java/com/android/internal/app/RingtonePickerActivity.java +++ b/core/java/com/android/internal/app/RingtonePickerActivity.java @@ -59,6 +59,9 @@ public final class RingtonePickerActivity extends AlertActivity implements /** The position in the list of the 'Default' item. */ private int mDefaultRingtonePos = -1; + /** The position in the list of the 'Buy ringtone' item (should be 0). */ + private int mBuyPos = -1; + /** The position in the list of the last clicked item. */ private int mClickedPos = -1; @@ -67,7 +70,10 @@ public final class RingtonePickerActivity extends AlertActivity implements /** Whether this list has the 'Silent' item. */ private boolean mHasSilentItem; - + + /** Whether this list has the 'Buy ringtone' item. */ + private boolean mHasBuyItem; + /** The Uri to place a checkmark next to. */ private Uri mExistingUri; @@ -94,13 +100,19 @@ public final class RingtonePickerActivity extends AlertActivity implements * On item clicked */ public void onClick(DialogInterface dialog, int which) { + if (which == mBuyPos) { + startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("http://wap.t-zones.com"))); + finish(); + } + // Save the position of most recently clicked item mClickedPos = which; - + // Play clip playRingtone(which, 0); } - + }; @Override @@ -120,10 +132,13 @@ public final class RingtonePickerActivity extends AlertActivity implements if (mUriForDefaultItem == null) { mUriForDefaultItem = Settings.System.DEFAULT_RINGTONE_URI; } - + // Get whether to show the 'Silent' item mHasSilentItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true); - + + // Get whether to show the 'Buy ringtones' item + mHasBuyItem = intent.getBooleanExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_BUY, false); + // Give the Activity so it can do managed queries mRingtoneManager = new RingtoneManager(this); @@ -168,7 +183,11 @@ public final class RingtonePickerActivity extends AlertActivity implements } public void onPrepareListView(ListView listView) { - + + if (mHasBuyItem) { + mBuyPos = addBuyItem(listView); + } + if (mHasDefaultItem) { mDefaultRingtonePos = addDefaultRingtoneItem(listView); @@ -202,29 +221,38 @@ public final class RingtonePickerActivity extends AlertActivity implements * @param textResId The resource ID of the text for the item. * @return The position of the inserted item. */ - private int addStaticItem(ListView listView, int textResId) { - TextView textView = (TextView) getLayoutInflater().inflate( - com.android.internal.R.layout.select_dialog_singlechoice, listView, false); + private int addStaticItem(ListView listView, int resId, int textResId) { + TextView textView = (TextView) getLayoutInflater().inflate(resId, listView, false); textView.setText(textResId); listView.addHeaderView(textView); mStaticItemCount++; return listView.getHeaderViewsCount() - 1; } - + + private int addBuyItem(ListView listView) { + return addStaticItem(listView, + com.android.internal.R.layout.simple_list_item_1, + com.android.internal.R.string.buy_ringtones); + } + private int addDefaultRingtoneItem(ListView listView) { - return addStaticItem(listView, com.android.internal.R.string.ringtone_default); + return addStaticItem(listView, + com.android.internal.R.layout.select_dialog_singlechoice, + com.android.internal.R.string.ringtone_default); } private int addSilentItem(ListView listView) { - return addStaticItem(listView, com.android.internal.R.string.ringtone_silent); + return addStaticItem(listView, + com.android.internal.R.layout.select_dialog_singlechoice, + com.android.internal.R.string.ringtone_silent); } /* * On click of Ok/Cancel buttons */ public void onClick(DialogInterface dialog, int which) { - boolean positiveResult = which == BUTTON1; - + boolean positiveResult = which == BUTTON1 && mClickedPos != mBuyPos; + // Stop playing the previous ringtone mRingtoneManager.stopPreviousRingtone(); diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 404c513..b4b90b6 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -533,7 +533,7 @@ public class ZygoteInit { String args[] = { "--setuid=1000", "--setgid=1000", - "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,3001,3002,3003", + "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1300,3001,3002,3003", "--capabilities=130104352,130104352", "--runtime-init", "--nice-name=system_server", diff --git a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java index bf44d51..3326c67 100644 --- a/core/java/com/android/internal/view/menu/ContextMenuBuilder.java +++ b/core/java/com/android/internal/view/menu/ContextMenuBuilder.java @@ -16,6 +16,7 @@ package com.android.internal.view.menu; +import android.app.Dialog; import android.content.Context; import android.graphics.drawable.Drawable; import android.os.IBinder; @@ -38,6 +39,7 @@ import android.view.View; * with a view's context menu items and show the context menu. */ public class ContextMenuBuilder extends MenuBuilder implements ContextMenu { + private Context mContext; public ContextMenuBuilder(Context context) { super(context); @@ -85,12 +87,27 @@ public class ContextMenuBuilder extends MenuBuilder implements ContextMenu { EventLog.writeEvent(50001, 1); MenuDialogHelper helper = new MenuDialogHelper(this); - helper.show(token); + Dialog d = helper.create(token); + mContext = d.getContext(); + d.show(); return helper; } return null; } - + + /** + * The dialog holding this context menu can be themed separately from the + * Context that created it. This is here to ensure that the right context + * is used during inflation. + */ + @Override + public Context getContext() { + if (mContext != null) { + return mContext; + } else { + return super.getContext(); + } + } } diff --git a/core/java/com/android/internal/view/menu/MenuBuilder.java b/core/java/com/android/internal/view/menu/MenuBuilder.java index 228d5d0..e0f66a2 100644 --- a/core/java/com/android/internal/view/menu/MenuBuilder.java +++ b/core/java/com/android/internal/view/menu/MenuBuilder.java @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Parcelable; @@ -176,8 +177,22 @@ public class MenuBuilder implements Menu { LayoutInflater getInflater() { // Create an inflater that uses the given theme for the Views it inflates if (mInflater == null) { - Context wrappedContext = new ContextThemeWrapper(mContext, - THEME_RES_FOR_TYPE[mMenuType]); + Context wrappedContext = null; + // We theme expanded menu view only for now. + if (mMenuType == TYPE_EXPANDED) { + wrappedContext = new ContextThemeWrapper(getContext(), + resolveDefaultTheme(getContext(), 0, + com.android.internal.R.styleable.Theme_expandedMenuTheme, + com.android.internal.R.style.Theme_ExpandedMenu)); + } else if (mMenuType == TYPE_ICON) { + wrappedContext = new ContextThemeWrapper(getContext(), + resolveDefaultTheme(getContext(), 0, + com.android.internal.R.styleable.Theme_iconMenuTheme, + com.android.internal.R.style.Theme_IconMenu)); + } else { + wrappedContext = new ContextThemeWrapper(getContext(), + THEME_RES_FOR_TYPE[mMenuType]); + } mInflater = (LayoutInflater) wrappedContext .getSystemService(Context.LAYOUT_INFLATER_SERVICE); } @@ -217,6 +232,18 @@ public class MenuBuilder implements Menu { boolean hasMenuView() { return mMenuView != null && mMenuView.get() != null; } + + int resolveDefaultTheme(Context context, int theme, int themeAttrIndex, + int defStyle) { + if (theme != 0) { + return theme; + } else { + TypedArray a = context.obtainStyledAttributes(android.R.styleable.Theme); + int newTheme = a.getResourceId(themeAttrIndex, defStyle); + a.recycle(); + return newTheme; + } + } } /** diff --git a/core/java/com/android/internal/view/menu/MenuDialogHelper.java b/core/java/com/android/internal/view/menu/MenuDialogHelper.java index 70f040a..d657f47 100644 --- a/core/java/com/android/internal/view/menu/MenuDialogHelper.java +++ b/core/java/com/android/internal/view/menu/MenuDialogHelper.java @@ -21,6 +21,7 @@ import android.app.Dialog; import android.content.DialogInterface; import android.os.IBinder; import android.view.KeyEvent; +import android.view.LayoutInflater; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -39,13 +40,13 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn public MenuDialogHelper(MenuBuilder menu) { mMenu = menu; } - + /** - * Shows menu as a dialog. + * Creates menu as a dialog. Does not show. * * @param windowToken Optional token to assign to the window. */ - public void show(IBinder windowToken) { + public Dialog create(IBinder windowToken) { // Many references to mMenu, create local reference final MenuBuilder menu = mMenu; @@ -81,8 +82,18 @@ public class MenuDialogHelper implements DialogInterface.OnKeyListener, DialogIn if (windowToken != null) { lp.token = windowToken; } - lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; + return mDialog; + } + + /** + * Shows menu as a dialog. + * + * @param windowToken Optional token to assign to the window. + */ + public void show(IBinder windowToken) { + create(windowToken); mDialog.show(); } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 562cc8f..39a03de 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -2,16 +2,16 @@ ** ** Copyright 2006, The Android Open Source Project ** -** Licensed under the Apache License, Version 2.0 (the "License"); -** you may not use this file except in compliance with the License. -** You may obtain a copy of the License at +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at ** -** http://www.apache.org/licenses/LICENSE-2.0 +** http://www.apache.org/licenses/LICENSE-2.0 ** -** Unless required by applicable law or agreed to in writing, software -** distributed under the License is distributed on an "AS IS" BASIS, -** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -** See the License for the specific language governing permissions and +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and ** limitations under the License. */ @@ -29,6 +29,7 @@ #include <utils/Asset.h> #include <utils/AssetManager.h> #include <utils/ResourceTypes.h> +#include <utils/ZipFile.h> #include <stdio.h> @@ -159,30 +160,30 @@ static jobject returnParcelFileDescriptor(JNIEnv* env, Asset* a, jlongArray outO off_t startOffset, length; int fd = a->openFileDescriptor(&startOffset, &length); delete a; - + if (fd < 0) { doThrow(env, "java/io/FileNotFoundException", "This file can not be opened as a file descriptor; it is probably compressed"); return NULL; } - + jlong* offsets = (jlong*)env->GetPrimitiveArrayCritical(outOffsets, 0); if (offsets == NULL) { close(fd); return NULL; } - + offsets[0] = startOffset; offsets[1] = length; - + env->ReleasePrimitiveArrayCritical(outOffsets, offsets, 0); - + jobject fileDesc = newFileDescriptor(env, fd); if (fileDesc == NULL) { close(fd); return NULL; } - + return newParcelFileDescriptor(env, fileDesc); } @@ -391,7 +392,7 @@ static jint android_content_AssetManager_readAsset(JNIEnv* env, jobject clazz, if (len == 0) { return 0; } - + jsize bLen = env->GetArrayLength(bArray); if (off < 0 || off >= bLen || len < 0 || len > bLen || (off+len) > bLen) { doThrow(env, "java/lang/IndexOutOfBoundsException"); @@ -545,9 +546,9 @@ static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject c ResTable_config config; memset(&config, 0, sizeof(config)); - + const char* locale8 = locale != NULL ? env->GetStringUTFChars(locale, NULL) : NULL; - + config.mcc = (uint16_t)mcc; config.mnc = (uint16_t)mnc; config.orientation = (uint8_t)orientation; @@ -562,7 +563,7 @@ static void android_content_AssetManager_setConfiguration(JNIEnv* env, jobject c config.sdkVersion = (uint16_t)sdkVersion; config.minorVersion = 0; am->setConfiguration(config, locale8); - + if (locale != NULL) env->ReleaseStringUTFChars(locale, locale8); } @@ -613,12 +614,12 @@ static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject if (am == NULL) { return NULL; } - + ResTable::resource_name name; if (!am->getResources().getResourceName(resid, &name)) { return NULL; } - + String16 str; if (name.package != NULL) { str.setTo(name.package, name.packageLen); @@ -637,7 +638,7 @@ static jstring android_content_AssetManager_getResourceName(JNIEnv* env, jobject } str.append(name.name, name.nameLen); } - + return env->NewString((const jchar*)str.string(), str.size()); } @@ -648,16 +649,16 @@ static jstring android_content_AssetManager_getResourcePackageName(JNIEnv* env, if (am == NULL) { return NULL; } - + ResTable::resource_name name; if (!am->getResources().getResourceName(resid, &name)) { return NULL; } - + if (name.package != NULL) { return env->NewString((const jchar*)name.package, name.packageLen); } - + return NULL; } @@ -668,16 +669,16 @@ static jstring android_content_AssetManager_getResourceTypeName(JNIEnv* env, job if (am == NULL) { return NULL; } - + ResTable::resource_name name; if (!am->getResources().getResourceName(resid, &name)) { return NULL; } - + if (name.type != NULL) { return env->NewString((const jchar*)name.type, name.typeLen); } - + return NULL; } @@ -688,16 +689,16 @@ static jstring android_content_AssetManager_getResourceEntryName(JNIEnv* env, jo if (am == NULL) { return NULL; } - + ResTable::resource_name name; if (!am->getResources().getResourceName(resid, &name)) { return NULL; } - + if (name.name != NULL) { return env->NewString((const jchar*)name.name, name.nameLen); } - + return NULL; } @@ -732,10 +733,10 @@ static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobje return 0; } const ResTable& res(am->getResources()); - + // Now lock down the resource object and start pulling stuff from it. res.lock(); - + ssize_t block = -1; Res_value value; @@ -756,7 +757,7 @@ static jint android_content_AssetManager_loadResourceBagValue(JNIEnv* env, jobje if (block < 0) { return block; } - + uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref, &typeSpecFlags); @@ -828,6 +829,16 @@ static void android_content_AssetManager_applyThemeStyle(JNIEnv* env, jobject cl theme->applyStyle(styleRes, force ? true : false); } +static void android_content_AssetManager_setAttributeValue(JNIEnv* env, jobject clazz, + jint themeInt, + jint attribute, + jint value) +{ + ResTable::Theme* theme = (ResTable::Theme*)themeInt; + theme->setAttributeValue(attribute, value); +} + + static void android_content_AssetManager_copyTheme(JNIEnv* env, jobject clazz, jint destInt, jint srcInt) { @@ -859,21 +870,21 @@ static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, { ResTable::Theme* theme = (ResTable::Theme*)themeInt; const ResTable& res(theme->getResTable()); - + if (tag == NULL) { doThrow(env, "java/lang/NullPointerException"); return; } - + const char* tag8 = env->GetStringUTFChars(tag, NULL); const char* prefix8 = NULL; if (prefix != NULL) { prefix8 = env->GetStringUTFChars(prefix, NULL); } - + // XXX Need to use params. theme->dumpToLog(); - + if (prefix8 != NULL) { env->ReleaseStringUTFChars(prefix, prefix8); } @@ -1076,12 +1087,12 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla dest[STYLE_RESOURCE_ID] = resid; dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; dest[STYLE_DENSITY] = config.density; - + if (indices != NULL && value.dataType != Res_value::TYPE_NULL) { indicesIdx++; indices[indicesIdx] = ii; } - + dest += STYLE_NUM_ENTRIES; } @@ -1107,7 +1118,7 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job doThrow(env, "java/lang/NullPointerException"); return JNI_FALSE; } - + AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return JNI_FALSE; @@ -1116,20 +1127,20 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job ResXMLParser* xmlParser = (ResXMLParser*)xmlParserToken; ResTable_config config; Res_value value; - + const jsize NI = env->GetArrayLength(attrs); const jsize NV = env->GetArrayLength(outValues); if (NV < (NI*STYLE_NUM_ENTRIES)) { doThrow(env, "java/lang/IndexOutOfBoundsException"); return JNI_FALSE; } - + jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); if (src == NULL) { doThrow(env, "java/lang/OutOfMemoryError"); return JNI_FALSE; } - + jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); jint* dest = baseDest; if (dest == NULL) { @@ -1137,7 +1148,7 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job doThrow(env, "java/lang/OutOfMemoryError"); return JNI_FALSE; } - + jint* indices = NULL; int indicesIdx = 0; if (outIndices != NULL) { @@ -1148,27 +1159,27 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job // Now lock down the resource object and start pulling stuff from it. res.lock(); - + // Retrieve the XML attributes, if requested. const jsize NX = xmlParser->getAttributeCount(); jsize ix=0; uint32_t curXmlAttr = xmlParser->getAttributeNameResID(ix); - + static const ssize_t kXmlBlock = 0x10000000; - + // Now iterate through all of the attributes that the client has requested, // filling in each with whatever data we can find. ssize_t block = 0; uint32_t typeSetFlags; for (jsize ii=0; ii<NI; ii++) { const uint32_t curIdent = (uint32_t)src[ii]; - + // Try to find a value for this attribute... value.dataType = Res_value::TYPE_NULL; value.data = 0; typeSetFlags = 0; config.density = 0; - + // Skip through XML attributes until the end or the next possible match. while (ix < NX && curIdent > curXmlAttr) { ix++; @@ -1181,7 +1192,7 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job ix++; curXmlAttr = xmlParser->getAttributeNameResID(ix); } - + //printf("Attribute 0x%08x: type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); uint32_t resid = 0; if (value.dataType != Res_value::TYPE_NULL) { @@ -1191,14 +1202,14 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job &typeSetFlags, &config); if (newBlock >= 0) block = newBlock; } - + // Deal with the special @null value -- it turns back to TYPE_NULL. if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { value.dataType = Res_value::TYPE_NULL; } - + //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); - + // Write the final value back to Java. dest[STYLE_TYPE] = value.dataType; dest[STYLE_DATA] = value.data; @@ -1207,25 +1218,25 @@ static jboolean android_content_AssetManager_retrieveAttributes(JNIEnv* env, job dest[STYLE_RESOURCE_ID] = resid; dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; dest[STYLE_DENSITY] = config.density; - + if (indices != NULL && value.dataType != Res_value::TYPE_NULL) { indicesIdx++; indices[indicesIdx] = ii; } - + dest += STYLE_NUM_ENTRIES; } - + res.unlock(); - + if (indices != NULL) { indices[0] = indicesIdx; env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); } - + env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); env->ReleasePrimitiveArrayCritical(attrs, src, 0); - + return JNI_TRUE; } @@ -1237,12 +1248,12 @@ static jint android_content_AssetManager_getArraySize(JNIEnv* env, jobject clazz return NULL; } const ResTable& res(am->getResources()); - + res.lock(); const ResTable::bag_entry* defStyleEnt = NULL; ssize_t bagOff = res.getBagLocked(id, &defStyleEnt); res.unlock(); - + return bagOff; } @@ -1254,7 +1265,7 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz doThrow(env, "java/lang/NullPointerException"); return JNI_FALSE; } - + AssetManager* am = assetManagerForJavaObject(env, clazz); if (am == NULL) { return JNI_FALSE; @@ -1263,25 +1274,25 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz ResTable_config config; Res_value value; ssize_t block; - + const jsize NV = env->GetArrayLength(outValues); - + jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); jint* dest = baseDest; if (dest == NULL) { doThrow(env, "java/lang/OutOfMemoryError"); return JNI_FALSE; } - + // Now lock down the resource object and start pulling stuff from it. res.lock(); - + const ResTable::bag_entry* arrayEnt = NULL; uint32_t arrayTypeSetFlags = 0; ssize_t bagOff = res.getBagLocked(id, &arrayEnt, &arrayTypeSetFlags); const ResTable::bag_entry* endArrayEnt = arrayEnt + (bagOff >= 0 ? bagOff : 0); - + int i = 0; uint32_t typeSetFlags; while (i < NV && arrayEnt < endArrayEnt) { @@ -1289,7 +1300,7 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz typeSetFlags = arrayTypeSetFlags; config.density = 0; value = arrayEnt->map.value; - + uint32_t resid = 0; if (value.dataType != Res_value::TYPE_NULL) { // Take care of resolving the found resource to its final value. @@ -1317,13 +1328,13 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz i+= STYLE_NUM_ENTRIES; arrayEnt++; } - + i /= STYLE_NUM_ENTRIES; - + res.unlock(); - + env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); - + return i; } @@ -1396,13 +1407,13 @@ static jintArray android_content_AssetManager_getArrayStringInfo(JNIEnv* env, jo jint stringIndex = -1; jint stringBlock = 0; value = bag->map.value; - + // Take care of resolving the found resource to its final value. stringBlock = res.resolveReference(&value, bag->stringBlock, NULL); if (value.dataType == Res_value::TYPE_STRING) { stringIndex = value.data; } - + //todo: It might be faster to allocate a C array to contain // the blocknums and indices, put them in there and then // do just one SetIntArrayRegion() @@ -1448,7 +1459,7 @@ static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { value = bag->map.value; jstring str = NULL; - + // Take care of resolving the found resource to its final value. ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); if (value.dataType == Res_value::TYPE_STRING) { @@ -1460,7 +1471,7 @@ static jobjectArray android_content_AssetManager_getArrayStringResource(JNIEnv* return NULL; } } - + env->SetObjectArrayElement(array, i, str); } res.unlockBag(startOfBag); @@ -1493,7 +1504,7 @@ static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, j const ResTable::bag_entry* bag = startOfBag; for (size_t i=0; ((ssize_t)i)<N; i++, bag++) { value = bag->map.value; - + // Take care of resolving the found resource to its final value. ssize_t block = res.resolveReference(&value, bag->stringBlock, NULL); if (value.dataType >= Res_value::TYPE_FIRST_INT @@ -1506,6 +1517,84 @@ static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, j return array; } +static jint android_content_AssetManager_splitThemePackage(JNIEnv* env, jobject clazz, + jstring srcFileName, jstring dstFileName, jobjectArray drmProtectedAssetNames) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return -1; + } + + LOGV("splitThemePackage in %p (Java object %p)\n", am, clazz); + + if (srcFileName == NULL || dstFileName == NULL) { + doThrow(env, "java/lang/NullPointerException"); + return -2; + } + + jsize size = env->GetArrayLength(drmProtectedAssetNames); + if (size == 0) { + doThrow(env, "java/lang/IllegalArgumentException"); + return -3; + } + + const char* srcFileName8 = env->GetStringUTFChars(srcFileName, NULL); + ZipFile* srcZip = new ZipFile; + status_t err = srcZip->open(srcFileName8, ZipFile::kOpenReadWrite); + if (err != NO_ERROR) { + LOGV("error opening zip file %s\n", srcFileName8); + delete srcZip; + env->ReleaseStringUTFChars(srcFileName, srcFileName8); + return -4; + } + + const char* dstFileName8 = env->GetStringUTFChars(dstFileName, NULL); + ZipFile* dstZip = new ZipFile; + err = dstZip->open(dstFileName8, ZipFile::kOpenReadWrite | ZipFile::kOpenTruncate | ZipFile::kOpenCreate); + + if (err != NO_ERROR) { + LOGV("error opening zip file %s\n", dstFileName8); + delete srcZip; + delete dstZip; + env->ReleaseStringUTFChars(srcFileName, srcFileName8); + env->ReleaseStringUTFChars(dstFileName, dstFileName8); + return -5; + } + + int result = 0; + for (int i = 0; i < size; i++) { + jstring javaString = (jstring)env->GetObjectArrayElement(drmProtectedAssetNames, i); + const char* drmProtectedAssetFileName8 = env->GetStringUTFChars(javaString, NULL); + ZipEntry *assetEntry = srcZip->getEntryByName(drmProtectedAssetFileName8); + if (assetEntry == NULL) { + result = 1; + LOGV("Invalid asset entry %s\n", drmProtectedAssetFileName8); + } else { + status_t loc_result = dstZip->add(srcZip, assetEntry, 0, NULL); + if (loc_result != NO_ERROR) { + LOGV("error copying zip entry %s\n", drmProtectedAssetFileName8); + result = result | 2; + } else { + loc_result = srcZip->remove(assetEntry); + if (loc_result != NO_ERROR) { + LOGV("error removing zip entry %s\n", drmProtectedAssetFileName8); + result = result | 4; + } + } + } + env->ReleaseStringUTFChars(javaString, drmProtectedAssetFileName8); + } + srcZip->flush(); + dstZip->flush(); + + delete srcZip; + delete dstZip; + env->ReleaseStringUTFChars(srcFileName, srcFileName8); + env->ReleaseStringUTFChars(dstFileName, dstFileName8); + + return (jint)result; +} + static void android_content_AssetManager_init(JNIEnv* env, jobject clazz) { AssetManager* am = new AssetManager(); @@ -1557,6 +1646,60 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, return AssetManager::getGlobalCount(); } +static jboolean android_content_AssetManager_removeAssetPath(JNIEnv* env, jobject clazz, + jstring packageName, jstring path) +{ + if (path == NULL) { + doThrow(env, "java/lang/NullPointerException"); + return JNI_FALSE; + } + + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + const char* name8 = env->GetStringUTFChars(packageName, NULL); + const char* path8 = env->GetStringUTFChars(path, NULL); + bool res = am->removeAssetPath(String8(name8), String8(path8)); + env->ReleaseStringUTFChars(path, path8); + env->ReleaseStringUTFChars(packageName, name8); + + return res; +} + +static jint android_content_AssetManager_updateResourcesWithAssetPath( + JNIEnv* env, jobject clazz, jstring path) +{ + if (path == NULL) { + doThrow(env, "java/lang/NullPointerException"); + return JNI_FALSE; + } + + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + const char* path8 = env->GetStringUTFChars(path, NULL); + + void* cookie; + bool res = am->updateWithAssetPath(String8(path8), &cookie); + + env->ReleaseStringUTFChars(path, path8); + + return (res) ? (jint)cookie : 0; +} + +static void android_content_AssetManager_dumpRes(JNIEnv* env, jobject clazz) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return; + } + am->dumpRes(); +} + // ---------------------------------------------------------------------------- /* @@ -1642,6 +1785,8 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_getArraySize }, { "retrieveArray","(I[I)I", (void*) android_content_AssetManager_retrieveArray }, + { "setAttributeValue", "(III)V", + (void*) android_content_AssetManager_setAttributeValue }, // XML files. { "openXmlAssetNative", "(ILjava/lang/String;)I", @@ -1666,6 +1811,17 @@ static JNINativeMethod gAssetManagerMethods[] = { (void*) android_content_AssetManager_getAssetAllocations }, { "getGlobalAssetManagerCount", "()I", (void*) android_content_AssetManager_getGlobalAssetCount }, + + // Split theme package apk into two. + { "splitThemePackage","(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)I", + (void*) android_content_AssetManager_splitThemePackage }, + + { "removeAssetPath", "(Ljava/lang/String;Ljava/lang/String;)Z", + (void*) android_content_AssetManager_removeAssetPath }, + { "updateResourcesWithAssetPath", "(Ljava/lang/String;)I", + (void*) android_content_AssetManager_updateResourcesWithAssetPath }, + { "dumpResources", "()V", + (void*) android_content_AssetManager_dumpRes }, }; int register_android_content_AssetManager(JNIEnv* env) diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 7d6f158..f50cf2b 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -18,6 +18,7 @@ */ --> <manifest xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:pluto="http://www.w3.org/2001/pluto.html" package="android" android:sharedUserId="android.uid.system" android:sharedUserLabel="@string/android_system_label"> @@ -1134,7 +1135,8 @@ android:allowClearUserData="false" android:backupAgent="com.android.server.SystemBackupAgent" android:killAfterRestore="false" - android:icon="@drawable/ic_launcher_android"> + android:icon="@drawable/ic_launcher_android" + pluto:isThemeable="true"> <activity android:name="com.android.internal.app.ChooserActivity" android:theme="@style/Theme.Dialog.Alert" android:finishOnCloseSystemDialogs="true" @@ -1210,6 +1212,18 @@ <category android:name="android.intent.category.MASTER_CLEAR" /> </intent-filter> </receiver> + + <receiver android:name="com.android.server.AppsLaunchFailureReceiver" > + <intent-filter> + <action android:name="com.tmobile.intent.action.APP_LAUNCH_FAILURE" /> + <action android:name="com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET" /> + <action android:name="android.intent.action.PACKAGE_ADDED" /> + <action android:name="android.intent.action.PACKAGE_REMOVED" /> + <action android:name="com.tmobile.intent.action.THEME_PACKAGE_UPDATED" /> + <category android:name="com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE" /> + <data android:scheme="package" /> + </intent-filter> + </receiver> </application> </manifest> diff --git a/core/res/res/drawable/pluto_mall_icon.png b/core/res/res/drawable/pluto_mall_icon.png Binary files differnew file mode 100755 index 0000000..8599f48 --- /dev/null +++ b/core/res/res/drawable/pluto_mall_icon.png diff --git a/core/res/res/layout/list_menu_item_layout.xml b/core/res/res/layout/list_menu_item_layout.xml index df4958f..06e9afd 100644 --- a/core/res/res/layout/list_menu_item_layout.xml +++ b/core/res/res/layout/list_menu_item_layout.xml @@ -36,11 +36,12 @@ android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_alignParentLeft="true" - android:textAppearance="?android:attr/textAppearanceLargeInverse" + android:textAppearance="?android:attr/textAppearanceLarge" android:singleLine="true" android:duplicateParentState="true" android:ellipsize="marquee" - android:fadingEdge="horizontal" /> + android:fadingEdge="horizontal" + style="?android:attr/listItemTextViewStyle" /> <TextView android:id="@+id/shortcut" @@ -48,9 +49,10 @@ android:layout_height="wrap_content" android:layout_below="@id/title" android:layout_alignParentLeft="true" - android:textAppearance="?android:attr/textAppearanceSmallInverse" + android:textAppearance="?android:attr/textAppearanceSmall" android:singleLine="true" - android:duplicateParentState="true" /> + android:duplicateParentState="true" + style="?android:attr/listItemTextViewStyle" /> </RelativeLayout> diff --git a/core/res/res/layout/number_picker.xml b/core/res/res/layout/number_picker.xml index bbdb31c..974130a 100644 --- a/core/res/res/layout/number_picker.xml +++ b/core/res/res/layout/number_picker.xml @@ -22,21 +22,18 @@ <com.android.internal.widget.NumberPickerButton android:id="@+id/increment" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:background="@drawable/timepicker_up_btn" /> + style="?android:attr/buttonStyleTimePickerUp" /> <EditText android:id="@+id/timepicker_input" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:gravity="center" android:singleLine="true" - style="?android:attr/textAppearanceLargeInverse" - android:textColor="@android:color/primary_text_light" - android:textSize="30sp" - android:background="@drawable/timepicker_input" /> + style="?android:attr/timePickerInputStyle" + /> <com.android.internal.widget.NumberPickerButton android:id="@+id/decrement" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:background="@drawable/timepicker_down_btn" /> + style="?android:attr/buttonStyleTimePickerDown" /> </merge> diff --git a/core/res/res/layout/search_dropdown_item_2line.xml b/core/res/res/layout/search_dropdown_item_2line.xml new file mode 100644 index 0000000..05ca4fb --- /dev/null +++ b/core/res/res/layout/search_dropdown_item_2line.xml @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/assets/res/any/layout/simple_spinner_item.xml +** +** Copyright 2008, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:gravity="center_vertical" + android:baselineAligned="false" + > + + <TwoLineListItem + android:paddingTop="2dip" + android:paddingBottom="2dip" + android:layout_width="0dip" + android:layout_weight="1" + android:layout_height="wrap_content" + android:mode="twoLine" > + + <TextView + android:id="@android:id/text1" + style="?android:attr/dropDownItemStyle" + android:textAppearance="?android:attr/textAppearanceMediumInverse" + android:textColor="@android:color/primary_text_light" + android:singleLine="true" + android:layout_width="fill_parent" + android:layout_height="wrap_content" /> + + <TextView + android:id="@android:id/text2" + style="?android:attr/dropDownItemStyle" + android:textAppearance="?android:attr/textAppearanceSmallInverse" + android:textColor="@android:color/secondary_text_light" + android:singleLine="true" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_below="@android:id/text1" + android:layout_alignLeft="@android:id/text1" /> + + </TwoLineListItem> + +</LinearLayout> diff --git a/core/res/res/layout/select_dialog.xml b/core/res/res/layout/select_dialog.xml index 249b527..f9ae79a 100644 --- a/core/res/res/layout/select_dialog.xml +++ b/core/res/res/layout/select_dialog.xml @@ -29,6 +29,4 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginTop="5px" - android:cacheColorHint="@null" - android:divider="@android:drawable/divider_horizontal_bright" android:scrollbars="vertical" /> diff --git a/core/res/res/layout/select_dialog_item.xml b/core/res/res/layout/select_dialog_item.xml index 60a74a4..977e089 100644 --- a/core/res/res/layout/select_dialog_item.xml +++ b/core/res/res/layout/select_dialog_item.xml @@ -29,9 +29,9 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="@android:color/primary_text_light_disable_only" android:gravity="center_vertical" android:paddingLeft="14dip" android:paddingRight="15dip" android:ellipsize="marquee" + style="?android:attr/listItemTextViewStyle" /> diff --git a/core/res/res/layout/select_dialog_multichoice.xml b/core/res/res/layout/select_dialog_multichoice.xml index 55fc39b..5a5f317 100644 --- a/core/res/res/layout/select_dialog_multichoice.xml +++ b/core/res/res/layout/select_dialog_multichoice.xml @@ -24,7 +24,8 @@ android:gravity="center_vertical" android:paddingLeft="12dip" android:paddingRight="7dip" - android:checkMark="@android:drawable/btn_check" + android:checkMark="?android:attr/listChoiceIndicatorMultiple" android:ellipsize="marquee" + style="?android:attr/listItemTextViewStyle" /> diff --git a/core/res/res/layout/select_dialog_singlechoice.xml b/core/res/res/layout/select_dialog_singlechoice.xml index 220af64..a51774f 100644 --- a/core/res/res/layout/select_dialog_singlechoice.xml +++ b/core/res/res/layout/select_dialog_singlechoice.xml @@ -20,10 +20,10 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:textAppearance="?android:attr/textAppearanceLarge" - android:textColor="@android:color/primary_text_light_disable_only" android:gravity="center_vertical" android:paddingLeft="12dip" android:paddingRight="7dip" - android:checkMark="@android:drawable/btn_radio" + android:checkMark="?android:attr/listChoiceIndicatorSingle" android:ellipsize="marquee" + style="?android:attr/listItemTextViewStyle" /> diff --git a/core/res/res/layout/simple_list_item_1.xml b/core/res/res/layout/simple_list_item_1.xml index fe617ac..d720ce0 100644 --- a/core/res/res/layout/simple_list_item_1.xml +++ b/core/res/res/layout/simple_list_item_1.xml @@ -22,4 +22,5 @@ android:gravity="center_vertical" android:paddingLeft="6dip" android:minHeight="?android:attr/listPreferredItemHeight" + style="?android:attr/listItemTextViewStyle" /> diff --git a/core/res/res/layout/simple_list_item_2.xml b/core/res/res/layout/simple_list_item_2.xml index b5e2385..535fade 100644 --- a/core/res/res/layout/simple_list_item_2.xml +++ b/core/res/res/layout/simple_list_item_2.xml @@ -29,6 +29,7 @@ android:layout_marginLeft="6dip" android:layout_marginTop="6dip" android:textAppearance="?android:attr/textAppearanceLarge" + style="?android:attr/listItemTextViewStyle" /> <TextView android:id="@android:id/text2" @@ -37,6 +38,7 @@ android:layout_below="@android:id/text1" android:layout_alignLeft="@android:id/text1" android:textAppearance="?android:attr/textAppearanceSmall" + style="?android:attr/listItemTextViewStyle" /> </TwoLineListItem> diff --git a/core/res/res/layout/status_bar_expanded.xml b/core/res/res/layout/status_bar_expanded.xml index fd9d26e..34bb829 100644 --- a/core/res/res/layout/status_bar_expanded.xml +++ b/core/res/res/layout/status_bar_expanded.xml @@ -20,7 +20,6 @@ <com.android.server.status.ExpandedView xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" - android:background="@drawable/status_bar_background" android:focusable="true" android:descendantFocusability="afterDescendants"> @@ -67,12 +66,10 @@ android:layout_marginTop="4dp" android:layout_marginBottom="1dp" android:textSize="14sp" - android:textColor="#ff000000" android:text="@string/status_bar_clear_all_button" - style="?android:attr/buttonStyle" + style="?android:attr/buttonStyleSmall" android:paddingLeft="15dp" android:paddingRight="15dp" - android:background="@drawable/btn_default_small" /> </LinearLayout> diff --git a/core/res/res/layout/status_bar_latest_event.xml b/core/res/res/layout/status_bar_latest_event.xml index d524bb6..42ad37e 100644 --- a/core/res/res/layout/status_bar_latest_event.xml +++ b/core/res/res/layout/status_bar_latest_event.xml @@ -7,7 +7,6 @@ <com.android.server.status.LatestItemView android:id="@+id/content" android:layout_width="fill_parent" android:layout_height="64sp" - android:background="@drawable/status_bar_item_background" android:focusable="true" android:clickable="true" android:paddingRight="6sp" @@ -16,8 +15,8 @@ <View android:layout_width="fill_parent" - android:layout_height="1sp" - android:background="@drawable/divider_horizontal_bright" + android:layout_height="wrap_content" + android:background="?android:attr/listDivider" /> </LinearLayout> diff --git a/core/res/res/layout/status_bar_latest_event_content.xml b/core/res/res/layout/status_bar_latest_event_content.xml index eeb9d9d..d044db3 100644 --- a/core/res/res/layout/status_bar_latest_event_content.xml +++ b/core/res/res/layout/status_bar_latest_event_content.xml @@ -25,9 +25,8 @@ android:ellipsize="marquee" android:fadingEdge="horizontal" android:textStyle="bold" - android:textSize="18sp" android:paddingLeft="4dp" - android:textColor="#ff000000" /> + android:textAppearance="@style/TextAppearance.Medium" /> </LinearLayout> <LinearLayout android:layout_width="fill_parent" @@ -38,11 +37,10 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1" - android:textColor="#ff000000" android:singleLine="true" android:ellipsize="marquee" android:fadingEdge="horizontal" - android:textSize="14sp" + android:textAppearance="@style/TextAppearance.Small" android:paddingLeft="4dp" /> <TextView android:id="@+id/time" @@ -50,8 +48,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:singleLine="true" - android:textSize="14sp" android:paddingRight="5dp" - android:textColor="#ff000000" /> + android:textAppearance="@style/TextAppearance.Small" /> </LinearLayout> </LinearLayout> diff --git a/core/res/res/layout/tab_indicator.xml b/core/res/res/layout/tab_indicator.xml index 71e4001..34f0e3e 100644 --- a/core/res/res/layout/tab_indicator.xml +++ b/core/res/res/layout/tab_indicator.xml @@ -15,13 +15,9 @@ --> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="0dip" - android:layout_height="64dip" - android:layout_weight="1" - android:layout_marginLeft="-3dip" - android:layout_marginRight="-3dip" android:orientation="vertical" - android:background="@android:drawable/tab_indicator"> + style="?android:attr/tabIndicatorStyle" + > <ImageView android:id="@+id/icon" android:layout_width="wrap_content" @@ -34,7 +30,9 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerHorizontal="true" - style="?android:attr/tabWidgetStyle" + android:ellipsize="marquee" + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceTab" /> </RelativeLayout> diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 81da739..8ed1780 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -153,8 +153,25 @@ <!-- ToggleButton style. --> <attr name="buttonStyleToggle" format="reference" /> + + <!-- TimePicker Button styles. --> + <attr name="buttonStyleTimePickerUp" format="reference" /> + <attr name="buttonStyleTimePickerDown" format="reference" /> <!-- ============== --> + <!-- Tab styles --> + <!-- ============== --> + <eat-comment /> + + <!-- Style for the inflated indicator widget itself, not the text or + image views typically contained within. --> + <attr name="tabIndicatorStyle" format="reference" /> + + <!-- Style for the text label on the default indicator. Previously + was lumped with tabWidgetStyle. --> + <attr name="textAppearanceTab" format="reference" /> + + <!-- ============== --> <!-- Gallery styles --> <!-- ============== --> <eat-comment /> @@ -176,6 +193,8 @@ <attr name="listDivider" format="reference" /> <!-- TextView style for list separators. --> <attr name="listSeparatorTextViewStyle" format="reference" /> + <!-- TextView style for regular list items. --> + <attr name="listItemTextViewStyle" format="reference" /> <!-- The preferred left padding for an expandable list item (for child-specific layouts, use expandableListPreferredChildPaddingLeft). This takes into account the indicator that will be shown to next to the item. --> @@ -302,6 +321,21 @@ <attr name="windowNoDisplay" format="boolean" /> <!-- ============ --> + <!-- General Dialog theme --> + <!-- ============ --> + <eat-comment /> + <attr name="dialogTheme" format="reference" /> + <attr name="alertDialogTheme" format="reference" /> + <attr name="searchDialogTheme" format="reference" /> + + <!-- ============ --> + <!-- Menu theme --> + <!-- ============ --> + <eat-comment /> + <attr name="iconMenuTheme" format="reference" /> + <attr name="expandedMenuTheme" format="reference" /> + + <!-- ============ --> <!-- Alert Dialog styles --> <!-- ============ --> <eat-comment /> @@ -338,6 +372,8 @@ <attr name="dropDownListViewStyle" format="reference" /> <!-- Default EditText style. --> <attr name="editTextStyle" format="reference" /> + <!-- Default TimePicker Input style. --> + <attr name="timePickerInputStyle" format="reference" /> <!-- Default ExpandableListView style. --> <attr name="expandableListViewStyle" format="reference" /> <!-- Default Gallery style. --> @@ -446,6 +482,17 @@ <attr name="ringtonePreferenceStyle" format="reference" /> <!-- The preference layout that has the child/tabbed effect. --> <attr name="preferenceLayoutChild" format="reference" /> + + <!-- =================== --> + <!-- DigitalClock attributes --> + <!-- =================== --> + <attr name="menuItemBackground" format="reference" /> + + <!-- =================== --> + <!-- status bar related attributes --> + <!-- =================== --> + <attr name="com_android_server_status_expandedView" format="reference" /> + <attr name="com_android_server_status_latestItemView" format="reference" /> </declare-styleable> @@ -1722,6 +1769,8 @@ <!-- When set to false, the ListView will not draw the divider before each footer view. The default value is true. --> <attr name="footerDividersEnabled" format="boolean" /> + <!-- Drawable used to colorize an item's background in the list. --> + <attr name="listItemBackground" format="color|reference" /> </declare-styleable> <declare-styleable name="MenuView"> <!-- Default appearance of menu item text. --> @@ -1842,6 +1891,10 @@ <attr name="layout_span" format="integer" /> </declare-styleable> <declare-styleable name="TabWidget"> + <!-- Bottom-left tab strip. --> + <attr name="bottomLeftStrip" format="reference" /> + <!-- Bottom-right tab strip. --> + <attr name="bottomRightStrip" format="reference" /> </declare-styleable> <declare-styleable name="TextAppearance"> <!-- Text color. --> @@ -2330,6 +2383,8 @@ value is false. See {@link android.graphics.drawable.Drawable#setVisible}. --> <attr name="visible" format="boolean" /> + <!-- Set a tinting color --> + <attr name="tint" /> </declare-styleable> <declare-styleable name="StateListDrawable"> @@ -2447,6 +2502,7 @@ <attr name="bottom" /> <attr name="drawable" /> <attr name="id" /> + <attr name="tint" /> </declare-styleable> <declare-styleable name="LevelListDrawableItem"> diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index 5eb1c8e..549f76f 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -1205,4 +1205,31 @@ <public type="attr" name="wallpaperAuthor" /> <public type="attr" name="wallpaperDescription" /> + +<!-- =============================================================== + Resources added to support dynamic theming. + =============================================================== --> + <eat-comment /> + + <public type="style" name="Widget.TabIndicator" /> + <public type="style" name="Widget.EditText.TimePicker" /> + + <public type="attr" name="listItemBackground" /> + + <public type="attr" name="dialogTheme" /> + <public type="attr" name="alertDialogTheme" /> + <public type="attr" name="searchDialogTheme" /> + <public type="attr" name="listItemTextViewStyle" /> + <public type="attr" name="bottomLeftStrip" /> + <public type="attr" name="bottomRightStrip" /> + <public type="attr" name="tabIndicatorStyle" /> + <public type="attr" name="textAppearanceTab" /> + + <public type="attr" name="timePickerInputStyle" /> + <public type="attr" name="buttonStyleTimePickerUp" /> + <public type="attr" name="buttonStyleTimePickerDown" /> + <public type="attr" name="menuItemBackground" /> + <public type="attr" name="expandedMenuTheme" /> + <public type="attr" name="iconMenuTheme" /> + </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index de30fe7..2ad1b22 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -2100,4 +2100,9 @@ <string name="l2tp_vpn_description">Layer 2 Tunneling Protocol</string> <string name="l2tp_ipsec_psk_vpn_description">Pre-shared key based L2TP/IPSec VPN</string> <string name="l2tp_ipsec_crt_vpn_description">Certificate based L2TP/IPSec VPN</string> + + <string name="ringtone_button_done">Done</string> + <string name="ringtone_button_cancel">Cancel</string> + <string name="ringtone_header">Select Ringtone</string> + <string name="buy_ringtones">Buy Ringtones</string> </resources> diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml index 6e38138..b13a21a 100644 --- a/core/res/res/values/styles.xml +++ b/core/res/res/values/styles.xml @@ -192,6 +192,14 @@ <item name="android:textColor">?android:attr/textColorPrimary</item> </style> + <style name="com_android_server_status_ExpandedView"> + <item name="android:background">@android:drawable/status_bar_background</item> + </style> + + <style name="com_android_server_status_LatestItemView"> + <item name="android:background">@android:drawable/list_selector_background</item> + </style> + <!-- Widget Styles --> <style name="Widget"> @@ -378,6 +386,13 @@ <item name="android:background">@android:drawable/light_header_dither</item> </style> + <style name="Widget.TextView.ListItem"> + </style> + + <style name="Widget.TextView.ListItem.White"> + <item name="android:textColor">@android:color/primary_text_light</item> + </style> + <style name="Widget.EditText"> <item name="android:focusable">true</item> <item name="android:focusableInTouchMode">true</item> @@ -388,12 +403,29 @@ <item name="android:gravity">center_vertical</item> </style> + <style name="Widget.EditText.TimePicker"> + <item name="android:background">@android:drawable/timepicker_input</item> + <item name="android:textAppearance">?android:attr/textAppearanceLargeInverse</item> + <item name="android:gravity">center</item> + <item name="android:textSize">30sp</item> + </style> + <style name="Widget.ExpandableListView" parent="Widget.ListView"> <item name="android:groupIndicator">@android:drawable/expander_group</item> <item name="android:indicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item> <item name="android:indicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item> <item name="android:childDivider">@android:drawable/divider_horizontal_dark_opaque</item> </style> + + <style name="Widget.ExpandableListView.White" parent="Widget.ListView.White"> + <item name="android:groupIndicator">@android:drawable/expander_group</item> + <item name="android:indicatorLeft">?android:attr/expandableListPreferredItemIndicatorLeft</item> + <item name="android:indicatorRight">?android:attr/expandableListPreferredItemIndicatorRight</item> + <item name="android:childDivider">@android:drawable/divider_horizontal_bright</item> + </style> + + <style name="Widget.ExpandableListView.Dialog" parent="Widget.ExpandableListView.White"> + </style> <style name="Widget.ImageWell"> <item name="android:background">@android:drawable/panel_picture_frame_background</item> @@ -413,6 +445,7 @@ <item name="android:background">@android:drawable/edit_text</item> <item name="android:completionHintView">@android:layout/simple_dropdown_hint</item> <item name="android:textAppearance">?android:attr/textAppearanceMediumInverse</item> + <item name="android:textColor">@android:color/primary_text_light</item> <item name="android:gravity">center_vertical</item> <item name="android:completionThreshold">2</item> <item name="android:dropDownSelector">@android:drawable/list_selector_background</item> @@ -469,6 +502,15 @@ <item name="android:divider">@android:drawable/divider_horizontal_bright_opaque</item> </style> + <style name="Widget.ListView.Dialog" parent="Widget.ListView.White"> + <item name="textColorPrimary">@android:color/primary_text_light</item> + <item name="textColorSecondary">@android:color/secondary_text_light</item> + <item name="textColorTertiary">@android:color/tertiary_text_light</item> + <item name="textColorPrimaryInverse">@android:color/primary_text_dark</item> + <item name="textColorSecondaryInverse">@android:color/secondary_text_dark</item> + <item name="textColorTertiaryInverse">@android:color/tertiary_text_dark</item> + </style> + <style name="Widget.ListView.DropDown"> <item name="android:cacheColorHint">@null</item> <item name="android:divider">@android:drawable/divider_horizontal_bright_opaque</item> @@ -493,9 +535,17 @@ </style> <style name="Widget.TabWidget"> - <item name="android:textAppearance">@style/TextAppearance.Widget.TabWidget</item> - <item name="ellipsize">marquee</item> - <item name="singleLine">true</item> + <item name="android:bottomLeftStrip">@android:drawable/tab_bottom_left</item> + <item name="android:bottomRightStrip">@android:drawable/tab_bottom_right</item> + </style> + + <style name="Widget.TabIndicator"> + <item name="android:layout_width">0px</item> + <item name="android:layout_weight">1</item> + <item name="android:layout_height">64dp</item> + <item name="android:layout_marginLeft">-3dp</item> + <item name="android:layout_marginRight">-3dp</item> + <item name="android:background">@android:drawable/tab_indicator</item> </style> <style name="Widget.Gallery"> @@ -798,4 +848,13 @@ <item name="android:paddingBottom">1dip</item> <item name="android:background">@android:drawable/bottom_bar</item> </style> + + <style name="TimePickerUpButton"> + <item name="android:background">@android:drawable/timepicker_up_btn</item> + </style> + + <style name="TimePickerDownButton"> + <item name="android:background">@android:drawable/timepicker_down_btn</item> + </style> + </resources> diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml index 17ea66a..47f597f 100644 --- a/core/res/res/values/themes.xml +++ b/core/res/res/values/themes.xml @@ -26,7 +26,7 @@ of it a dark color. --> <style name="Theme"> - + <item name="colorForeground">@android:color/bright_foreground_dark</item> <item name="colorForegroundInverse">@android:color/bright_foreground_dark_inverse</item> <item name="colorBackground">@android:color/background_dark</item> @@ -77,6 +77,9 @@ <item name="buttonStyleInset">@android:style/Widget.Button.Inset</item> <item name="buttonStyleToggle">@android:style/Widget.Button.Toggle</item> + + <item name="buttonStyleTimePickerUp">@android:style/TimePickerUpButton</item> + <item name="buttonStyleTimePickerDown">@android:style/TimePickerDownButton</item> <!-- List attributes --> <item name="listPreferredItemHeight">64dip</item> @@ -84,6 +87,7 @@ <item name="searchResultListItemHeight">58dip</item> <item name="listDivider">@drawable/divider_horizontal_dark</item> <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator</item> + <item name="listItemTextViewStyle">@android:style/Widget.TextView.ListItem</item> <item name="listChoiceIndicatorSingle">@android:drawable/btn_radio</item> <item name="listChoiceIndicatorMultiple">@android:drawable/btn_check</item> @@ -117,7 +121,14 @@ <item name="android:windowSoftInputMode">stateUnspecified|adjustUnspecified</item> <!-- Dialog attributes --> + <item name="dialogTheme">@android:style/Theme.Dialog</item> + <item name="alertDialogTheme">@android:style/Theme.Dialog.Alert</item> <item name="alertDialogStyle">@android:style/AlertDialog</item> + <item name="searchDialogTheme">@android:style/Theme.SearchBar</item> + + <!-- Menu attributes --> + <item name="iconMenuTheme">@android:style/Theme.IconMenu</item> + <item name="expandedMenuTheme">@android:style/Theme.ExpandedMenu</item> <!-- Panel attributes --> <item name="panelBackground">@android:drawable/menu_background</item> @@ -135,12 +146,20 @@ <item name="scrollbarTrackHorizontal">@null</item> <item name="scrollbarTrackVertical">@null</item> + <!-- DigitalClock attributes --> + <item name="menuItemBackground">@android:drawable/menuitem_background</item> + + <!-- status bar related attributes --> + <item name="com_android_server_status_expandedView">@style/com_android_server_status_ExpandedView</item> + <item name="com_android_server_status_latestItemView">@style/com_android_server_status_LatestItemView</item> + <!-- Widget styles --> <item name="absListViewStyle">@android:style/Widget.AbsListView</item> <item name="autoCompleteTextViewStyle">@android:style/Widget.AutoCompleteTextView</item> <item name="checkboxStyle">@android:style/Widget.CompoundButton.CheckBox</item> <item name="dropDownListViewStyle">@android:style/Widget.ListView.DropDown</item> <item name="editTextStyle">@android:style/Widget.EditText</item> + <item name="timePickerInputStyle">@android:style/Widget.EditText.TimePicker</item> <item name="expandableListViewStyle">@android:style/Widget.ExpandableListView</item> <item name="galleryStyle">@android:style/Widget.Gallery</item> <item name="gestureOverlayViewStyle">@android:style/Widget.GestureOverlayView</item> @@ -196,6 +215,12 @@ <!-- Search widget styles --> <item name="searchWidgetCorpusItemBackground">@android:color/search_widget_corpus_item_background</item> + + <!-- Style for the inflated indicator widget itself, not the text or + image views typically contained within. --> + <item name="tabIndicatorStyle">@android:style/Widget.TabIndicator</item> + <item name="textAppearanceTab">@android:style/TextAppearance.Widget.TabWidget</item> + </style> <!-- Variant of the default (dark) theme with no title bar --> @@ -251,6 +276,7 @@ <item name="progressBarStyleInverse">@android:style/Widget.ProgressBar</item> <item name="progressBarStyleSmallInverse">@android:style/Widget.ProgressBar.Small</item> <item name="progressBarStyleLargeInverse">@android:style/Widget.ProgressBar.Large</item> + </style> <!-- Variant of the light theme with no title bar --> @@ -421,6 +447,19 @@ <item name="windowTitleStyle">@android:style/DialogWindowTitle</item> <item name="windowIsFloating">true</item> <item name="windowContentOverlay">@null</item> + + <!-- Use light themes for various components. --> + <item name="listViewStyle">@android:style/Widget.ListView.Dialog</item> + <item name="expandableListViewStyle">@android:style/Widget.ExpandableListView.Dialog</item> + <item name="listDivider">@drawable/divider_horizontal_bright</item> + <item name="listSeparatorTextViewStyle">@android:style/Widget.TextView.ListSeparator.White</item> + <item name="listItemTextViewStyle">@android:style/Widget.TextView.ListItem.White</item> + <item name="colorBackground">@android:color/white</item> + <item name="cacheColorHint">@android:color/white</item> + + <item name="android:timePickerInputStyle">@android:style/Widget.EditText.TimePicker</item> + <item name="android:buttonStyleTimePickerUp">@android:style/TimePickerUpButton</item> + <item name="android:buttonStyleTimePickerDown">@android:style/TimePickerDownButton</item> </style> <!-- Default dark theme for panel windows. This removes all extraneous diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 7322e6c..5cf2f40 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -72,6 +72,11 @@ <group gid="diag" /> </permission> + <!-- Permissions to read DRM-protected theme resources. --> + <permission name="com.tmobile.permission.ACCESS_DRM_THEME" > + <group gid="theme_manager" /> + </permission> + <!-- ================================================================== --> <!-- ================================================================== --> <!-- ================================================================== --> diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index 6a7b2d1..3af7222 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -811,6 +811,11 @@ public abstract class Drawable { TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); + int tint = a.getColor(com.android.internal.R.styleable.Drawable_tint, 0); + if (tint != 0) { + setColorFilter(tint, PorterDuff.Mode.SRC_ATOP); + } + a.recycle(); } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 91a2bc1..8f18047 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -783,8 +783,9 @@ public class GradientDrawable extends Drawable { setCornerRadii(new float[] { topLeftRadius, topLeftRadius, topRightRadius, topRightRadius, - bottomLeftRadius, bottomLeftRadius, - bottomRightRadius, bottomRightRadius + bottomRightRadius, bottomRightRadius, + bottomLeftRadius, bottomLeftRadius + }); } a.recycle(); diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 389fd40..40770d8 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -41,6 +41,7 @@ import java.io.IOException; * @attr ref android.R.styleable#LayerDrawableItem_bottom * @attr ref android.R.styleable#LayerDrawableItem_drawable * @attr ref android.R.styleable#LayerDrawableItem_id + * @attr ref android.R.styleable#LayerDrawableItem_tint */ public class LayerDrawable extends Drawable implements Drawable.Callback { LayerState mLayerState; @@ -136,6 +137,12 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { com.android.internal.R.styleable.LayerDrawableItem_drawable, 0); int id = a.getResourceId(com.android.internal.R.styleable.LayerDrawableItem_id, View.NO_ID); + int tint = a.getColor(com.android.internal.R.styleable.LayerDrawableItem_tint, 0); + ColorFilter colorFilter = null; + if (tint != 0) { + colorFilter = new PorterDuffColorFilter(tint, PorterDuff.Mode.SRC_ATOP); + android.util.Log.d("Themes", "Setting tint=" + tint + " for drawable res=" + drawableRes + " (colorFilter=" + colorFilter + ")"); + } a.recycle(); @@ -152,8 +159,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } dr = Drawable.createFromXmlInner(r, parser, attrs); } - - addLayer(dr, id, left, top, right, bottom); + + addLayer(dr, id, left, top, right, bottom, colorFilter); } ensurePadding(); @@ -169,8 +176,10 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param top The top padding of the new layer. * @param right The right padding of the new layer. * @param bottom The bottom padding of the new layer. + * @param colorFilter Optional color filter for the new layer. */ - private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom) { + private void addLayer(Drawable layer, int id, int left, int top, int right, int bottom, + ColorFilter colorFilter) { final LayerState st = mLayerState; int N = st.mChildren != null ? st.mChildren.length : 0; int i = st.mNum; @@ -192,6 +201,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { childDrawable.mInsetT = top; childDrawable.mInsetR = right; childDrawable.mInsetB = bottom; + childDrawable.mColorFilter = colorFilter; st.mNum++; layer.setCallback(this); @@ -317,6 +327,11 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { final ChildDrawable[] array = mLayerState.mChildren; final int N = mLayerState.mNum; for (int i=0; i<N; i++) { + if (array[i].mColorFilter != null) { + android.util.Log.d("Themes", "Applying colorFilter=" + + array[i].mColorFilter + " to " + array[i].mDrawable); + array[i].mDrawable.setColorFilter(array[i].mColorFilter); + } array[i].mDrawable.draw(canvas); } } @@ -545,6 +560,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { public Drawable mDrawable; public int mInsetL, mInsetT, mInsetR, mInsetB; public int mId; + public ColorFilter mColorFilter; } static class LayerState extends ConstantState { @@ -588,6 +604,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { r.mInsetR = or.mInsetR; r.mInsetB = or.mInsetB; r.mId = or.mId; + r.mColorFilter = or.mColorFilter; } mHaveOpacity = orig.mHaveOpacity; diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h index d8994e0..a609230 100644 --- a/include/utils/AssetManager.h +++ b/include/utils/AssetManager.h @@ -61,8 +61,8 @@ public: virtual ~AssetManager(void); static int32_t getGlobalCount(); - - /* + + /* * Add a new source for assets. This can be called multiple times to * look in multiple places for assets. It can be either a directory (for * finding assets as raw files on the disk) or a ZIP file. This newly @@ -75,13 +75,13 @@ public: */ bool addAssetPath(const String8& path, void** cookie); - /* + /* * Convenience for adding the standard system assets. Uses the * ANDROID_ROOT environment variable to find them. */ bool addDefaultAssets(); - /* + /* * Iterate over the asset paths in this manager. (Previously * added via addAssetPath() and addDefaultAssets().) On first call, * 'cookie' must be NULL, resulting in the first cookie being returned. @@ -90,7 +90,7 @@ public: */ void* nextAssetPath(void* cookie) const; - /* + /* * Return an asset path in the manager. 'which' must be between 0 and * countAssetPaths(). */ @@ -172,7 +172,7 @@ public: */ FileType getFileType(const char* fileName); - /* + /* * Return the complete resource table to find things in the package. */ const ResTable& getResources(bool required = true) const; @@ -191,12 +191,23 @@ public: * the current data. */ bool isUpToDate(); - + /** * Get the known locales for this asset manager object. */ void getLocales(Vector<String8>* locales) const; + /* + * Remove existing source for assets. It can be either a directory (for + * deleting assets as raw files on the disk) or a ZIP file. + * Also, updates the ResTable object to reflect the change. + * + * Returns "true" on success, "false" on failure. + */ + bool removeAssetPath(const String8 &packageName, const String8 &assetPath); + bool updateWithAssetPath(const String8& path, void** cookie); + void dumpRes(); + private: struct asset_path { @@ -204,6 +215,7 @@ private: FileType type; }; + void updateResTableFromAssetPath(ResTable *rt, const asset_path& ap, void *cookie) const; Asset* openInPathLocked(const char* fileName, AccessMode mode, const asset_path& path); Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode, @@ -255,7 +267,7 @@ private: ResTable* setResourceTable(ResTable* res); bool isUpToDate(); - + protected: ~SharedZip(); @@ -302,7 +314,7 @@ private: static String8 getPathName(const char* path); bool isUpToDate(); - + private: void closeZip(int idx); diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index 49145e8..a9dddaa 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -1733,6 +1733,7 @@ public: status_t applyStyle(uint32_t resID, bool force=false); status_t setTo(const Theme& other); + void setAttributeValue(uint32_t attribute, uint32_t value); /** * Retrieve a value in the theme. If the theme defines this @@ -1885,9 +1886,12 @@ public: void getLocales(Vector<String8>* locales) const; + void removeAssetsByCookie(const String8 &packageName, void* cookie); + #ifndef HAVE_ANDROID_OS void print(bool inclValues) const; #endif + void dump() const; private: struct Header; diff --git a/include/utils/ZipEntry.h b/include/utils/ZipEntry.h new file mode 100644 index 0000000..7f721b4 --- /dev/null +++ b/include/utils/ZipEntry.h @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> + +namespace android { + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace android + +#endif // __LIBS_ZIPENTRY_H diff --git a/include/utils/ZipFile.h b/include/utils/ZipFile.h new file mode 100644 index 0000000..dbbd072 --- /dev/null +++ b/include/utils/ZipFile.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include <utils/Vector.h> +#include <utils/Errors.h> +#include <stdio.h> + +#include "ZipEntry.h" + +namespace android { + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + typedef enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least <uncompressed len> bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + Vector<ZipEntry*> mEntries; +}; + +}; // namespace android + +#endif // __LIBS_ZIPFILE_H diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 59409a2..1e3c190 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -41,6 +41,8 @@ commonSources:= \ ZipFileCRO.cpp \ ZipFileRO.cpp \ ZipUtils.cpp \ + ../../tools/aapt/ZipFile.cpp \ + ../../tools/aapt/ZipEntry.cpp \ misc.cpp diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp index 5a05e6a..058c76c 100644 --- a/libs/utils/AssetManager.cpp +++ b/libs/utils/AssetManager.cpp @@ -122,7 +122,7 @@ bool AssetManager::addAssetPath(const String8& path, void** cookie) return true; } } - + LOGV("In %p Asset %s path: %s", this, ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); @@ -185,7 +185,7 @@ void AssetManager::setLocaleLocked(const char* locale) delete[] mLocale; } mLocale = strdupNew(locale); - + updateResourceParamsLocked(); } @@ -392,79 +392,87 @@ const ResTable* AssetManager::getResTable(bool required) const if (mCacheMode != CACHE_OFF && !mCacheValid) const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); - const size_t N = mAssetPaths.size(); - for (size_t i=0; i<N; i++) { - Asset* ass = NULL; - ResTable* sharedRes = NULL; - bool shared = true; - const asset_path& ap = mAssetPaths.itemAt(i); - LOGV("Looking for resource asset in '%s'\n", ap.path.string()); - if (ap.type != kFileTypeDirectory) { - if (i == 0) { - // The first item is typically the framework resources, - // which we want to avoid parsing every time. - sharedRes = const_cast<AssetManager*>(this)-> - mZipSet.getZipResourceTable(ap.path); - } - if (sharedRes == NULL) { + mResources = rt = new ResTable(); + + if (rt) { + const size_t N = mAssetPaths.size(); + for (size_t i=0; i<N; i++) { + const asset_path& ap = mAssetPaths.itemAt(i); + updateResTableFromAssetPath(rt, ap, (void*)(i+1)); + } + } + + if (required && !rt) LOGW("Unable to find resources file resources.arsc"); + if (!rt) { + mResources = rt = new ResTable(); + } + + return rt; +} + +void AssetManager::updateResTableFromAssetPath(ResTable *rt, const asset_path& ap, void *cookie) const +{ + Asset* ass = NULL; + ResTable* sharedRes = NULL; + bool shared = true; + size_t cookiePos = (size_t)cookie; + LOGV("Looking for resource asset in '%s'\n", ap.path.string()); + if (ap.type != kFileTypeDirectory) { + if (cookiePos == 1) { + // The first item is typically the framework resources, + // which we want to avoid parsing every time. + sharedRes = const_cast<AssetManager*>(this)-> + mZipSet.getZipResourceTable(ap.path); + } + if (sharedRes == NULL) { + ass = const_cast<AssetManager*>(this)-> + mZipSet.getZipResourceTableAsset(ap.path); + if (ass == NULL) { + LOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> - mZipSet.getZipResourceTableAsset(ap.path); - if (ass == NULL) { - LOGV("loading resource table %s\n", ap.path.string()); + openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, + ap); + if (ass != NULL && ass != kExcludedAsset) { ass = const_cast<AssetManager*>(this)-> - openNonAssetInPathLocked("resources.arsc", - Asset::ACCESS_BUFFER, - ap); - if (ass != NULL && ass != kExcludedAsset) { - ass = const_cast<AssetManager*>(this)-> - mZipSet.setZipResourceTableAsset(ap.path, ass); - } - } - - if (i == 0 && ass != NULL) { - // If this is the first resource table in the asset - // manager, then we are going to cache it so that we - // can quickly copy it out for others. - LOGV("Creating shared resources for %s", ap.path.string()); - sharedRes = new ResTable(); - sharedRes->add(ass, (void*)(i+1), false); - sharedRes = const_cast<AssetManager*>(this)-> - mZipSet.setZipResourceTable(ap.path, sharedRes); + mZipSet.setZipResourceTableAsset(ap.path, ass); } } - } else { - LOGV("loading resource table %s\n", ap.path.string()); - Asset* ass = const_cast<AssetManager*>(this)-> - openNonAssetInPathLocked("resources.arsc", - Asset::ACCESS_BUFFER, - ap); - shared = false; - } - if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { - if (rt == NULL) { - mResources = rt = new ResTable(); - updateResourceParamsLocked(); - } - LOGV("Installing resource asset %p in to table %p\n", ass, mResources); - if (sharedRes != NULL) { - LOGV("Copying existing resources for %s", ap.path.string()); - rt->add(sharedRes); - } else { - LOGV("Parsing resources for %s", ap.path.string()); - rt->add(ass, (void*)(i+1), !shared); - } - if (!shared) { - delete ass; + if (cookiePos == 0 && ass != NULL) { + // If this is the first resource table in the asset + // manager, then we are going to cache it so that we + // can quickly copy it out for others. + LOGV("Creating shared resources for %s", ap.path.string()); + sharedRes = new ResTable(); + sharedRes->add(ass, cookie, false); + sharedRes = const_cast<AssetManager*>(this)-> + mZipSet.setZipResourceTable(ap.path, sharedRes); } } + } else { + LOGV("loading resource table %s\n", ap.path.string()); + Asset* ass = const_cast<AssetManager*>(this)-> + openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, + ap); + shared = false; } + if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { + updateResourceParamsLocked(); + LOGV("Installing resource asset %p in to table %p\n", ass, mResources); + if (sharedRes != NULL) { + LOGV("Copying existing resources for %s", ap.path.string()); + rt->add(sharedRes); + } else { + LOGV("Parsing resources for %s", ap.path.string()); + rt->add(ass, cookie, !shared); + } - if (required && !rt) LOGW("Unable to find resources file resources.arsc"); - if (!rt) { - mResources = rt = new ResTable(); + if (!shared) { + delete ass; + } } - return rt; } void AssetManager::updateResourceParamsLocked() const @@ -617,7 +625,7 @@ Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode m /* look at the filesystem on disk */ String8 path(createPathNameLocked(ap, locale, vendor)); path.appendPath(fileName); - + String8 excludeName(path); excludeName.append(kExcludeExtension); if (::getFileType(excludeName.string()) != kFileTypeNonexistent) { @@ -625,28 +633,28 @@ Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode m //printf("+++ excluding '%s'\n", (const char*) excludeName); return kExcludedAsset; } - + pAsset = openAssetFromFileLocked(path, mode); - + if (pAsset == NULL) { /* try again, this time with ".gz" */ path.append(".gz"); pAsset = openAssetFromFileLocked(path, mode); } - + if (pAsset != NULL) pAsset->setAssetSource(path); } else { /* find in cache */ String8 path(createPathNameLocked(ap, locale, vendor)); path.appendPath(fileName); - + AssetDir::FileInfo tmpInfo; bool found = false; - + String8 excludeName(path); excludeName.append(kExcludeExtension); - + if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) { /* go no farther */ //printf("+++ Excluding '%s'\n", (const char*) excludeName); @@ -1465,7 +1473,7 @@ bool AssetManager::fncScanAndMergeDirLocked( // XXX This is broken -- the filename cache needs to hold the base // asset path separately from its filename. - + partialPath = createPathNameLocked(ap, locale, vendor); if (dirName[0] != '\0') { partialPath.appendPath(dirName); @@ -1759,3 +1767,58 @@ int AssetManager::ZipSet::getIndex(const String8& zip) const return mZipPath.size()-1; } +bool AssetManager::updateWithAssetPath(const String8& path, void** cookie) +{ + bool res = addAssetPath(path, cookie); + ResTable* rt = mResources; + if (res && rt != NULL && ((size_t)*cookie == mAssetPaths.size())) { + AutoMutex _l(mLock); + const asset_path& ap = mAssetPaths.itemAt((size_t)*cookie - 1); + updateResTableFromAssetPath(rt, ap, *cookie); + } + return res; +} + +bool AssetManager::removeAssetPath(const String8 &packageName, const String8 &assetPath) +{ + AutoMutex _l(mLock); + + String8 realPath(assetPath); + if (kAppZipName) { + realPath.appendPath(kAppZipName); + } + + // Check if the path exists. + size_t cookie = 0; + for (size_t i = 0; i < mAssetPaths.size(); i++) { + if (strcmp(mAssetPaths[i].path, realPath) == 0) { + mAssetPaths.removeAt(i); + cookie = i + 1; + break; + } + } + + if (cookie == 0) { + return false; + } + + ResTable* rt = mResources; + if (rt == NULL) { + LOGV("ResTable must not be NULL"); + return false; + } + + rt->removeAssetsByCookie(packageName, (void *)cookie); + + return true; +} + +void AssetManager::dumpRes() +{ + ResTable* rt = mResources; + if (rt == NULL) { + fprintf(stderr, "ResTable must not be NULL"); + return; + } + rt->dump(); +} diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 872b2bc..5725ffa 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -62,6 +62,8 @@ namespace android { #endif #endif +#define PACKAGE_RESOURCE_ID_TO_DUMP 10 + static void printToLogFunc(void* cookie, const char* txt) { LOGV("%s", txt); @@ -1304,6 +1306,29 @@ ResTable::Theme::package_info* ResTable::Theme::copy_package(package_info* pi) return newpi; } +void ResTable::Theme::setAttributeValue(uint32_t attrID, uint32_t value) +{ + const ssize_t p = mTable.getResourcePackageIndex(attrID); + const uint32_t t = Res_GETTYPE(attrID); + const uint32_t e = Res_GETENTRY(attrID); + + TABLE_NOISY(LOGV("Looking up attr 0x%08x in theme %p", attrID, this)); + + if (p >= 0) { + const package_info* const pi = mPackages[p]; + if (pi != NULL) { + if (t < pi->numTypes) { + const type_info& ti = pi->types[t]; + if (e < ti.numEntries) { + theme_entry& te = ti.entries[e]; + te.value.data = value; + te.value.dataType = Res_value::TYPE_FIRST_INT; + } + } + } + } +} + status_t ResTable::Theme::applyStyle(uint32_t resID, bool force) { const bag_entry* bag; @@ -3889,9 +3914,61 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return NO_ERROR; } -#ifndef HAVE_ANDROID_OS +void ResTable::removeAssetsByCookie(const String8 &packageName, void* cookie) +{ + mError = NO_ERROR; + + size_t N = mHeaders.size(); + for (size_t i = 0; i < N; i++) { + Header* header = mHeaders[i]; + if ((size_t)header->cookie == (size_t)cookie) { + if (header->ownedData != NULL) { + free(header->ownedData); + } + mHeaders.removeAt(i); + break; + } + } + size_t pgCount = mPackageGroups.size(); + for (size_t pgIndex = 0; pgIndex < pgCount; pgIndex++) { + PackageGroup* pg = mPackageGroups[pgIndex]; + + size_t pkgCount = pg->packages.size(); + size_t index = pkgCount; + for (size_t pkgIndex = 0; pkgIndex < pkgCount; pkgIndex++) { + const Package* pkg = pg->packages[pkgIndex]; + if (String8(String16(pkg->package->name)).compare(packageName) == 0) { + index = pkgIndex; + LOGV("Delete Package %d id=%d name=%s\n", + (int)pkgIndex, pkg->package->id, + String8(String16(pkg->package->name)).string()); + break; + } + } + if (index < pkgCount) { + const Package* pkg = pg->packages[index]; + uint32_t id = dtohl(pkg->package->id); + if (id != 0 && id < 256) { + mPackageMap[id] = 0; + } + pg->packages.removeAt(index); + delete pkg; + if (pkgCount == 1) { + LOGV("Delete Package Group %d id=%d packageCount=%d name=%s\n", + (int)pgIndex, pg->id, (int)pg->packages.size(), + String8(pg->name).string()); + mPackageGroups.removeAt(pgIndex); + } + delete pg; + return; + } + } +} + #define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string()) +#ifndef HAVE_ANDROID_OS + #define CHAR16_ARRAY_EQ(constant, var, len) \ ((len == (sizeof(constant)/sizeof(constant[0]))) && (0 == memcmp((var), (constant), (len)))) @@ -4165,4 +4242,158 @@ void ResTable::print(bool inclValues) const #endif // HAVE_ANDROID_OS +void ResTable::dump() const +{ + LOGI("mError=0x%x (%s)\n", mError, strerror(mError)); +#if 0 + LOGI("mParams=%c%c-%c%c,\n", + mParams.language[0], mParams.language[1], + mParams.country[0], mParams.country[1]); +#endif + size_t pgCount = mPackageGroups.size(); + LOGI("Package Groups (%d)\n", (int)pgCount); + for (size_t pgIndex=0; pgIndex<pgCount; pgIndex++) { + const PackageGroup* pg = mPackageGroups[pgIndex]; + LOGI("Package Group %d id=%d packageCount=%d name=%s\n", + (int)pgIndex, pg->id, (int)pg->packages.size(), + String8(pg->name).string()); + + size_t pkgCount = pg->packages.size(); + for (size_t pkgIndex=0; pkgIndex<pkgCount; pkgIndex++) { + const Package* pkg = pg->packages[pkgIndex]; + if (pkg->package->id != PACKAGE_RESOURCE_ID_TO_DUMP) continue; // HACK! + size_t count = 0; + size_t typeCount = pkg->types.size(); + LOGI(" Package %d id=%d name=%s typeCount=%d\n", (int)pkgIndex, + pkg->package->id, String8(String16(pkg->package->name)).string(), + (int)typeCount); + for (size_t typeIndex=0; typeIndex<typeCount&&count<10; typeIndex++) { + const Type* typeConfigs = pkg->getType(typeIndex); + if (typeConfigs == NULL) { + LOGI(" type %d NULL\n", (int)typeIndex); + continue; + } + const size_t NTC = typeConfigs->configs.size(); + LOGI(" type %d configCount=%d entryCount=%d\n", + (int)typeIndex, (int)NTC, (int)typeConfigs->entryCount); + if (typeConfigs->typeSpecFlags != NULL) { + for (size_t entryIndex=0; entryIndex<typeConfigs->entryCount&&count<10; entryIndex++) { + uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + resource_name resName; + this->getResourceName(resID, &resName); + count++; + LOGI(" spec resource 0x%08x %s:%s/%s: flags=0x%08x\n", + resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + CHAR16_TO_CSTR(resName.type, resName.typeLen), + CHAR16_TO_CSTR(resName.name, resName.nameLen), + dtohl(typeConfigs->typeSpecFlags[entryIndex])); + } + } + for (size_t configIndex=0; configIndex<NTC&&count<10; configIndex++) { + const ResTable_type* type = typeConfigs->configs[configIndex]; + if ((((uint64_t)type)&0x3) != 0) { + LOGI(" NON-INTEGER ResTable_type ADDRESS: %p\n", type); + continue; + } + count++; + LOGI(" config %d lang=%c%c cnt=%c%c orien=%d touch=%d density=%d key=%d infl=%d nav=%d w=%d h=%d\n", + (int)configIndex, + type->config.language[0] ? type->config.language[0] : '-', + type->config.language[1] ? type->config.language[1] : '-', + type->config.country[0] ? type->config.country[0] : '-', + type->config.country[1] ? type->config.country[1] : '-', + type->config.orientation, + type->config.touchscreen, + dtohs(type->config.density), + type->config.keyboard, + type->config.inputFlags, + type->config.navigation, + dtohs(type->config.screenWidth), + dtohs(type->config.screenHeight)); + size_t entryCount = dtohl(type->entryCount); + uint32_t entriesStart = dtohl(type->entriesStart); + if ((entriesStart&0x3) != 0) { + LOGI(" NON-INTEGER ResTable_type entriesStart OFFSET: %p\n", (void*)entriesStart); + continue; + } + uint32_t typeSize = dtohl(type->header.size); + if ((typeSize&0x3) != 0) { + LOGI(" NON-INTEGER ResTable_type header.size: %p\n", (void*)typeSize); + continue; + } + for (size_t entryIndex=0; entryIndex<entryCount&&count<10; entryIndex++) { + const uint8_t* const end = ((const uint8_t*)type) + + dtohl(type->header.size); + const uint32_t* const eindex = (const uint32_t*) + (((const uint8_t*)type) + dtohs(type->header.headerSize)); + uint32_t thisOffset = dtohl(eindex[entryIndex]); + if (thisOffset == ResTable_type::NO_ENTRY) { + continue; + } + + uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) + | (0x00ff0000 & ((typeIndex+1)<<16)) + | (0x0000ffff & (entryIndex)); + resource_name resName; + this->getResourceName(resID, &resName); + count++; + LOGI(" resource 0x%08x %s:%s/%s: ", resID, + CHAR16_TO_CSTR(resName.package, resName.packageLen), + CHAR16_TO_CSTR(resName.type, resName.typeLen), + CHAR16_TO_CSTR(resName.name, resName.nameLen)); + if ((thisOffset&0x3) != 0) { + LOGI("NON-INTEGER OFFSET: %p\n", (void*)thisOffset); + continue; + } + if ((thisOffset+sizeof(ResTable_entry)) > typeSize) { + LOGI("OFFSET OUT OF BOUNDS: %p+%p (size is %p)\n", + (void*)entriesStart, (void*)thisOffset, + (void*)typeSize); + continue; + } + + const ResTable_entry* ent = (const ResTable_entry*) + (((const uint8_t*)type) + entriesStart + thisOffset); + if (((entriesStart + thisOffset)&0x3) != 0) { + LOGI("NON-INTEGER ResTable_entry OFFSET: %p\n", + (void*)(entriesStart + thisOffset)); + continue; + } + if ((dtohs(ent->flags)&ResTable_entry::FLAG_COMPLEX) != 0) { + LOGI("<bag>"); + } else { + uint16_t esize = dtohs(ent->size); + if ((esize&0x3) != 0) { + LOGI("NON-INTEGER ResTable_entry SIZE: %p\n", (void*)esize); + continue; + } + if ((thisOffset+esize) > typeSize) { + LOGI("ResTable_entry OUT OF BOUNDS: %p+%p+%p (size is %p)\n", + (void*)entriesStart, (void*)thisOffset, + (void*)esize, (void*)typeSize); + continue; + } + + const Res_value* value = (const Res_value*) + (((const uint8_t*)ent) + esize); + count++; + LOGI("t=0x%02x d=0x%08x (s=0x%04x r=0x%02x)", + (int)value->dataType, (int)dtohl(value->data), + (int)dtohs(value->size), (int)value->res0); + } + + if ((dtohs(ent->flags)&ResTable_entry::FLAG_PUBLIC) != 0) { + LOGI(" (PUBLIC)"); + } + LOGI("\n"); + } + } + } + } + } +} + } // namespace android diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index e80d8aa..0529db7 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.java @@ -20,8 +20,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.database.Cursor; -import android.media.AudioManager; -import android.media.MediaPlayer; import android.net.Uri; import android.provider.DrmStore; import android.provider.MediaStore; @@ -54,7 +52,7 @@ public class Ringtone { DrmStore.Audio.DATA, DrmStore.Audio.TITLE }; - + private MediaPlayer mAudio; private Uri mUri; @@ -110,6 +108,19 @@ public class Ringtone { if (mTitle != null) return mTitle; return mTitle = getTitle(context, mUri, true); } + + private static String stringForQuery(Cursor cursor) { + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getString(0); + } + } finally { + cursor.close(); + } + } + return null; + } private static String getTitle(Context context, Uri uri, boolean followSettingsUri) { Cursor cursor = null; @@ -129,14 +140,21 @@ public class Ringtone { .getString(com.android.internal.R.string.ringtone_default_with_actual, actualTitle); } + } else if (RingtoneManager.THEME_AUTHORITY.equals(authority)) { + Uri themes = Uri.parse("content://com.tmobile.thememanager.themes/themes"); + title = stringForQuery(res.query(themes, new String[] { "ringtone_name" }, + "ringtone_uri = ?", new String[] { uri.toString() }, null)); + if (title == null) { + title = stringForQuery(res.query(themes, new String[] { "notif_ringtone_name" }, + "notif_ringtone_uri = ?", new String[] { uri.toString() }, null)); + } } else { - if (DrmStore.AUTHORITY.equals(authority)) { cursor = res.query(uri, DRM_COLUMNS, null, null, null); } else if (MediaStore.AUTHORITY.equals(authority)) { cursor = res.query(uri, MEDIA_COLUMNS, null, null, null); } - + if (cursor != null && cursor.getCount() == 1) { cursor.moveToFirst(); return cursor.getString(2); diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 8481410..7878071 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.java @@ -21,8 +21,8 @@ import com.android.internal.database.SortCursor; import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Activity; -import android.content.ContentUris; import android.content.Context; +import android.content.Intent; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; @@ -53,6 +53,11 @@ public class RingtoneManager { private static final String TAG = "RingtoneManager"; + /** + * @hide + */ + public static final String ACTION_RINGTONE_CHANGED = "com.tmobile.intent.action.RINGTONE_CHANGED"; + // Make sure these are in sync with attrs.xml: // <attr name="ringtoneType"> @@ -112,6 +117,17 @@ public class RingtoneManager { "android.intent.extra.ringtone.SHOW_SILENT"; /** + * Given to the ringtone picker as a boolean. Whether to show an item for + * "Buy ringtones". + * + * @see #ACTION_RINGTONE_PICKER + * + * @hide + */ + public static final String EXTRA_RINGTONE_SHOW_BUY = + "com.tmobile.intent.extra.ringtone.SHOW_BUY"; + + /** * Given to the ringtone picker as a boolean. Whether to include DRM ringtones. */ public static final String EXTRA_RINGTONE_INCLUDE_DRM = @@ -175,26 +191,28 @@ public class RingtoneManager { public static final String EXTRA_RINGTONE_PICKED_URI = "android.intent.extra.ringtone.PICKED_URI"; + static final String THEME_AUTHORITY = "com.tmobile.thememanager.packageresources"; + // Make sure the column ordering and then ..._COLUMN_INDEX are in sync private static final String[] INTERNAL_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, - "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\"", + "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "/\" || " + MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE_KEY }; private static final String[] DRM_COLUMNS = new String[] { DrmStore.Audio._ID, DrmStore.Audio.TITLE, - "\"" + DrmStore.Audio.CONTENT_URI + "\"", + "\"" + DrmStore.Audio.CONTENT_URI + "/\" || " + DrmStore.Audio._ID, DrmStore.Audio.TITLE + " AS " + MediaStore.Audio.Media.TITLE_KEY }; private static final String[] MEDIA_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, - "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"", + "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/\" || " + MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE_KEY }; - + /** * The column index (in the cursor returned by {@link #getCursor()} for the * row ID. @@ -213,6 +231,14 @@ public class RingtoneManager { */ public static final int URI_COLUMN_INDEX = 2; + /** + * Full uri, rather than just the root stem of the Uri prior to + * concatenation of the id. + * + * @hide + */ + public static final int URI_FULL_COLUMN_INDEX = 3; + private Activity mActivity; private Context mContext; @@ -365,8 +391,10 @@ public class RingtoneManager { final Cursor internalCursor = getInternalRingtones(); final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null; final Cursor mediaCursor = getMediaRingtones(); - - return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor }, + final Cursor[] themeCursor = getThemeManagerRingtones(); + + return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor, + themeCursor[0], themeCursor[1] }, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); } @@ -401,12 +429,11 @@ public class RingtoneManager { return getUriFromCursor(cursor); } - + private static Uri getUriFromCursor(Cursor cursor) { - return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor - .getLong(ID_COLUMN_INDEX)); + return Uri.parse(cursor.getString(URI_FULL_COLUMN_INDEX)); } - + /** * Gets the position of a {@link Uri} within this {@link RingtoneManager}. * @@ -424,23 +451,12 @@ public class RingtoneManager { return -1; } - // Only create Uri objects when the actual URI changes - Uri currentUri = null; - String previousUriString = null; for (int i = 0; i < cursorCount; i++) { - String uriString = cursor.getString(URI_COLUMN_INDEX); - if (currentUri == null || !uriString.equals(previousUriString)) { - currentUri = Uri.parse(uriString); - } - - if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor - .getLong(ID_COLUMN_INDEX)))) { + if (ringtoneUri.equals(getUriFromCursor(cursor))) { return i; } cursor.move(1); - - previousUriString = uriString; } return -1; @@ -466,6 +482,18 @@ public class RingtoneManager { uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones()); } + if (uri == null) { + Cursor[] cursors = rm.getThemeManagerRingtones(); + if (cursors != null) { + for (Cursor cursor: cursors) { + uri = getValidRingtoneUriFromCursorAndClose(context, cursor); + if (uri != null) { + break; + } + } + } + } + return uri; } @@ -510,7 +538,34 @@ public class RingtoneManager { MediaStore.Audio.Media.DEFAULT_SORT_ORDER) : null; } - + + private String getThemeWhereClause(String uriColumn) { + String clause = uriColumn + " IS NOT NULL"; + if (mIncludeDrm) { + return clause; + } else { + return clause + " AND " + uriColumn + " NOT LIKE '%/assets/%locked%'"; + } + } + + private Cursor[] getThemeManagerRingtones() { + Cursor[] cursors = new Cursor[2]; + + if ((mType & TYPE_RINGTONE) != 0) { + cursors[0] = query(Uri.parse("content://com.tmobile.thememanager.themes/themes"), + new String[] { "_id", "ringtone_name AS title", "null", "ringtone_uri" }, + getThemeWhereClause("ringtone_uri"), null, "title"); + } + + if ((mType & TYPE_NOTIFICATION) != 0) { + cursors[1] = query(Uri.parse("content://com.tmobile.thememanager.themes/themes"), + new String[] { "_id", "notif_ringtone_name AS title", "null", "notif_ringtone_name" }, + getThemeWhereClause("notif_ringtone_uri"), null, "title"); + } + + return cursors; + } + private void setFilterColumnsList(int type) { List<String> columns = mFilterColumns; columns.clear(); @@ -642,6 +697,10 @@ public class RingtoneManager { if (setting == null) return; Settings.System.putString(context.getContentResolver(), setting, ringtoneUri != null ? ringtoneUri.toString() : null); + Intent intent = new Intent(); + intent.setAction(ACTION_RINGTONE_CHANGED); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, type); + context.sendBroadcast(intent); } private static String getSettingForType(int type) { diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 9877342..6e6d910 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -43,6 +43,7 @@ import android.util.Log; public class SettingsProvider extends ContentProvider { private static final String TAG = "SettingsProvider"; private static final boolean LOCAL_LOGV = false; + private static String EXTRA_AUTHORITY = "com.tmobile.thememanager.packageresources"; private static final String TABLE_FAVORITES = "favorites"; private static final String TABLE_OLD_FAVORITES = "old_favorites"; @@ -404,7 +405,8 @@ public class SettingsProvider extends ContentProvider { // Only proxy the openFile call to drm or media providers String authority = soundUri.getAuthority(); boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY); - if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) { + if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY) || + (EXTRA_AUTHORITY != null && authority.equals(EXTRA_AUTHORITY))) { if (isDrmAuthority) { try { diff --git a/services/java/Android.mk b/services/java/Android.mk index 5e912d6..b7e7ff8 100644 --- a/services/java/Android.mk +++ b/services/java/Android.mk @@ -10,6 +10,7 @@ LOCAL_SRC_FILES := \ LOCAL_MODULE:= services LOCAL_JAVA_LIBRARIES := android.policy +LOCAL_STATIC_JAVA_LIBRARIES := libtmobile-widget include $(BUILD_JAVA_LIBRARY) diff --git a/services/java/com/android/server/AppsLaunchFailureReceiver.java b/services/java/com/android/server/AppsLaunchFailureReceiver.java new file mode 100644 index 0000000..6220a18 --- /dev/null +++ b/services/java/com/android/server/AppsLaunchFailureReceiver.java @@ -0,0 +1,57 @@ +package com.android.server; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.CustomTheme; +import android.util.Log; +import android.app.ActivityManager; +import android.os.SystemClock; + +public class AppsLaunchFailureReceiver extends BroadcastReceiver { + + private static final int FAILURES_THRESHOLD = 5; + private static final int EXPIRATION_TIME_IN_MILLISECONDS = 30000; // 30 seconds + + private int mFailuresCount = 0; + private long mStartTime = 0; + + // This function implements the following logic. + // If after a theme was applied the number of application launch failures + // at any moment was equal to FAILURES_THRESHOLD + // in less than EXPIRATION_TIME_IN_MILLISECONDS + // the default theme is applied unconditionally. + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE)) { + long currentTime = SystemClock.uptimeMillis(); + if (currentTime - mStartTime > EXPIRATION_TIME_IN_MILLISECONDS) { + // reset both the count and the timer + mStartTime = currentTime; + mFailuresCount = 0; + } + if (mFailuresCount <= FAILURES_THRESHOLD) { + mFailuresCount++; + if (mFailuresCount == FAILURES_THRESHOLD) { + CustomTheme defaultTheme = CustomTheme.getDefault(); + ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + Configuration currentConfig = am.getConfiguration(); + currentConfig.customTheme = new CustomTheme( + defaultTheme.getThemeId(), + defaultTheme.getThemePackageName()); + am.updateConfiguration(currentConfig); + } + } + } else if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE_RESET)) { + mFailuresCount = 0; + mStartTime = SystemClock.uptimeMillis(); + } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) || + action.equals(Intent.ACTION_PACKAGE_REMOVED)) { + mFailuresCount = 0; + mStartTime = SystemClock.uptimeMillis(); + } + } + +} diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index a83459e..5b8f51e 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -128,6 +128,8 @@ class PackageManagerService extends IPackageManager.Stub { private static final int OBSERVER_EVENTS = REMOVE_EVENTS | ADD_EVENTS; + private static final int THEME_MAMANER_GUID = 1300; + static final int SCAN_MONITOR = 1<<0; static final int SCAN_NO_DEX = 1<<1; static final int SCAN_FORCE_DEX = 1<<2; @@ -354,7 +356,7 @@ class PackageManagerService extends IPackageManager.Stub { if (mSdkVersion <= 0) { Log.w(TAG, "**** ro.build.version.sdk not set!"); } - + mContext = context; mFactoryTest = factoryTest; mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); @@ -370,6 +372,9 @@ class PackageManagerService extends IPackageManager.Stub { MULTIPLE_APPLICATION_UIDS ? LOG_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLP("com.tmobile.thememanager", + THEME_MAMANER_GUID, + ApplicationInfo.FLAG_SYSTEM); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -689,7 +694,7 @@ class PackageManagerService extends IPackageManager.Stub { } Log.i(TAG, sb.toString()); } - + private void readPermissionsFromXml(File permFile) { FileReader permReader = null; try { @@ -1797,6 +1802,20 @@ class PackageManagerService extends IPackageManager.Stub { } return finalList; } + + public List<PackageInfo> getInstalledThemePackages() { + // Returns a list of theme APKs. + ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>(); + List<PackageInfo> installedPackagesList = getInstalledPackages(0); + Iterator<PackageInfo> i = installedPackagesList.iterator(); + while (i.hasNext()) { + final PackageInfo pi = i.next(); + if (pi != null && pi.isThemeApk) { + finalList.add(pi); + } + } + return finalList; + } public List<ApplicationInfo> getInstalledApplications(int flags) { ArrayList<ApplicationInfo> finalList = new ArrayList<ApplicationInfo>(); @@ -3548,7 +3567,7 @@ class PackageManagerService extends IPackageManager.Stub { } }; - private static final void sendPackageBroadcast(String action, String pkg, Bundle extras) { + private static final void sendPackageBroadcast(String action, String pkg, String intentCategory, Bundle extras) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { @@ -3558,6 +3577,9 @@ class PackageManagerService extends IPackageManager.Stub { intent.putExtras(extras); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + if (intentCategory != null) { + intent.addCategory(intentCategory); + } am.broadcastIntent( null, intent, null, null, 0, null, null, null, false, false); @@ -3578,6 +3600,7 @@ class PackageManagerService extends IPackageManager.Stub { int removedUid = -1; String addedPackage = null; int addedUid = -1; + String category = null; synchronized (mInstallLock) { String fullPathStr = null; @@ -3601,6 +3624,9 @@ class PackageManagerService extends IPackageManager.Stub { synchronized (mInstallLock) { PackageParser.Package p = mAppDirs.get(fullPathStr); if (p != null) { + if (p.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } removePackageLI(p, true); removedPackage = p.applicationInfo.packageName; removedUid = p.applicationInfo.uid; @@ -3624,7 +3650,10 @@ class PackageManagerService extends IPackageManager.Stub { addedUid = p.applicationInfo.uid; } } - } + if (p != null && p.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + } synchronized (mPackages) { mSettings.writeLP(); @@ -3635,12 +3664,12 @@ class PackageManagerService extends IPackageManager.Stub { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, extras); + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category, extras); } if (addedPackage != null) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, addedUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, extras); + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, category, extras); } } @@ -3688,19 +3717,24 @@ class PackageManagerService extends IPackageManager.Stub { // There appears to be a subtle deadlock condition if the sendPackageBroadcast // call appears in the synchronized block above. if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - res.removedInfo.sendBroadcast(false, true); + res.removedInfo.sendBroadcast(false, true, false); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } + String category = null; + if (res.pkg.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, res.pkg.applicationInfo.packageName, + category, extras); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - res.pkg.applicationInfo.packageName, + res.pkg.applicationInfo.packageName, category, extras); } } @@ -4046,6 +4080,21 @@ class PackageManagerService extends IPackageManager.Stub { res.returnCode = setPermissionsLI(pkgName, newPackage, destFilePath, destResourceFile, forwardLocked); + + if (pkg.mIsThemeApk) { + boolean isThemePackageDrmProtected = false; + int N = pkg.mThemeInfos.size(); + for (int i = 0; i < N; i++) { + if (pkg.mThemeInfos.get(i).isDrmProtected) { + isThemePackageDrmProtected = true; + break; + } + } + if (isThemePackageDrmProtected) { + splitThemePackage(destPackageFile); + } + } + if(res.returnCode != PackageManager.INSTALL_SUCCEEDED) { return; } else { @@ -4070,12 +4119,72 @@ class PackageManagerService extends IPackageManager.Stub { mSettings.writeLP(); } } - + private File getFwdLockedResource(String pkgName) { final String publicZipFileName = pkgName + ".zip"; return new File(mAppInstallDir, publicZipFileName); } + private void deleteLockedZipFileIfExists(String originalPackagePath) { + String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath); + File zipFile = new File(lockedZipFilePath); + if (zipFile.exists() && zipFile.isFile()) { + if (!zipFile.delete()) { + Log.w(TAG, "Couldn't delete locked zip file: " + originalPackagePath); + } + } + } + + private void splitThemePackage(File originalFile) { + final String originalPackagePath = originalFile.getPath(); + final String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath); + + try { + final List<String> drmProtectedEntries = new ArrayList<String>(); + final ZipFile privateZip = new ZipFile(originalFile.getPath()); + + final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries(); + while (privateZipEntries.hasMoreElements()) { + final ZipEntry zipEntry = privateZipEntries.nextElement(); + final String zipEntryName = zipEntry.getName(); + if (zipEntryName.startsWith("assets/") && zipEntryName.contains("/locked/")) { + drmProtectedEntries.add(zipEntryName); + } + } + privateZip.close(); + + String [] args = new String[0]; + args = drmProtectedEntries.toArray(args); + int code = mContext.getAssets().splitDrmProtectedThemePackage( + originalPackagePath, + lockedZipFilePath, + args); + if (code != 0) { + Log.e("PackageManagerService", + "splitDrmProtectedThemePackage returned = " + code); + } + code = FileUtils.setPermissions( + lockedZipFilePath, + 0640, + -1, + THEME_MAMANER_GUID); + if (code != 0) { + Log.e("PackageManagerService", + "Set permissions for " + lockedZipFilePath + " returned = " + code); + } + code = FileUtils.setPermissions( + originalPackagePath, + 0644, + -1, -1); + if (code != 0) { + Log.e("PackageManagerService", + "Set permissions for " + originalPackagePath + " returned = " + code); + } + } catch (IOException e) { + Log.e(TAG, "Failure to generate new zip files for theme"); + } + } + private File copyTempInstallFile(Uri pPackageURI, PackageInstalledInfo res) { File tmpPackageFile = createTempPackageFile(); @@ -4393,13 +4502,23 @@ class PackageManagerService extends IPackageManager.Stub { PackageRemovedInfo info = new PackageRemovedInfo(); boolean res; + synchronized (mPackages) { + PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + info.isThemeApk = p.mIsThemeApk; + if (info.isThemeApk && deleteCodeAndResources && + !info.isRemovedPackageSystemUpdate && sendBroadCast) { + deleteLockedZipFileIfExists(p.mPath); + } + } + } synchronized (mInstallLock) { res = deletePackageLI(packageName, deleteCodeAndResources, flags, info); } if(res && sendBroadCast) { boolean systemUpdate = info.isRemovedPackageSystemUpdate; - info.sendBroadcast(deleteCodeAndResources, systemUpdate); + info.sendBroadcast(deleteCodeAndResources, systemUpdate, true); // If the removed package was a system update, the old system packaged // was re-enabled; we need to broadcast this information @@ -4408,8 +4527,12 @@ class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, info.removedUid >= 0 ? info.removedUid : info.uid); extras.putBoolean(Intent.EXTRA_REPLACING, true); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, extras); + String category = null; + if (info.isThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, category, extras); + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, category, extras); } } return res; @@ -4420,8 +4543,10 @@ class PackageManagerService extends IPackageManager.Stub { int uid = -1; int removedUid = -1; boolean isRemovedPackageSystemUpdate = false; + boolean isThemeApk = false; - void sendBroadcast(boolean fullRemove, boolean replacing) { + void sendBroadcast(boolean fullRemove, boolean replacing, + boolean deleteLockedZipFileIfExists) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedUid >= 0 ? removedUid : uid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove); @@ -4429,10 +4554,14 @@ class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_REPLACING, true); } if (removedPackage != null) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, extras); + String category = null; + if (isThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category, extras); } if (removedUid >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras); + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, null, extras); } } } @@ -5100,7 +5229,7 @@ class PackageManagerService extends IPackageManager.Stub { extras.putString(Intent.EXTRA_CHANGED_COMPONENT_NAME, componentName); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras); + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, null, extras); } public String getInstallerPackageName(String packageName) { @@ -7241,7 +7370,7 @@ class PackageManagerService extends IPackageManager.Stub { "Error in package manager settings: package " + name + " has bad userId " + idStr + " at " + parser.getPositionDescription()); - }; + } if (su != null) { int outerDepth = parser.getDepth(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b8cf844..c4a357d 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -29,11 +29,16 @@ import android.content.ContentResolver; import android.content.ContentService; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.database.ContentObserver; import android.database.Cursor; import android.media.AudioService; -import android.os.*; +import android.os.Looper; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; import android.provider.Contacts.People; import android.provider.Settings; import android.server.BluetoothA2dpService; @@ -393,6 +398,15 @@ class ServerThread extends Thread { } catch (RemoteException e) { } + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE); + filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE_RESET); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addCategory(Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE); + filter.addDataScheme("package"); + context.registerReceiver(new AppsLaunchFailureReceiver(), filter); + // These are needed to propagate to the runnable below. final BatteryService batteryF = battery; final ConnectivityService connectivityF = connectivity; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index c3aeca4..b89c07d 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -25,6 +25,20 @@ import com.android.server.SystemServer; import com.android.server.Watchdog; import com.android.server.WindowManagerService; +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; + import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; @@ -65,6 +79,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; @@ -103,7 +118,14 @@ import android.view.View; import android.view.WindowManager; import android.view.WindowManagerPolicy; -import dalvik.system.Zygote; +import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.RuntimeInit; +import com.android.server.IntentResolver; +import com.android.server.ProcessMap; +import com.android.server.ProcessStats; +import com.android.server.SystemServer; +import com.android.server.Watchdog; +import com.android.server.WindowManagerService; import java.io.ByteArrayInputStream; import java.io.DataInputStream; @@ -113,15 +135,10 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.PrintWriter; + +import dalvik.system.Zygote; + import java.lang.IllegalStateException; -import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor { static final String TAG = "ActivityManager"; @@ -5696,10 +5713,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.state = ActivityState.PAUSED; completePauseLocked(); } else { - EventLog.writeEvent(LOG_AM_FAILED_TO_PAUSE_ACTIVITY, - System.identityHashCode(r), r.shortComponentName, - mPausingActivity != null - ? mPausingActivity.shortComponentName : "(none)"); + EventLog.writeEvent(LOG_AM_FAILED_TO_PAUSE_ACTIVITY, + System.identityHashCode(r), r.shortComponentName, + mPausingActivity != null + ? mPausingActivity.shortComponentName : "(none)"); } } } @@ -12780,6 +12797,17 @@ public final class ActivityManagerService extends ActivityManagerNative implemen !values.locale.equals(mConfiguration.locale), values.userSetLocale); } + +// if(values.themeResource != 0){ +// saveThemeResourceLocked(values.themeResource, (values.themeResource != mConfiguration.themeResource)); +// } + + if (values.customTheme != null) { + saveThemeResourceLocked(values.customTheme, + (!values.customTheme.equals(mConfiguration.customTheme))); + } else if (mConfiguration.customTheme != null) { + saveThemeResourceLocked(null, true); + } mConfiguration = newConfig; Log.i(TAG, "Config changed: " + newConfig); @@ -12997,6 +13025,30 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + private void saveThemeResourceLocked(CustomTheme customTheme, boolean isDiff){ + if(isDiff){ + String themeId; + String themePackage; + String resourcePath; + boolean hasParent; + boolean forceUpdate; + + if (customTheme != null) { + themeId = customTheme.getThemeId(); + themePackage = customTheme.getThemePackageName(); + } else { + themeId = null; + themePackage = ""; + resourcePath = null; + hasParent = false; + forceUpdate = false; + } + + SystemProperties.set(Configuration.THEME_ID_PERSISTENCE_PROPERTY, themeId); + SystemProperties.set(Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY, themePackage); + } + } + // ========================================================= // LIFETIME MANAGEMENT // ========================================================= diff --git a/services/java/com/android/server/status/ExpandedView.java b/services/java/com/android/server/status/ExpandedView.java index d0f14cb..afde479 100644 --- a/services/java/com/android/server/status/ExpandedView.java +++ b/services/java/com/android/server/status/ExpandedView.java @@ -19,7 +19,9 @@ public class ExpandedView extends LinearLayout { int mPrevHeight = -1; public ExpandedView(Context context, AttributeSet attrs) { - super(context, attrs); + super(context, attrs, com.tmobile.widget.Utils.resolveDefaultStyleAttr(context, + "com_android_server_status_expandedView", + com.android.internal.R.attr.com_android_server_status_expandedView)); mDisplay = ((WindowManager)context.getSystemService( Context.WINDOW_SERVICE)).getDefaultDisplay(); } diff --git a/services/java/com/android/server/status/LatestItemView.java b/services/java/com/android/server/status/LatestItemView.java index a47f6ad..27d1152 100644 --- a/services/java/com/android/server/status/LatestItemView.java +++ b/services/java/com/android/server/status/LatestItemView.java @@ -2,14 +2,15 @@ package com.android.server.status; import android.content.Context; import android.util.AttributeSet; -import android.util.Log; import android.view.MotionEvent; import android.widget.FrameLayout; public class LatestItemView extends FrameLayout { public LatestItemView(Context context, AttributeSet attrs) { - super(context, attrs); + super(context, attrs, com.tmobile.widget.Utils.resolveDefaultStyleAttr(context, + "com_android_server_status_latestItemView", + com.android.internal.R.attr.com_android_server_status_latestItemView)); } public boolean dispatchTouchEvent(MotionEvent ev) { diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java index 8d73904..516dbe7 100644 --- a/services/java/com/android/server/status/StatusBarService.java +++ b/services/java/com/android/server/status/StatusBarService.java @@ -18,6 +18,7 @@ package com.android.server.status; import com.android.internal.R; import com.android.internal.util.CharSequences; +import com.android.server.am.ActivityManagerService; import android.app.ActivityManagerNative; import android.app.Dialog; @@ -30,6 +31,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.content.res.CustomTheme; import android.graphics.PixelFormat; import android.graphics.drawable.Drawable; import android.net.Uri; @@ -41,6 +43,8 @@ import android.os.Message; import android.os.SystemClock; import android.provider.Telephony; import android.util.Log; +import android.util.TypedValue; +import android.view.ContextThemeWrapper; import android.view.Display; import android.view.Gravity; import android.view.KeyEvent; @@ -227,6 +231,10 @@ public class StatusBarService extends IStatusBar.Stub ArrayList<DisableRecord> mDisableRecords = new ArrayList<DisableRecord>(); int mDisabled = 0; + private String mThemeId; + private String mThemePackageName; + private Context mThemeContext; + /** * Construct the service, add the status bar view to the window manager */ @@ -242,6 +250,41 @@ public class StatusBarService extends IStatusBar.Stub mNotificationCallbacks = listener; } + private void createThemedExpandedView(Context context) { + try { + CustomTheme theme = ActivityManagerService.getDefault().getConfiguration().customTheme; + mThemeId = theme.getThemeId(); + mThemePackageName = theme.getThemePackageName(); + int styleId = CustomTheme.getStyleId(context, mThemePackageName, mThemeId); + if (styleId != -1) { + ContextThemeWrapper themeContext = new ContextThemeWrapper(context, styleId); + themeContext.useThemedResources(theme.getThemePackageName()); + mThemeContext = themeContext; + } else { + mThemeContext = context; + } + ExpandedView expanded = (ExpandedView)View.inflate(mThemeContext, + com.android.internal.R.layout.status_bar_expanded, null); + // Uncomment this line iff we want to make the dialog theme'able +// mExpandedDialog = new ExpandedDialog(themeContext); + expanded.mService = this; + mExpandedView = expanded; + mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); + mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); + mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); + mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems); + mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); + mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button); + mClearButton.setOnClickListener(mClearButtonListener); + mSpnLabel = (TextView)expanded.findViewById(R.id.spnLabel); + mPlmnLabel = (TextView)expanded.findViewById(R.id.plmnLabel); + mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); + mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout); + } catch (RemoteException e) { + Log.e("StatusBarService", "Failed to get configuration", e); + } + } + // ================================================================================ // Constructing the view // ================================================================================ @@ -250,9 +293,9 @@ public class StatusBarService extends IStatusBar.Stub mRightIconSlots = res.getStringArray(com.android.internal.R.array.status_bar_icon_order); mRightIcons = new StatusBarIcon[mRightIconSlots.length]; - ExpandedView expanded = (ExpandedView)View.inflate(context, - com.android.internal.R.layout.status_bar_expanded, null); - expanded.mService = this; + createThemedExpandedView(context); + // Comment out this line iff we want to make the dialog theme'able + mExpandedDialog = new ExpandedDialog(context); StatusBarView sb = (StatusBarView)View.inflate(context, com.android.internal.R.layout.status_bar, null); sb.mService = this; @@ -272,29 +315,12 @@ public class StatusBarService extends IStatusBar.Stub mTickerView = sb.findViewById(R.id.ticker); mDateView = (DateView)sb.findViewById(R.id.date); - mExpandedDialog = new ExpandedDialog(context); - mExpandedView = expanded; - mOngoingTitle = (TextView)expanded.findViewById(R.id.ongoingTitle); - mOngoingItems = (LinearLayout)expanded.findViewById(R.id.ongoingItems); - mLatestTitle = (TextView)expanded.findViewById(R.id.latestTitle); - mLatestItems = (LinearLayout)expanded.findViewById(R.id.latestItems); - mNoNotificationsTitle = (TextView)expanded.findViewById(R.id.noNotificationsTitle); - mClearButton = (TextView)expanded.findViewById(R.id.clear_all_button); - mClearButton.setOnClickListener(mClearButtonListener); - mSpnLabel = (TextView)expanded.findViewById(R.id.spnLabel); - mPlmnLabel = (TextView)expanded.findViewById(R.id.plmnLabel); - mScrollView = (ScrollView)expanded.findViewById(R.id.scroll); - mNotificationLinearLayout = expanded.findViewById(R.id.notificationLinearLayout); - - mOngoingTitle.setVisibility(View.GONE); - mLatestTitle.setVisibility(View.GONE); - mTicker = new MyTicker(context, sb); TickerView tickerView = (TickerView)sb.findViewById(R.id.tickerText); tickerView.mTicker = mTicker; - mTrackingView = (TrackingView)View.inflate(context, + mTrackingView = (TrackingView)View.inflate(mThemeContext, com.android.internal.R.layout.status_bar_tracking, null); mTrackingView.mService = this; mCloseView = (CloseDragHandle)mTrackingView.findViewById(R.id.close); @@ -824,8 +850,8 @@ public class StatusBarService extends IStatusBar.Stub } // create the row view - LayoutInflater inflater = (LayoutInflater)mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = + (LayoutInflater)mThemeContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View row = inflater.inflate(com.android.internal.R.layout.status_bar_latest_event, parent, false); // bind the click event to the content area @@ -1750,6 +1776,37 @@ public class StatusBarService extends IStatusBar.Stub * meantime, just update the things that we know change. */ void updateResources() { + try { + CustomTheme customTheme = ActivityManagerService.getDefault().getConfiguration().customTheme; + boolean themeChanged = + !customTheme.getThemeId().equalsIgnoreCase(mThemeId) || + !customTheme.getThemePackageName().equalsIgnoreCase(mThemePackageName); + if (themeChanged) { + createThemedExpandedView(mContext); + + mExpandedDialog.setContentView(mExpandedView, + new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT)); + // re-wire notifications if needed + NotificationViewList oldList = mNotificationData; + mNotificationData = new NotificationViewList(); + if (oldList.latestCount() > 0) { + for (int i = 0; i < oldList.latestCount(); i++) { + StatusBarNotification notification = oldList.getLatest(i); + updateNotificationView(notification, null); + } + } + if (oldList.ongoingCount() > 0) { + for (int i = 0; i < oldList.ongoingCount(); i++) { + StatusBarNotification notification = oldList.getOngoing(i); + updateNotificationView(notification, null); + } + } + setAreThereNotifications(); + } + } catch (RemoteException e) { + Log.e("StatusBarService", "Can't retrieve default theme", e); + } mClearButton.setText(mContext.getText(R.string.status_bar_clear_all_button)); mOngoingTitle.setText(mContext.getText(R.string.status_bar_ongoing_events_title)); mLatestTitle.setText(mContext.getText(R.string.status_bar_latest_events_title)); diff --git a/test-runner/android/test/mock/MockContext.java b/test-runner/android/test/mock/MockContext.java index 57b22f8..0f3e02c 100644 --- a/test-runner/android/test/mock/MockContext.java +++ b/test-runner/android/test/mock/MockContext.java @@ -85,7 +85,7 @@ public class MockContext extends Context { public void setTheme(int resid) { throw new UnsupportedOperationException(); } - + @Override public Resources.Theme getTheme() { throw new UnsupportedOperationException(); diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java index 2f313af..1850bf3 100644 --- a/test-runner/android/test/mock/MockPackageManager.java +++ b/test-runner/android/test/mock/MockPackageManager.java @@ -36,11 +36,11 @@ import android.content.pm.ProviderInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ThemeInfo; import android.content.res.Resources; import android.content.res.XmlResourceParser; import android.graphics.drawable.Drawable; import android.net.Uri; -import android.os.RemoteException; import java.util.List; @@ -120,6 +120,11 @@ public class MockPackageManager extends PackageManager { } @Override + public List<PackageInfo> getInstalledThemePackages() { + throw new UnsupportedOperationException(); + } + + @Override public int checkPermission(String permName, String pkgName) { throw new UnsupportedOperationException(); } diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index 1ac13f2..649d97f 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -35,7 +35,7 @@ public: Bundle(void) : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false), mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), - mUpdate(false), mExtending(false), + mUpdate(false), mExtending(false), mExtendedPackageId(0), mRequireLocalization(false), mPseudolocalize(false), mValues(false), mCompressionMethod(0), mOutputAPKFile(NULL), @@ -72,6 +72,8 @@ public: void setUpdate(bool val) { mUpdate = val; } bool getExtending(void) const { return mExtending; } void setExtending(bool val) { mExtending = val; } + int getExtendedPackageId(void) const { return mExtendedPackageId; } + void setExtendedPackageId(int val) { mExtendedPackageId = val; } bool getRequireLocalization(void) const { return mRequireLocalization; } void setRequireLocalization(bool val) { mRequireLocalization = val; } bool getPseudolocalize(void) const { return mPseudolocalize; } @@ -159,6 +161,7 @@ private: bool mMakePackageDirs; bool mUpdate; bool mExtending; + int mExtendedPackageId; bool mRequireLocalization; bool mPseudolocalize; bool mValues; diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 98286c0..735751c 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -14,6 +14,7 @@ #include <stdlib.h> #include <getopt.h> #include <assert.h> +#include <ctype.h> using namespace android; @@ -55,7 +56,7 @@ void usage(void) " xmltree Print the compiled xmls in the given assets.\n" " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName); fprintf(stderr, - " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n" + " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n" " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n" " [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n" " [--max-sdk-version VAL] [--app-version VAL] \\\n" @@ -106,7 +107,7 @@ void usage(void) #endif " -u update existing packages (add new, replace older, remove deleted files)\n" " -v verbose output\n" - " -x create extending (non-application) resource IDs\n" + " -x either create or assign (if specified) extending (non-application) resource IDs\n" " -z require localization of resource attributes marked with\n" " localization=\"suggested\"\n" " -A additional directory in which to find raw asset files\n" @@ -253,6 +254,14 @@ int main(int argc, char* const argv[]) break; case 'x': bundle.setExtending(true); + argc--; + argv++; + if (!argc || !isdigit(argv[0][0])) { + argc++; + argv--; + } else { + bundle.setExtendedPackageId(atoi(argv[0])); + } break; case 'z': bundle.setRequireLocalization(true); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index 19b9b01..1d4459b 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -3557,7 +3557,16 @@ sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) mHaveAppPackage = true; p = new Package(package, 127); } else { - p = new Package(package, mNextPackageId); + int extendedPackageId = mBundle->getExtendedPackageId(); + if (extendedPackageId != 0) { + if ((uint32_t)extendedPackageId < mNextPackageId) { + fprintf(stderr, "Package ID %d already in use!\n", mNextPackageId); + return NULL; + } + p = new Package(package, extendedPackageId); + } else { + p = new Package(package, mNextPackageId); + } } //printf("*** NEW PACKAGE: \"%s\" id=%d\n", // String8(package).string(), p->getAssignedId()); diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java index d842a66..e6791cb 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeXmlBlockParser.java @@ -193,6 +193,11 @@ public class BridgeXmlBlockParser implements XmlResourceParser { return mParser.getAttributeNamespace(index); } + /** @hide */ + public int getAttributeDataType(int index) { + throw new RuntimeException("getAttributeDataType not supported"); + } + public String getAttributeName(int index) { return mParser.getAttributeName(index); } |