diff options
author | Ricardo Cerqueira <github@cerqueira.org> | 2012-05-05 18:49:09 +0100 |
---|---|---|
committer | Ricardo Cerqueira <github@cerqueira.org> | 2012-05-05 18:49:17 +0100 |
commit | e821f5b727ef247a789557badb6640880cf992dd (patch) | |
tree | fc74bf9cc07a3eac04166477bed32ee5f9583d40 /core | |
parent | f0b7008ac173a4402b982b0f50ca3db8b0659bb7 (diff) | |
parent | fd6ee4e7d2f1600021c0d2f47914c01600bd1b97 (diff) | |
download | frameworks_base-e821f5b727ef247a789557badb6640880cf992dd.zip frameworks_base-e821f5b727ef247a789557badb6640880cf992dd.tar.gz frameworks_base-e821f5b727ef247a789557badb6640880cf992dd.tar.bz2 |
Merge branch 'themes-4.0' into 'ics'
Change-Id: Idc363f8140be2d252bee2aeba46c944032fb0ae9
Diffstat (limited to 'core')
29 files changed, 2018 insertions, 29 deletions
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index fdf8921..0f20809 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1548,6 +1549,16 @@ public class ActivityManager { return new HashMap<String, Integer>(); } } + /** + * @hide + */ + public Configuration getConfiguration() { + try { + return ActivityManagerNative.getDefault().getConfiguration(); + } catch (RemoteException e) { + return null; + } + } /** * Returns the usage statistics of each installed package. @@ -1579,4 +1590,16 @@ public class ActivityManager { } } + /** + * @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 0c761fc..a0ea99e 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,19 +17,28 @@ package android.app; +import com.android.internal.app.IAssetRedirectionManager; +import com.android.internal.os.BinderInternal; +import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SamplingProfilerIntegration; + +import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; + import android.app.backup.BackupAgent; import android.content.BroadcastReceiver; import android.content.ComponentCallbacks2; 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.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; +import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ProviderInfo; @@ -36,6 +46,8 @@ 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.PackageRedirectionMap; import android.content.res.Resources; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDebug; @@ -47,6 +59,7 @@ import android.net.Proxy; import android.net.ProxyProperties; import android.opengl.GLUtils; import android.os.AsyncTask; +import android.net.Uri; import android.os.Bundle; import android.os.Debug; import android.os.Handler; @@ -60,6 +73,7 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.StrictMode; import android.os.SystemClock; +import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; @@ -68,6 +82,7 @@ import android.util.LogPrinter; import android.util.Slog; import android.view.Display; import android.view.HardwareRenderer; +import android.view.InflateException; import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; @@ -76,12 +91,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 org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; - import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -140,6 +149,7 @@ public final class ActivityThread { static ContextImpl mSystemContext = null; static IPackageManager sPackageManager; + static IAssetRedirectionManager sAssetRedirectionManager; final ApplicationThread mAppThread = new ApplicationThread(); final Looper mLooper = Looper.myLooper(); @@ -1355,12 +1365,14 @@ public final class ActivityThread { private static class ResourcesKey { final private String mResDir; final private float mScale; + final private boolean mIsThemeable; final private int mHash; - ResourcesKey(String resDir, float scale) { + ResourcesKey(String resDir, float scale, boolean isThemeable) { mResDir = resDir; mScale = scale; - mHash = mResDir.hashCode() << 2 + (int) (mScale * 2); + mIsThemeable = isThemeable; + mHash = mResDir.hashCode() << 3 + ((mIsThemeable ? 1 : 0) << 2) + (int) (mScale * 2); } @Override @@ -1374,7 +1386,8 @@ public final class ActivityThread { return false; } ResourcesKey peer = (ResourcesKey) obj; - return mResDir.equals(peer.mResDir) && mScale == peer.mScale; + return mResDir.equals(peer.mResDir) && mScale == peer.mScale && + mIsThemeable == peer.mIsThemeable; } } @@ -1405,6 +1418,18 @@ public final class ActivityThread { return sPackageManager; } + // NOTE: this method can return null if the SystemServer is still + // initializing (for example, of another SystemServer component is accessing + // a resources object) + public static IAssetRedirectionManager getAssetRedirectionManager() { + if (sAssetRedirectionManager != null) { + return sAssetRedirectionManager; + } + IBinder b = ServiceManager.getService("assetredirection"); + sAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b); + return sAssetRedirectionManager; + } + DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) { DisplayMetrics dm = mDisplayMetrics.get(ci); if (dm != null && !forceUpdate) { @@ -1454,7 +1479,7 @@ public final class ActivityThread { * null. */ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { - ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale, compInfo.isThemeable); Resources r; synchronized (mPackages) { // Resources is app scale dependent. @@ -1480,10 +1505,23 @@ public final class ActivityThread { //} AssetManager assets = new AssetManager(); + assets.setThemeSupport(compInfo.isThemeable); if (assets.addAssetPath(resDir) == 0) { return null; } + /* Attach theme information to the resulting AssetManager when appropriate. */ + Configuration config = getConfiguration(); + if (compInfo.isThemeable && config != null) { + if (config.customTheme == null) { + config.customTheme = CustomTheme.getBootTheme(); + } + + if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { + attachThemeAssets(assets, config.customTheme); + } + } + //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics metrics = getDisplayMetricsLocked(null, false); r = new Resources(assets, metrics, getConfiguration(), compInfo); @@ -1509,6 +1547,81 @@ public final class ActivityThread { } } + private void detachThemeAssets(AssetManager assets) { + String themePackageName = assets.getThemePackageName(); + int themeCookie = assets.getThemeCookie(); + if (!TextUtils.isEmpty(themePackageName) && themeCookie != 0) { + assets.detachThemePath(themePackageName, themeCookie); + assets.setThemePackageName(null); + assets.setThemeCookie(0); + assets.clearRedirections(); + } + } + + /** + * Attach the necessary theme asset paths and meta information to convert an + * AssetManager to being globally "theme-aware". + * + * @param assets + * @param theme + * @return true if the AssetManager is now theme-aware; false otherwise. + * This can fail, for example, if the theme package has been been + * removed and the theme manager has yet to revert formally back to + * the framework default. + */ + private boolean attachThemeAssets(AssetManager assets, CustomTheme theme) { + IAssetRedirectionManager rm = getAssetRedirectionManager(); + if (rm == null) { + return false; + } + PackageInfo pi = null; + try { + pi = getPackageManager().getPackageInfo(theme.getThemePackageName(), 0); + } catch (RemoteException e) { + } + if (pi != null && pi.applicationInfo != null && pi.themeInfos != null) { + String themeResDir = pi.applicationInfo.publicSourceDir; + int cookie = assets.attachThemePath(themeResDir); + if (cookie != 0) { + String themePackageName = theme.getThemePackageName(); + String themeId = theme.getThemeId(); + int N = assets.getBasePackageCount(); + for (int i = 0; i < N; i++) { + String packageName = assets.getBasePackageName(i); + int packageId = assets.getBasePackageId(i); + + /* + * For now, we only consider redirections coming from the + * framework or regular android packages. This excludes + * themes and other specialty APKs we are not aware of. + */ + if (packageId != 0x01 && packageId != 0x7f) { + continue; + } + + try { + PackageRedirectionMap map = rm.getPackageRedirectionMap(themePackageName, themeId, + packageName); + if (map != null) { + assets.addRedirections(map); + } + } catch (RemoteException e) { + Log.e(TAG, "Failure accessing package redirection map, removing theme support."); + assets.detachThemePath(themePackageName, cookie); + return false; + } + } + + assets.setThemePackageName(theme.getThemePackageName()); + assets.setThemeCookie(cookie); + return true; + } else { + Log.e(TAG, "Unable to attach theme assets at " + themeResDir); + } + } + return false; + } + /** * Creates the top level resources for the given package. */ @@ -1953,6 +2066,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); @@ -3478,7 +3601,7 @@ public final class ActivityThread { } } - final boolean applyConfigurationToResourcesLocked(Configuration config, + final int applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat) { if (mResConfiguration == null) { mResConfiguration = new Configuration(); @@ -3486,7 +3609,7 @@ public final class ActivityThread { if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" + mResConfiguration.seq + ", newSeq=" + config.seq); - return false; + return 0; } int changes = mResConfiguration.updateFrom(config); DisplayMetrics dm = getDisplayMetricsLocked(null, true); @@ -3519,7 +3642,20 @@ public final class ActivityThread { if (r != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); + boolean themeChanged = (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0; + if (themeChanged) { + AssetManager am = r.getAssets(); + if (am.hasThemeSupport()) { + detachThemeAssets(am); + if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { + attachThemeAssets(am, config.customTheme); + } + } + } r.updateConfiguration(config, dm, compat); + if (themeChanged) { + r.updateStringCache(); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { @@ -3528,7 +3664,7 @@ public final class ActivityThread { } } - return changes != 0; + return changes; } final Configuration applyCompatConfiguration() { @@ -3548,6 +3684,8 @@ public final class ActivityThread { ArrayList<ComponentCallbacks2> callbacks = null; + int diff = 0; + synchronized (mPackages) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { @@ -3563,7 +3701,7 @@ public final class ActivityThread { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: " + config); - applyConfigurationToResourcesLocked(config, compat); + diff = applyConfigurationToResourcesLocked(config, compat); if (mConfiguration == null) { mConfiguration = new Configuration(); @@ -3582,7 +3720,20 @@ public final class ActivityThread { if (callbacks != null) { final int N = callbacks.size(); for (int i=0; i<N; i++) { - performConfigurationChanged(callbacks.get(i), config); + ComponentCallbacks2 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 ContextImpl) { + ((ContextImpl)context).refreshResourcesIfNecessary(); + } + } + } + + performConfigurationChanged(cb, config); } } } @@ -4356,7 +4507,7 @@ public final class ActivityThread { // We need to apply this change to the resources // immediately, because upon returning the view // hierarchy will be informed about it. - if (applyConfigurationToResourcesLocked(newConfig, null)) { + if (applyConfigurationToResourcesLocked(newConfig, null) != 0) { // This actually changed the resources! Tell // everyone about it. if (mPendingConfiguration == null || diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 180a442..ce8fd39 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -403,6 +403,16 @@ final class ApplicationPackageManager extends PackageManager { @SuppressWarnings("unchecked") @Override + public List<PackageInfo> getInstalledThemePackages() { + try { + return mPM.getInstalledThemePackages(); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @SuppressWarnings("unchecked") + @Override public List<ApplicationInfo> getInstalledApplications(int flags) { try { final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>(); diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 9d972b9..a8e40fd 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,15 +19,17 @@ package android.app; import com.android.internal.policy.PolicyManager; +import android.accounts.AccountManager; +import android.accounts.IAccountManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.IContentProvider; +import android.content.IIntentReceiver; import android.content.Intent; import android.content.IntentFilter; -import android.content.IIntentReceiver; import android.content.IntentSender; import android.content.ReceiverCallNotAllowedException; import android.content.ServiceConnection; @@ -36,6 +39,8 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; 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.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -491,6 +496,20 @@ class ContextImpl 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.mCompatibilityInfo.get().isThemeable) { + mTheme = null; + } + } + @Override public PackageManager getPackageManager() { if (mPackageManager != null) { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 34966bb..af6f3b3 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -2140,6 +2141,19 @@ public class Intent implements Parcelable, Cloneable { 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"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent categories (see addCategory()). @@ -2272,6 +2286,7 @@ public class Intent implements Parcelable, Cloneable { */ 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. For more @@ -2308,6 +2323,14 @@ public class Intent implements Parcelable, Cloneable { @SdkConstant(SdkConstantType.INTENT_CATEGORY) public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE"; + /** + * 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"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Application launch intent categories (see addCategory()). diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 0e6694d..d320768 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -321,6 +322,10 @@ public class ActivityInfo extends ComponentInfo */ 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 * {@link android.R.attr#configChanges} attribute. diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 65a8750..af7dcd2 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -419,6 +420,30 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public int enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; + /** + * 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"; + + /** + * @hide + */ + public static final String PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME = "isThemeable"; + + /** + * @hide + */ + public static final String PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME = "handleThemeConfigChanges"; + + /** + * @hide + */ + public static boolean isPlutoNamespace(String namespace) { + return namespace != null && namespace.equalsIgnoreCase(PLUTO_SCHEMA); + } /** * For convenient access to package's install location. @@ -520,6 +545,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { manageSpaceActivityName = orig.manageSpaceActivityName; descriptionRes = orig.descriptionRes; uiOptions = orig.uiOptions; + isThemeable = orig.isThemeable; } @@ -559,6 +585,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeInt(uiOptions); + dest.writeInt(isThemeable? 1 : 0); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -597,6 +624,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { backupAgentName = source.readString(); descriptionRes = source.readInt(); uiOptions = 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..0171137b --- /dev/null +++ b/core/java/android/content/pm/BaseThemeInfo.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2010, T-Mobile USA, Inc. + * + * 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. + */ + +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; + +/** + * @hide + */ +public class BaseThemeInfo implements Parcelable { + + /** + * Wallpaper drawable. + * + * @see wallpaperImage attribute + */ + public int wallpaperResourceId; + + /** + * The resource id of theme thumbnail. + * Specifies a theme thumbnail image resource as @drawable/foo. + * + * @see thumbnail attribute + * + */ + public int thumbnailResourceId; + + /** + * 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 = 0; + + /** + * 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; + + /** + * Preview image drawable. + * + * @see preview attribute + */ + public int previewResourceId; + + /** + * 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(wallpaperResourceId); + dest.writeInt(thumbnailResourceId); + 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.writeInt(previewResourceId); + } + + /** @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) { + wallpaperResourceId = source.readInt(); + thumbnailResourceId = source.readInt(); + 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(); + previewResourceId = source.readInt(); + } + + protected void changeDrmFlagIfNeeded(String resourcePath) { + if (resourcePath != null && resourcePath.contains(LOCKED_NAME)) { + isDrmProtected = true; + } + } +} diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index decb974..0842820 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -39,6 +39,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.ThemeInfo; import android.net.Uri; import android.content.IntentSender; @@ -121,6 +122,8 @@ interface IPackageManager { */ ParceledListSlice getInstalledPackages(int flags, in String lastRead); + List<PackageInfo> getInstalledThemePackages(); + /** * This implements getInstalledApplications via a "last returned row" * mechanism that is not exposed in the API. This is to get around the IPC diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java index eb05d76..42dd621 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -194,9 +195,69 @@ public class PackageInfo implements Parcelable { */ public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY; + // Is Theme Apk + /** + * {@hide} + */ + public boolean isThemeApk = false; + + // ThemeInfo + /** + * {@hide} + */ + public ThemeInfo [] themeInfos; + 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)) @@ -233,6 +294,12 @@ public class PackageInfo implements Parcelable { dest.writeTypedArray(configPreferences, parcelableFlags); dest.writeTypedArray(reqFeatures, parcelableFlags); dest.writeInt(installLocation); + + /* Theme-specific. */ + dest.writeInt((isThemeApk)? 1 : 0); + dest.writeInt((drmProtectedThemeApk)? 1 : 0); + dest.writeTypedArray(themeInfos, parcelableFlags); + dest.writeString(lockedZipFilePath); } public static final Parcelable.Creator<PackageInfo> CREATOR @@ -270,5 +337,11 @@ public class PackageInfo implements Parcelable { configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR); reqFeatures = source.createTypedArray(FeatureInfo.CREATOR); installLocation = source.readInt(); + + /* Theme-specific. */ + isThemeApk = (source.readInt() != 0); + drmProtectedThemeApk = (source.readInt() != 0); + themeInfos = source.createTypedArray(ThemeInfo.CREATOR); + lockedZipFilePath = source.readString(); } } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 8541748..c249a53 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -1407,6 +1408,17 @@ 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. + * + * @hide + */ + 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 e593d5b..fddd0bc 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2007 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -200,6 +201,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. * @@ -215,6 +227,21 @@ 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)); + } + } + } pi.applicationInfo = generateApplicationInfo(p, flags); pi.installLocation = p.installLocation; pi.firstInstallTime = firstInstallTime; @@ -1180,7 +1207,10 @@ 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 (RIGID_PARSER) { outError[0] = "Bad element under <manifest>: " + parser.getName(); @@ -1253,6 +1283,9 @@ public class PackageParser { >= android.os.Build.VERSION_CODES.DONUT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES; } + if (pkg.mIsThemeApk) { + pkg.applicationInfo.isThemeable = false; + } return pkg; } @@ -1536,12 +1569,43 @@ public class PackageParser { return a; } + private void parseApplicationThemeAttributes(XmlPullParser parser, AttributeSet attrs, + ApplicationInfo appInfo) { + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String attrName = attrs.getAttributeName(i); + if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) { + appInfo.isThemeable = attrs.getAttributeBooleanValue(i, false); + return; + } + } + } + + private void parseActivityThemeAttributes(XmlPullParser parser, AttributeSet attrs, + ActivityInfo ai) { + for (int i = 0; i < attrs.getAttributeCount(); i++) { + if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) { + continue; + } + String attrName = attrs.getAttributeName(i); + if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME)) { + ai.configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE; + } + } + } + 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; + // assume that this package is themeable unless explicitly set to false. + ai.isThemeable = true; + parseApplicationThemeAttributes(parser, attrs, ai); + TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestApplication); @@ -2045,6 +2109,8 @@ public class PackageParser { return null; } + parseActivityThemeAttributes(parser, attrs, a.info); + int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -2982,6 +3048,12 @@ 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); + // User set enabled state. public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; 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..e51dbb6 --- /dev/null +++ b/core/java/android/content/pm/ThemeInfo.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2010, T-Mobile USA, Inc. + * + * 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. + */ + +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:preview="@drawable/preview" + * pluto:author="John Doe" + * pluto:ringtoneFileName="media/audio/ringtone.mp3" + * pluto:notificationRingtoneFileName="media/audio/locked/notification.mp3" + * pluto:copyright="T-Mobile, 2009" + * /> + * + * @hide + */ +public final class ThemeInfo extends BaseThemeInfo { + private enum AttributeIndex { + THEME_PACKAGE_INDEX, + PREVIEW_INDEX, + AUTHOR_INDEX, + THEME_INDEX, + THEME_STYLE_NAME_INDEX, + THUMBNAIL_INDEX, + RINGTONE_FILE_NAME_INDEX, + NOTIFICATION_RINGTONE_FILE_NAME_INDEX, + WALLPAPER_IMAGE_INDEX, + COPYRIGHT_INDEX, + RINGTONE_NAME_INDEX, + NOTIFICATION_RINGTONE_NAME_INDEX, + STYLE_INDEX; + + public static AttributeIndex get(int ordinal) { + return values()[ordinal]; + } + }; + + private static final String [] compulsoryAttributes = new String [] { + "name", + "preview", + "author", + "themeId", + "styleName", + }; + + private static final String [] optionalAttributes = new String [] { + "thumbnail", + "ringtoneFileName", + "notificationRingtoneFileName", + "wallpaperImage", + "copyright", + "ringtoneName", + "notificationRingtoneName", + "styleId", + }; + + private static final Map<String, AttributeIndex> sAttributesLookupTable; + + static { + sAttributesLookupTable = new HashMap<String, AttributeIndex>(); + for (int i = 0; i < compulsoryAttributes.length; i++) { + sAttributesLookupTable.put(compulsoryAttributes[i], AttributeIndex.get(i)); + } + + for (int i = 0; i < optionalAttributes.length; i++) { + sAttributesLookupTable.put(optionalAttributes[i], + AttributeIndex.get(compulsoryAttributes.length + i)); + } + } + + public ThemeInfo(XmlPullParser parser, Resources res, AttributeSet attrs) throws XmlPullParserException { + super(); + + Map<String, AttributeIndex> tempMap = + new HashMap<String, AttributeIndex>(sAttributesLookupTable); + 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)) { + AttributeIndex index = tempMap.get(key); + tempMap.remove(key); + + if (index.ordinal() < compulsoryAttributes.length) { + numberOfCompulsoryAttributes++; + } + switch (index) { + case THEME_PACKAGE_INDEX: + // theme name + name = getResolvedString(res, attrs, i); + break; + + case THUMBNAIL_INDEX: + // theme thumbprint + thumbnailResourceId = attrs.getAttributeResourceValue(i, 0); + 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 WALLPAPER_IMAGE_INDEX: + // wallpaperImage attribute + wallpaperResourceId = attrs.getAttributeResourceValue(i, 0); + 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 STYLE_INDEX: + styleResourceId = attrs.getAttributeResourceValue(i, 0); + break; + + case PREVIEW_INDEX: + // theme thumbprint + previewResourceId = attrs.getAttributeResourceValue(i, 0); + break; + } + } + } + if (numberOfCompulsoryAttributes < compulsoryAttributes.length) { + throw new XmlPullParserException("Not all compulsory attributes are specified in <theme>"); + } + } + + 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 ffefaa2..80d0946 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +19,7 @@ package android.content.res; import android.os.ParcelFileDescriptor; import android.util.Log; +import android.util.SparseArray; import android.util.TypedValue; import java.io.FileNotFoundException; @@ -77,6 +79,20 @@ public final class AssetManager { private boolean mOpen = true; private HashMap<Integer, RuntimeException> mRefStacks; + private String mAssetDir; + private String mAppName; + + private boolean mThemeSupport; + private String mThemePackageName; + private int mThemeCookie; + + /** + * Organize all added redirection maps using Java strong references to keep + * the native layer cleanup simple (that is, finalize() in Java will be + * responsible for delete in C++). + */ + private SparseArray<PackageRedirectionMap> mRedirections; + /** * Create a new AssetManager containing only the basic system assets. * Applications will not generally use this method, instead retrieving the @@ -252,6 +268,12 @@ public final class AssetManager { } } + /*package*/ final void recreateStringBlocks() { + synchronized (this) { + makeStringBlocks(true); + } + } + /*package*/ final void makeStringBlocks(boolean copyFromSystem) { final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0; final int num = getStringBlockCount(); @@ -460,6 +482,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. * @@ -625,6 +659,110 @@ public final class AssetManager { } /** + * Delete a set of theme assets from the asset manager. Not for use by + * applications. Returns true if succeeded or false on failure. + * + * @hide + */ + public native final boolean detachThemePath(String packageName, int cookie); + + /** + * Attach a set of theme assets to the asset manager. If necessary, this + * method will forcefully update the internal ResTable data structure. + * + * @return Cookie of the added asset or 0 on failure. + * @hide + */ + public native final int attachThemePath(String path); + + /** + * 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; + } + + /** + * Apply a heuristic to match-up all attributes from the source style with + * attributes in the destination style. For each match, an entry in the + * package redirection map will be inserted. + * + * {@hide} + */ + public native final boolean generateStyleRedirections(int resMapNative, int sourceStyle, + int destStyle); + + /** + * Get package name of current theme (may return null). + * {@hide} + */ + public String getThemePackageName() { + return mThemePackageName; + } + + /** + * Sets package name and highest level style id for current theme (null, 0 is allowed). + * {@hide} + */ + public void setThemePackageName(String packageName) { + mThemePackageName = packageName; + } + + /** + * Get asset cookie for current theme (may return 0). + * {@hide} + */ + public int getThemeCookie() { + return mThemeCookie; + } + + /** + * Sets asset cookie for current theme (0 if not a themed asset manager). + * {@hide} + */ + public void setThemeCookie(int cookie) { + mThemeCookie = cookie; + } + + /** + * Add a redirection map to the asset manager. All future resource lookups + * will consult this map. + * {@hide} + */ + public void addRedirections(PackageRedirectionMap map) { + if (mRedirections == null) { + mRedirections = new SparseArray<PackageRedirectionMap>(2); + } + mRedirections.append(map.getPackageId(), map); + addRedirectionsNative(map.getNativePointer()); + } + + /** + * Clear redirection map for the asset manager. + * {@hide} + */ + public void clearRedirections() { + if (mRedirections != null) { + mRedirections.clear(); + } + clearRedirectionsNative(); + } + + /** * 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. @@ -741,6 +879,26 @@ 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); + + /** + * {@hide} + */ + public native final int getBasePackageCount(); + + /** + * {@hide} + */ + public native final String getBasePackageName(int index); + + /** + * {@hide} + */ + public native final int getBasePackageId(int index); + + private native final void addRedirectionsNative(int redirectionMapNativePointer); + private native final void clearRedirectionsNative(); + private native final void init(); private native final void destroy(); diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 1c9285e..d6856c9 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -92,9 +92,15 @@ public class CompatibilityInfo implements Parcelable { */ public final float applicationInvertedScale; + /** + * Whether the application supports third-party theming. + */ + public final boolean isThemeable; + public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw, boolean forceCompat) { int compatFlags = 0; + isThemeable = appInfo.isThemeable; if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0 || appInfo.largestWidthLimitDp != 0) { @@ -242,17 +248,19 @@ public class CompatibilityInfo implements Parcelable { } private CompatibilityInfo(int compFlags, - int dens, float scale, float invertedScale) { + int dens, float scale, float invertedScale, boolean isThemeable) { mCompatibilityFlags = compFlags; applicationDensity = dens; applicationScale = scale; applicationInvertedScale = invertedScale; + this.isThemeable = isThemeable; } private CompatibilityInfo() { this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE, 1.0f, - 1.0f); + 1.0f, + true); } /** @@ -519,6 +527,7 @@ public class CompatibilityInfo implements Parcelable { if (applicationDensity != oc.applicationDensity) return false; if (applicationScale != oc.applicationScale) return false; if (applicationInvertedScale != oc.applicationInvertedScale) return false; + if (isThemeable != oc.isThemeable) return false; return true; } catch (ClassCastException e) { return false; @@ -556,6 +565,7 @@ public class CompatibilityInfo implements Parcelable { result = 31 * result + applicationDensity; result = 31 * result + Float.floatToIntBits(applicationScale); result = 31 * result + Float.floatToIntBits(applicationInvertedScale); + result = 31 * result + (isThemeable ? 1 : 0); return result; } @@ -570,6 +580,7 @@ public class CompatibilityInfo implements Parcelable { dest.writeInt(applicationDensity); dest.writeFloat(applicationScale); dest.writeFloat(applicationInvertedScale); + dest.writeInt(isThemeable ? 1 : 0); } public static final Parcelable.Creator<CompatibilityInfo> CREATOR @@ -588,5 +599,6 @@ public class CompatibilityInfo implements Parcelable { applicationDensity = source.readInt(); applicationScale = source.readFloat(); applicationInvertedScale = source.readFloat(); + isThemeable = source.readInt() == 1 ? true : false; } } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 5c3a17a..9822936 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2008 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +21,9 @@ import android.content.pm.ActivityInfo; import android.os.Parcel; import android.os.Parcelable; import android.util.LocaleUtil; +import android.util.Log; +import android.os.SystemProperties; +import android.text.TextUtils; import java.util.Locale; @@ -56,6 +60,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 @@ -214,7 +223,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}, @@ -327,6 +351,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration compatScreenHeightDp = o.compatScreenHeightDp; compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp; seq = o.seq; + if (o.customTheme != null) { + customTheme = (CustomTheme) o.customTheme.clone(); + } } public String toString() { @@ -444,6 +471,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration sb.append(" s."); sb.append(seq); } + sb.append(" themeResource="); + sb.append(customTheme); sb.append('}'); return sb.toString(); } @@ -470,6 +499,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; textLayoutDirection = LocaleUtil.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE; seq = 0; + customTheme = null; } /** {@hide} */ @@ -589,7 +619,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (delta.seq != 0) { seq = delta.seq; } - + + if (delta.customTheme != null + && (customTheme == null || !customTheme.equals(delta.customTheme))) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + customTheme = (CustomTheme)delta.customTheme.clone(); + } + return changed; } @@ -685,6 +721,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration && smallestScreenWidthDp != delta.smallestScreenWidthDp) { changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE; } + if (delta.customTheme != null && + (customTheme == null || !customTheme.equals(delta.customTheme))) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + } return changed; } @@ -701,7 +741,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration * @return Return true if the resource needs to be loaded, else false. */ public static boolean needNewResources(int configChanges, int interestingChanges) { - return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0; + return (configChanges & (interestingChanges | + ActivityInfo.CONFIG_FONT_SCALE | + ActivityInfo.CONFIG_THEME_RESOURCE)) != 0; } /** @@ -774,6 +816,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(compatSmallestScreenWidthDp); dest.writeInt(textLayoutDirection); dest.writeInt(seq); + + if (customTheme == null) { + dest.writeInt(0); + } else { + dest.writeInt(1); + dest.writeString(customTheme.getThemeId()); + dest.writeString(customTheme.getThemePackageName()); + } } public void readFromParcel(Parcel source) { @@ -802,6 +852,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration compatSmallestScreenWidthDp = source.readInt(); textLayoutDirection = source.readInt(); seq = source.readInt(); + + if (source.readInt() != 0) { + String themeId = source.readString(); + String themePackage = source.readString(); + customTheme = new CustomTheme(themeId, themePackage); + } } public static final Parcelable.Creator<Configuration> CREATOR @@ -868,6 +924,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (n != 0) return n; n = this.smallestScreenWidthDp - that.smallestScreenWidthDp; //if (n != 0) return n; + if (this.customTheme == null) { + if (that.customTheme != null) return 1; + } else if (that.customTheme == null) { + return -1; + } else { + n = this.customTheme.getThemeId().compareTo(that.customTheme.getThemeId()); + if (n != 0) return n; + n = this.customTheme.getThemePackageName().compareTo(that.customTheme.getThemePackageName()); + if (n != 0) return n; + } + return n; } @@ -903,6 +970,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration result = 31 * result + screenWidthDp; result = 31 * result + screenHeightDp; result = 31 * result + smallestScreenWidthDp; + result = 31 * result + (this.customTheme != null ? + this.customTheme.hashCode() : 0); return result; } } diff --git a/core/java/android/content/res/CustomTheme.java b/core/java/android/content/res/CustomTheme.java new file mode 100644 index 0000000..364fb11 --- /dev/null +++ b/core/java/android/content/res/CustomTheme.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2010, T-Mobile USA, Inc. + * + * 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. + */ + +package android.content.res; + +import android.os.SystemProperties; +import android.text.TextUtils; + +/** + * @hide + */ +public final class CustomTheme implements Cloneable { + private final String mThemeId; + private final String mThemePackageName; + + private static final CustomTheme sBootTheme = new CustomTheme(); + private static final CustomTheme sSystemTheme = new CustomTheme("", ""); + + private CustomTheme() { + mThemeId = SystemProperties.get("persist.sys.themeId"); + mThemePackageName = SystemProperties.get("persist.sys.themePackageName"); + } + + 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; + + /* uhh, why are we trimming here instead of when the object is + * constructed? actually, why are we trimming at all? */ + return (currentPackageName.trim().equalsIgnoreCase(newPackageName.trim())) && + (currentThemeId.trim().equalsIgnoreCase(newThemeId.trim())); + } + return false; + } + + @Override + public final String toString() { + StringBuilder result = new StringBuilder(); + if (!TextUtils.isEmpty(mThemePackageName) && !TextUtils.isEmpty(mThemeId)) { + result.append(mThemePackageName); + result.append('('); + result.append(mThemeId); + result.append(')'); + } else { + result.append("system"); + } + return result.toString(); + } + + @Override + public synchronized int hashCode() { + return mThemeId.hashCode() + mThemePackageName.hashCode(); + } + + public String getThemeId() { + return mThemeId; + } + + public String getThemePackageName() { + return mThemePackageName; + } + + /** + * Represents the theme that the device booted into. This is used to + * simulate a "default" configuration based on the user's last known + * preference until the theme is switched at runtime. + */ + public static CustomTheme getBootTheme() { + return sBootTheme; + } + + /** + * Represents the system framework theme, perceived by the system as there + * being no theme applied. + */ + public static CustomTheme getSystemTheme() { + return sSystemTheme; + } +} diff --git a/core/java/android/content/res/PackageRedirectionMap.aidl b/core/java/android/content/res/PackageRedirectionMap.aidl new file mode 100644 index 0000000..4f47525 --- /dev/null +++ b/core/java/android/content/res/PackageRedirectionMap.aidl @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2011, T-Mobile USA, Inc. + * + * 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. + */ + +package android.content.res; + +/** + * @hide + */ +parcelable PackageRedirectionMap; diff --git a/core/java/android/content/res/PackageRedirectionMap.java b/core/java/android/content/res/PackageRedirectionMap.java new file mode 100644 index 0000000..55c4282 --- /dev/null +++ b/core/java/android/content/res/PackageRedirectionMap.java @@ -0,0 +1,90 @@ +package android.content.res; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * Native transport for package asset redirection information coming from the + * AssetRedirectionManagerService. + * + * @hide + */ +public class PackageRedirectionMap implements Parcelable { + private final int mNativePointer; + + public static final Parcelable.Creator<PackageRedirectionMap> CREATOR + = new Parcelable.Creator<PackageRedirectionMap>() { + public PackageRedirectionMap createFromParcel(Parcel in) { + return new PackageRedirectionMap(in); + } + + public PackageRedirectionMap[] newArray(int size) { + return new PackageRedirectionMap[size]; + } + }; + + public PackageRedirectionMap() { + this(nativeConstructor()); + } + + private PackageRedirectionMap(Parcel in) { + this(nativeCreateFromParcel(in)); + } + + private PackageRedirectionMap(int nativePointer) { + if (nativePointer == 0) { + throw new RuntimeException(); + } + mNativePointer = nativePointer; + } + + @Override + protected void finalize() throws Throwable { + nativeDestructor(mNativePointer); + } + + public int getNativePointer() { + return mNativePointer; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + if (!nativeWriteToParcel(mNativePointer, dest)) { + throw new RuntimeException(); + } + } + + public int getPackageId() { + return nativeGetPackageId(mNativePointer); + } + + public void addRedirection(int fromIdent, int toIdent) { + nativeAddRedirection(mNativePointer, fromIdent, toIdent); + } + + // Used for debugging purposes only. + public int[] getRedirectionKeys() { + return nativeGetRedirectionKeys(mNativePointer); + } + + // Used for debugging purposes only. + public int lookupRedirection(int fromIdent) { + return nativeLookupRedirection(mNativePointer, fromIdent); + } + + private static native int nativeConstructor(); + private static native void nativeDestructor(int nativePointer); + + private static native int nativeCreateFromParcel(Parcel p); + private static native boolean nativeWriteToParcel(int nativePointer, Parcel p); + + private native void nativeAddRedirection(int nativePointer, int fromIdent, int toIdent); + private native int nativeGetPackageId(int nativePointer); + private native int[] nativeGetRedirectionKeys(int nativePointer); + private native int nativeLookupRedirection(int nativePointer, int fromIdent); +} diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 990c42c..51c81f5 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1477,7 +1477,15 @@ public class Resources { mTmpConfig.locale = Locale.getDefault(); } configChanges = mConfiguration.updateFrom(mTmpConfig); - configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); + + /* This is ugly, but modifying the activityInfoConfigToNative + * adapter would be messier */ + if ((configChanges & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) { + configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); + configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE; + } else { + configChanges = ActivityInfo.activityInfoConfigToNative(configChanges); + } } if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); @@ -1539,6 +1547,18 @@ public class Resources { private void clearDrawableCache( LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) { + /* + * Quick test to find out if the config change that occurred should + * trigger a full cache wipe. + */ + if (Configuration.needNewResources(configChanges, 0)) { + if (DEBUG_CONFIG) { + Log.d(TAG, "Clear drawable cache from config changes: 0x" + + Integer.toHexString(configChanges)); + } + cache.clear(); + return; + } int N = cache.size(); if (DEBUG_CONFIG) { Log.d(TAG, "Cleaning up drawables config changes: 0x" @@ -1890,7 +1910,13 @@ public class Resources { flushLayoutCache(); } } - + + public final void updateStringCache() { + synchronized (mTmpValue) { + mAssets.recreateStringBlocks(); + } + } + /*package*/ Drawable loadDrawable(TypedValue value, int id) throws NotFoundException { diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java index 7cb32c7..ea5b016 100644 --- a/core/java/android/os/SystemProperties.java +++ b/core/java/android/os/SystemProperties.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006 The Android Open Source Project + * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -128,4 +129,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; + } + } + } + } diff --git a/core/java/com/android/internal/app/IAssetRedirectionManager.aidl b/core/java/com/android/internal/app/IAssetRedirectionManager.aidl new file mode 100644 index 0000000..8b47f0b --- /dev/null +++ b/core/java/com/android/internal/app/IAssetRedirectionManager.aidl @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2011, T-Mobile USA, Inc. + * + * 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. + */ + +package com.android.internal.app; + +import android.content.res.PackageRedirectionMap; + +/** + * Interface used to interact with the AssetRedirectionManagerService. + */ +interface IAssetRedirectionManager { + /** + * Access the package redirection map for the supplied package name given a + * particular theme. + */ + PackageRedirectionMap getPackageRedirectionMap(in String themePackageName, + String themeId, in String targetPackageName); + + /** + * Clear all redirection maps for the given theme. + */ + void clearRedirectionMapsByTheme(in String themePackageName, + in String themeId); + + /** + * Clear all redirection maps for the given target package. + */ + void clearPackageRedirectionMap(in String targetPackageName); +} diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 51eb7ea..7e80095 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -100,7 +100,7 @@ public class ZygoteInit { private static final String PRELOADED_CLASSES = "preloaded-classes"; /** Controls whether we should preload resources during zygote init. */ - private static final boolean PRELOAD_RESOURCES = true; + private static final boolean PRELOAD_RESOURCES = false; /** * Invokes a static "main(argv[]) method on class "className". @@ -360,6 +360,8 @@ public class ZygoteInit { N = preloadColorStateLists(runtime, ar); Log.i(TAG, "...preloaded " + N + " resources in " + (SystemClock.uptimeMillis()-startTime) + "ms."); + } else { + Log.i(TAG, "Preload resources disabled, skipped."); } mResources.finishPreloading(); } catch (RuntimeException e) { diff --git a/core/jni/Android.mk b/core/jni/Android.mk index 8b2f0c4..3853973 100755 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -86,6 +86,7 @@ LOCAL_SRC_FILES:= \ android_util_Process.cpp \ android_util_StringBlock.cpp \ android_util_XmlBlock.cpp \ + android_util_PackageRedirectionMap.cpp \ android/graphics/AutoDecodeCancel.cpp \ android/graphics/Bitmap.cpp \ android/graphics/BitmapFactory.cpp \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index b5a6b37..5fa3172 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -177,6 +177,7 @@ extern int register_android_content_res_ObbScanner(JNIEnv* env); extern int register_android_content_res_Configuration(JNIEnv* env); extern int register_android_animation_PropertyValuesHolder(JNIEnv *env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); +extern int register_android_content_res_PackageRedirectionMap(JNIEnv* env); #ifdef QCOM_HARDWARE extern int register_org_codeaurora_Performance(JNIEnv *env); #endif @@ -1207,6 +1208,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_animation_PropertyValuesHolder), REG_JNI(register_com_android_internal_content_NativeLibraryHelper), + REG_JNI(register_android_content_res_PackageRedirectionMap), #ifdef QCOM_HARDWARE REG_JNI(register_org_codeaurora_Performance), #endif diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 4f8f1af..1a8f3e7 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -1,6 +1,7 @@ /* //device/libs/android_runtime/android_util_AssetManager.cpp ** ** Copyright 2006, The Android Open Source Project +** This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. ** ** Licensed under the Apache License, Version 2.0 (the "License"); ** you may not use this file except in compliance with the License. @@ -34,9 +35,13 @@ #include <utils/Asset.h> #include <utils/AssetManager.h> #include <utils/ResourceTypes.h> +#include <utils/PackageRedirectionMap.h> +#include <utils/ZipFile.h> #include <stdio.h> +#define REDIRECT_NOISY(x) //x + namespace android { // ---------------------------------------------------------------------------- @@ -691,17 +696,23 @@ static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject } const ResTable& res(am->getResources()); + uint32_t ref = res.lookupRedirectionMap(ident); + if (ref == 0) { + ref = ident; + } else { + REDIRECT_NOISY(LOGW("PERFORMED REDIRECT OF ident=0x%08x FOR ref=0x%08x\n", ident, ref)); + } + Res_value value; ResTable_config config; uint32_t typeSpecFlags; - ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config); + ssize_t block = res.getResource(ref, &value, false, density, &typeSpecFlags, &config); #if THROW_ON_BAD_ID if (block == BAD_INDEX) { jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); return 0; } #endif - uint32_t ref = ident; if (resolve) { block = res.resolveReference(&value, block, &ref); #if THROW_ON_BAD_ID @@ -952,6 +963,20 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla // Now lock down the resource object and start pulling stuff from it. res.lock(); + // Apply theme redirections to the referenced styles. + if (defStyleRes != 0) { + uint32_t ref = res.lookupRedirectionMap(defStyleRes); + if (ref != 0) { + defStyleRes = ref; + } + } + if (style != 0) { + uint32_t ref = res.lookupRedirectionMap(style); + if (ref != 0) { + style = ref; + } + } + // Retrieve the default style bag, if requested. const ResTable::bag_entry* defStyleEnt = NULL; uint32_t defStyleTypeSetFlags = 0; @@ -1076,6 +1101,31 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla block = kXmlBlock; } + // One final test for a resource redirection from the applied theme. + if (resid != 0) { + uint32_t redirect = res.lookupRedirectionMap(resid); + if (redirect != 0) { + REDIRECT_NOISY(LOGW("deep REDIRECT 0x%08x => 0x%08x\n", resid, redirect)); + ssize_t newBlock = res.getResource(redirect, &value, true, config.density, &typeSetFlags, &config); + if (newBlock >= 0) { + newBlock = res.resolveReference(&value, newBlock, &redirect, &typeSetFlags, &config); +#if THROW_ON_BAD_ID + if (newBlock == BAD_INDEX) { + jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); + return JNI_FALSE; + } +#endif + if (newBlock >= 0) { + block = newBlock; + resid = redirect; + } + } + if (resid != redirect) { + LOGW("deep redirect failure from 0x%08x => 0x%08x, defStyleAttr=0x%08x, defStyleRes=0x%08x, style=0x%08x\n", resid, redirect, defStyleAttr, defStyleRes, style); + } + } + } + DEBUG_STYLES(LOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data)); @@ -1333,6 +1383,31 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz value.dataType = Res_value::TYPE_NULL; } + // One final test for a resource redirection from the applied theme. + if (resid != 0) { + uint32_t redirect = res.lookupRedirectionMap(resid); + if (redirect != 0) { + REDIRECT_NOISY(LOGW("array REDIRECT 0x%08x => 0x%08x\n", resid, redirect)); + ssize_t newBlock = res.getResource(redirect, &value, true, config.density, &typeSetFlags, &config); + if (newBlock >= 0) { + newBlock = res.resolveReference(&value, newBlock, &redirect, &typeSetFlags, &config); +#if THROW_ON_BAD_ID + if (newBlock == BAD_INDEX) { + jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); + return JNI_FALSE; + } +#endif + if (newBlock >= 0) { + block = newBlock; + resid = redirect; + } + } + if (resid != redirect) { + LOGW("array redirect failure from 0x%08x => 0x%08x, array id=0x%08x", resid, redirect, id); + } + } + } + //printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data); // Write the final value back to Java. @@ -1561,6 +1636,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) { + jniThrowException(env, "java/lang/NullPointerException", srcFileName == NULL ? "srcFileName" : "dstFileName"); + return -2; + } + + jsize size = env->GetArrayLength(drmProtectedAssetNames); + if (size == 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", "drmProtectedAssetNames"); + 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(); @@ -1607,6 +1760,173 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env, return AssetManager::getGlobalCount(); } +static jint android_content_AssetManager_getBasePackageCount(JNIEnv* env, jobject clazz) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + return am->getResources().getBasePackageCount(); +} + +static jstring android_content_AssetManager_getBasePackageName(JNIEnv* env, jobject clazz, jint index) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + String16 packageName(am->getResources().getBasePackageName(index)); + return env->NewString((const jchar*)packageName.string(), packageName.size()); +} + +static jint android_content_AssetManager_getBasePackageId(JNIEnv* env, jobject clazz, jint index) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + return am->getResources().getBasePackageId(index); +} + +static void android_content_AssetManager_addRedirectionsNative(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return; + } + + am->addRedirections(resMap); +} + +static void android_content_AssetManager_clearRedirectionsNative(JNIEnv* env, jobject clazz) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return; + } + + am->clearRedirections(); +} + +static jboolean android_content_AssetManager_generateStyleRedirections(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap, jint sourceStyle, jint destStyle) +{ + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + const ResTable& res(am->getResources()); + + res.lock(); + + // Load up a bag for the user-supplied theme. + const ResTable::bag_entry* themeEnt = NULL; + ssize_t N = res.getBagLocked(destStyle, &themeEnt); + const ResTable::bag_entry* endThemeEnt = themeEnt + (N >= 0 ? N : 0); + + // ...and a bag for the framework default. + const ResTable::bag_entry* frameworkEnt = NULL; + N = res.getBagLocked(sourceStyle, &frameworkEnt); + const ResTable::bag_entry* endFrameworkEnt = frameworkEnt + (N >= 0 ? N : 0); + + // Add the source => dest style redirection first. + jboolean ret = JNI_FALSE; + if (themeEnt < endThemeEnt && frameworkEnt < endFrameworkEnt) { + resMap->addRedirection(sourceStyle, destStyle); + ret = JNI_TRUE; + } + + // Now compare them and infer resource redirections for attributes that + // remap to different styles. This works by essentially lining up all the + // sorted attributes from each theme and detected TYPE_REFERENCE entries + // that point to different resources. When we find such a mismatch, we'll + // create a resource redirection from the original framework resource ID to + // the one in the theme. This lets us do things like automatically find + // redirections for @android:style/Widget.Button by looking at how the + // theme overrides the android:attr/buttonStyle attribute. + REDIRECT_NOISY(LOGW("delta between 0x%08x and 0x%08x:\n", sourceStyle, destStyle)); + for (; frameworkEnt < endFrameworkEnt; frameworkEnt++) { + if (frameworkEnt->map.value.dataType != Res_value::TYPE_REFERENCE) { + continue; + } + + uint32_t curIdent = frameworkEnt->map.name.ident; + + // Walk along the theme entry looking for a match. + while (themeEnt < endThemeEnt && curIdent > themeEnt->map.name.ident) { + themeEnt++; + } + // Match found, compare the references. + if (themeEnt < endThemeEnt && curIdent == themeEnt->map.name.ident) { + if (themeEnt->map.value.data != frameworkEnt->map.value.data) { + uint32_t fromIdent = frameworkEnt->map.value.data; + uint32_t toIdent = themeEnt->map.value.data; + REDIRECT_NOISY(LOGW(" generated mapping from 0x%08x => 0x%08x (by attr 0x%08x)\n", + fromIdent, toIdent, curIdent)); + resMap->addRedirection(fromIdent, toIdent); + } + themeEnt++; + } + + // Exhausted the theme, bail early. + if (themeEnt >= endThemeEnt) { + break; + } + } + + res.unlock(); + + return ret; +} + +static jboolean android_content_AssetManager_detachThemePath(JNIEnv* env, jobject clazz, + jstring packageName, jint cookie) +{ + if (packageName == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "packageName"); + return JNI_FALSE; + } + + AssetManager* am = assetManagerForJavaObject(env, clazz); + if (am == NULL) { + return JNI_FALSE; + } + + const char* name8 = env->GetStringUTFChars(packageName, NULL); + bool res = am->detachThemePath(String8(name8), (void *)cookie); + env->ReleaseStringUTFChars(packageName, name8); + + return res; +} + +static jint android_content_AssetManager_attachThemePath( + JNIEnv* env, jobject clazz, jstring path) +{ + if (path == NULL) { + jniThrowException(env, "java/lang/NullPointerException", "path"); + 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->attachThemePath(String8(path8), &cookie); + + env->ReleaseStringUTFChars(path, path8); + + return (res) ? (jint)cookie : 0; +} + // ---------------------------------------------------------------------------- /* @@ -1716,6 +2036,28 @@ 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 }, + + // Dynamic theme package support. + { "detachThemePath", "(Ljava/lang/String;I)Z", + (void*) android_content_AssetManager_detachThemePath }, + { "attachThemePath", "(Ljava/lang/String;)I", + (void*) android_content_AssetManager_attachThemePath }, + { "getBasePackageCount", "()I", + (void*) android_content_AssetManager_getBasePackageCount }, + { "getBasePackageName", "(I)Ljava/lang/String;", + (void*) android_content_AssetManager_getBasePackageName }, + { "getBasePackageId", "(I)I", + (void*) android_content_AssetManager_getBasePackageId }, + { "addRedirectionsNative", "(I)V", + (void*) android_content_AssetManager_addRedirectionsNative }, + { "clearRedirectionsNative", "()V", + (void*) android_content_AssetManager_clearRedirectionsNative }, + { "generateStyleRedirections", "(III)Z", + (void*) android_content_AssetManager_generateStyleRedirections }, }; int register_android_content_AssetManager(JNIEnv* env) diff --git a/core/jni/android_util_PackageRedirectionMap.cpp b/core/jni/android_util_PackageRedirectionMap.cpp new file mode 100644 index 0000000..2391edb --- /dev/null +++ b/core/jni/android_util_PackageRedirectionMap.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2011, T-Mobile USA, Inc. + * + * 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. + */ + +#include <utils/PackageRedirectionMap.h> + +#include "jni.h" +#include "JNIHelp.h" +#include <utils/misc.h> +#include <android_runtime/AndroidRuntime.h> + +#include "android_util_Binder.h" +#include <binder/Parcel.h> + +#include <utils/ResourceTypes.h> + +#include <stdio.h> + +namespace android { + +// ---------------------------------------------------------------------------- + +static PackageRedirectionMap* PackageRedirectionMap_constructor(JNIEnv* env, jobject clazz) +{ + return new PackageRedirectionMap; +} + +static void PackageRedirectionMap_destructor(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap) +{ + delete resMap; +} + +static PackageRedirectionMap* PackageRedirectionMap_createFromParcel(JNIEnv* env, jobject clazz, + jobject parcel) +{ + if (parcel == NULL) { + return NULL; + } + + Parcel* p = parcelForJavaObject(env, parcel); + PackageRedirectionMap* resMap = new PackageRedirectionMap; + + int32_t entryCount = p->readInt32(); + while (entryCount-- > 0) { + uint32_t fromIdent = (uint32_t)p->readInt32(); + uint32_t toIdent = (uint32_t)p->readInt32(); + resMap->addRedirection(fromIdent, toIdent); + } + + return resMap; +} + +static jboolean PackageRedirectionMap_writeToParcel(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap, jobject parcel) +{ + if (parcel == NULL) { + return JNI_FALSE; + } + + Parcel* p = parcelForJavaObject(env, parcel); + + int package = resMap->getPackage(); + size_t nTypes = resMap->getNumberOfTypes(); + size_t entryCount = 0; + for (size_t type=0; type<nTypes; type++) { + entryCount += resMap->getNumberOfUsedEntries(type); + } + p->writeInt32(entryCount); + for (size_t type=0; type<nTypes; type++) { + size_t nEntries = resMap->getNumberOfEntries(type); + for (size_t entry=0; entry<nEntries; entry++) { + uint32_t toIdent = resMap->getEntry(type, entry); + if (toIdent != 0) { + uint32_t fromIdent = Res_MAKEID(package-1, type, entry); + p->writeInt32(fromIdent); + p->writeInt32(toIdent); + } + } + } + + return JNI_TRUE; +} + +static void PackageRedirectionMap_addRedirection(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap, jint fromIdent, jint toIdent) +{ + resMap->addRedirection(fromIdent, toIdent); +} + +static jint PackageRedirectionMap_getPackageId(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap) +{ + return resMap->getPackage(); +} + +static jint PackageRedirectionMap_lookupRedirection(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap, jint fromIdent) +{ + return resMap->lookupRedirection(fromIdent); +} + +static jintArray PackageRedirectionMap_getRedirectionKeys(JNIEnv* env, jobject clazz, + PackageRedirectionMap* resMap) +{ + int package = resMap->getPackage(); + size_t nTypes = resMap->getNumberOfTypes(); + size_t entryCount = 0; + for (size_t type=0; type<nTypes; type++) { + size_t usedEntries = resMap->getNumberOfUsedEntries(type); + entryCount += usedEntries; + } + jintArray array = env->NewIntArray(entryCount); + if (array == NULL) { + jniThrowException(env, "java/lang/OutOfMemoryError", ""); + return NULL; + } + jsize index = 0; + for (size_t type=0; type<nTypes; type++) { + size_t nEntries = resMap->getNumberOfEntries(type); + for (size_t entry=0; entry<nEntries; entry++) { + uint32_t toIdent = resMap->getEntry(type, entry); + if (toIdent != 0) { + jint fromIdent = (jint)Res_MAKEID(package-1, type, entry); + env->SetIntArrayRegion(array, index++, 1, &fromIdent); + } + } + } + return array; +} + +// ---------------------------------------------------------------------------- + +/* + * JNI registration. + */ +static JNINativeMethod gPackageRedirectionMapMethods[] = { + { "nativeConstructor", "()I", + (void*) PackageRedirectionMap_constructor }, + { "nativeDestructor", "(I)V", + (void*) PackageRedirectionMap_destructor }, + { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I", + (void*) PackageRedirectionMap_createFromParcel }, + { "nativeWriteToParcel", "(ILandroid/os/Parcel;)Z", + (void*) PackageRedirectionMap_writeToParcel }, + { "nativeAddRedirection", "(III)V", + (void*) PackageRedirectionMap_addRedirection }, + { "nativeGetPackageId", "(I)I", + (void*) PackageRedirectionMap_getPackageId }, + { "nativeLookupRedirection", "(II)I", + (void*) PackageRedirectionMap_lookupRedirection }, + { "nativeGetRedirectionKeys", "(I)[I", + (void*) PackageRedirectionMap_getRedirectionKeys }, +}; + +int register_android_content_res_PackageRedirectionMap(JNIEnv* env) +{ + return AndroidRuntime::registerNativeMethods(env, + "android/content/res/PackageRedirectionMap", + gPackageRedirectionMapMethods, + NELEM(gPackageRedirectionMapMethods)); +} + +}; // namespace android diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 2e5dd50..26cc3ca 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" coreApp="true" android:sharedUserId="android.uid.system" android:sharedUserLabel="@string/android_system_label"> @@ -1638,6 +1639,18 @@ </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> + <service android:name="com.android.internal.os.storage.ExternalStorageFormatter" android:permission="android.permission.MASTER_CLEAR" android:exported="true" /> |