diff options
66 files changed, 4220 insertions, 180 deletions
@@ -149,6 +149,7 @@ LOCAL_SRC_FILES += \ core/java/com/android/internal/app/IBatteryStats.aidl \ core/java/com/android/internal/app/IUsageStats.aidl \ core/java/com/android/internal/app/IMediaContainerService.aidl \ + core/java/com/android/internal/app/IAssetRedirectionManager.aidl \ core/java/com/android/internal/appwidget/IAppWidgetService.aidl \ core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \ core/java/com/android/internal/backup/IBackupTransport.aidl \ 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" /> diff --git a/data/etc/com.tmobile.software.themes.xml b/data/etc/com.tmobile.software.themes.xml new file mode 100644 index 0000000..c145e3e --- /dev/null +++ b/data/etc/com.tmobile.software.themes.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- 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. +--> + +<!-- T-Mobile global theme engine is present. --> +<permissions> + <feature name="com.tmobile.software.themes" /> +</permissions> diff --git a/data/etc/platform.xml b/data/etc/platform.xml index 6cd07a3..27c562c 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -94,6 +94,11 @@ <group gid="net_bw_acct" /> </permission> + <!-- Permissions to read DRM-protected theme resources. --> + <permission name="com.tmobile.permission.ACCESS_DRM_THEME" > + <group gid="theme_manager" /> + </permission> + <!-- ================================================================== --> <!-- ================================================================== --> <!-- ================================================================== --> diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h index a8c7ddb..062a6fb 100644 --- a/include/utils/AssetManager.h +++ b/include/utils/AssetManager.h @@ -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. @@ -22,6 +23,7 @@ #include <utils/Asset.h> #include <utils/AssetDir.h> +#include <utils/PackageRedirectionMap.h> #include <utils/KeyedVector.h> #include <utils/String8.h> #include <utils/Vector.h> @@ -91,7 +93,7 @@ public: * then on success, *cookie is set to the value corresponding to the * newly-added asset source. */ - bool addAssetPath(const String8& path, void** cookie); + bool addAssetPath(const String8& path, void** cookie, bool asSkin=false); /* * Convenience for adding the standard system assets. Uses the @@ -217,14 +219,28 @@ public: */ void getLocales(Vector<String8>* locales) const; + /* + * Remove existing source for assets. + * + * Also updates the ResTable object to reflect the change. + * + * Returns "true" on success, "false" on failure. + */ + bool detachThemePath(const String8& packageName, void *cookie); + bool attachThemePath(const String8& path, void** cookie); + void addRedirections(PackageRedirectionMap* resMap); + void clearRedirections(); + private: struct asset_path { String8 path; FileType type; String8 idmap; + bool asSkin; }; + void updateResTableFromAssetPath(ResTable* rt, const asset_path& ap, void* cookie) const; Asset* openInPathLocked(const char* fileName, AccessMode mode, const asset_path& path); Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode, diff --git a/include/utils/PackageRedirectionMap.h b/include/utils/PackageRedirectionMap.h new file mode 100644 index 0000000..9e6435b --- /dev/null +++ b/include/utils/PackageRedirectionMap.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2005 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ANDROID_PACKAGEREDIRECTIONMAP_H +#define ANDROID_PACKAGEREDIRECTIONMAP_H + +#include <binder/Parcel.h> + +// --------------------------------------------------------------------------- + +namespace android { + +class PackageRedirectionMap +{ +public: + PackageRedirectionMap(); + ~PackageRedirectionMap(); + + bool addRedirection(uint32_t fromIdent, uint32_t toIdent); + uint32_t lookupRedirection(uint32_t fromIdent); + + // If there are no redirections present in this map, this method will + // return -1. + int getPackage(); + + // Usage of the following methods is intended to be used only by the JNI + // methods for the purpose of parceling. + size_t getNumberOfTypes(); + size_t getNumberOfUsedTypes(); + + size_t getNumberOfEntries(int type); + size_t getNumberOfUsedEntries(int type); + + // Similar to lookupRedirection, but with no sanity checking. + uint32_t getEntry(int type, int entry); + +private: + int mPackage; + + /* + * Sparse array organized into two layers: first by type, then by entry. + * The result of each lookup will be a qualified resource ID in the theme + * package scope. + * + * Underneath each layer is a SharedBuffer which + * indicates the array size. + */ + uint32_t** mEntriesByType; +}; + +} // namespace android + +// --------------------------------------------------------------------------- + +#endif // ANDROID_PACKAGEREDIRECTIONMAP_H diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h index 612ff93..d0cd257 100644 --- a/include/utils/ResourceTypes.h +++ b/include/utils/ResourceTypes.h @@ -1,5 +1,6 @@ /* * Copyright (C) 2005 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. @@ -23,6 +24,7 @@ #include <utils/Asset.h> #include <utils/ByteOrder.h> #include <utils/Errors.h> +#include <utils/PackageRedirectionMap.h> #include <utils/String16.h> #include <utils/Vector.h> @@ -1818,6 +1820,9 @@ public: bool copyData=false, const void* idmap = NULL); status_t add(ResTable* src); + void addRedirections(PackageRedirectionMap* resMap); + void clearRedirections(); + status_t getError() const; void uninit(); @@ -1865,6 +1870,8 @@ public: uint32_t* inoutTypeSpecFlags = NULL, ResTable_config* outConfig = NULL) const; + uint32_t lookupRedirectionMap(uint32_t resID) const; + enum { TMP_BUFFER_SIZE = 16 }; @@ -2079,6 +2086,7 @@ public: // IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file. static bool getIdmapInfo(const void* idmap, size_t size, uint32_t* pOriginalCrc, uint32_t* pOverlayCrc); + void removeAssetsByCookie(const String8 &packageName, void* cookie); #ifndef HAVE_ANDROID_OS void print(bool inclValues) const; @@ -2121,6 +2129,11 @@ private: // Mapping from resource package IDs to indices into the internal // package array. uint8_t mPackageMap[256]; + + // Resource redirection mapping provided by the applied theme (if there is + // one). Resources requested which are found in this map will be + // automatically redirected to the appropriate themed value. + Vector<PackageRedirectionMap*> mRedirectionMap; }; } // namespace android diff --git a/include/utils/ZipEntry.h b/include/utils/ZipEntry.h new file mode 100644 index 0000000..7f721b4 --- /dev/null +++ b/include/utils/ZipEntry.h @@ -0,0 +1,345 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// Zip archive entries. +// +// The ZipEntry class is tightly meshed with the ZipFile class. +// +#ifndef __LIBS_ZIPENTRY_H +#define __LIBS_ZIPENTRY_H + +#include <utils/Errors.h> + +#include <stdlib.h> +#include <stdio.h> + +namespace android { + +class ZipFile; + +/* + * ZipEntry objects represent a single entry in a Zip archive. + * + * You can use one of these to get or set information about an entry, but + * there are no functions here for accessing the data itself. (We could + * tuck a pointer to the ZipFile in here for convenience, but that raises + * the likelihood of using ZipEntry objects after discarding the ZipFile.) + * + * File information is stored in two places: next to the file data (the Local + * File Header, and possibly a Data Descriptor), and at the end of the file + * (the Central Directory Entry). The two must be kept in sync. + */ +class ZipEntry { +public: + friend class ZipFile; + + ZipEntry(void) + : mDeleted(false), mMarked(false) + {} + ~ZipEntry(void) {} + + /* + * Returns "true" if the data is compressed. + */ + bool isCompressed(void) const { + return mCDE.mCompressionMethod != kCompressStored; + } + int getCompressionMethod(void) const { return mCDE.mCompressionMethod; } + + /* + * Return the uncompressed length. + */ + off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; } + + /* + * Return the compressed length. For uncompressed data, this returns + * the same thing as getUncompresesdLen(). + */ + off_t getCompressedLen(void) const { return mCDE.mCompressedSize; } + + /* + * Return the absolute file offset of the start of the compressed or + * uncompressed data. + */ + off_t getFileOffset(void) const { + return mCDE.mLocalHeaderRelOffset + + LocalFileHeader::kLFHLen + + mLFH.mFileNameLength + + mLFH.mExtraFieldLength; + } + + /* + * Return the data CRC. + */ + unsigned long getCRC32(void) const { return mCDE.mCRC32; } + + /* + * Return file modification time in UNIX seconds-since-epoch. + */ + time_t getModWhen(void) const; + + /* + * Return the archived file name. + */ + const char* getFileName(void) const { return (const char*) mCDE.mFileName; } + + /* + * Application-defined "mark". Can be useful when synchronizing the + * contents of an archive with contents on disk. + */ + bool getMarked(void) const { return mMarked; } + void setMarked(bool val) { mMarked = val; } + + /* + * Some basic functions for raw data manipulation. "LE" means + * Little Endian. + */ + static inline unsigned short getShortLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8); + } + static inline unsigned long getLongLE(const unsigned char* buf) { + return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24); + } + static inline void putShortLE(unsigned char* buf, short val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + } + static inline void putLongLE(unsigned char* buf, long val) { + buf[0] = (unsigned char) val; + buf[1] = (unsigned char) (val >> 8); + buf[2] = (unsigned char) (val >> 16); + buf[3] = (unsigned char) (val >> 24); + } + + /* defined for Zip archives */ + enum { + kCompressStored = 0, // no compression + // shrunk = 1, + // reduced 1 = 2, + // reduced 2 = 3, + // reduced 3 = 4, + // reduced 4 = 5, + // imploded = 6, + // tokenized = 7, + kCompressDeflated = 8, // standard deflate + // Deflate64 = 9, + // lib imploded = 10, + // reserved = 11, + // bzip2 = 12, + }; + + /* + * Deletion flag. If set, the entry will be removed on the next + * call to "flush". + */ + bool getDeleted(void) const { return mDeleted; } + +protected: + /* + * Initialize the structure from the file, which is pointing at + * our Central Directory entry. + */ + status_t initFromCDE(FILE* fp); + + /* + * Initialize the structure for a new file. We need the filename + * and comment so that we can properly size the LFH area. The + * filename is mandatory, the comment is optional. + */ + void initNew(const char* fileName, const char* comment); + + /* + * Initialize the structure with the contents of a ZipEntry from + * another file. + */ + status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry); + + /* + * Add some pad bytes to the LFH. We do this by adding or resizing + * the "extra" field. + */ + status_t addPadding(int padding); + + /* + * Set information about the data for this entry. + */ + void setDataInfo(long uncompLen, long compLen, unsigned long crc32, + int compressionMethod); + + /* + * Set the modification date. + */ + void setModWhen(time_t when); + + /* + * Return the offset of the local file header. + */ + off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; } + + /* + * Set the offset of the local file header, relative to the start of + * the current file. + */ + void setLFHOffset(off_t offset) { + mCDE.mLocalHeaderRelOffset = (long) offset; + } + + /* mark for deletion; used by ZipFile::remove() */ + void setDeleted(void) { mDeleted = true; } + +private: + /* these are private and not defined */ + ZipEntry(const ZipEntry& src); + ZipEntry& operator=(const ZipEntry& src); + + /* returns "true" if the CDE and the LFH agree */ + bool compareHeaders(void) const; + void copyCDEtoLFH(void); + + bool mDeleted; // set if entry is pending deletion + bool mMarked; // app-defined marker + + /* + * Every entry in the Zip archive starts off with one of these. + */ + class LocalFileHeader { + public: + LocalFileHeader(void) : + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileName(NULL), + mExtraField(NULL) + {} + virtual ~LocalFileHeader(void) { + delete[] mFileName; + delete[] mExtraField; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned char* mFileName; + unsigned char* mExtraField; + + enum { + kSignature = 0x04034b50, + kLFHLen = 30, // LocalFileHdr len, excl. var fields + }; + + void dump(void) const; + }; + + /* + * Every entry in the Zip archive has one of these in the "central + * directory" at the end of the file. + */ + class CentralDirEntry { + public: + CentralDirEntry(void) : + mVersionMadeBy(0), + mVersionToExtract(0), + mGPBitFlag(0), + mCompressionMethod(0), + mLastModFileTime(0), + mLastModFileDate(0), + mCRC32(0), + mCompressedSize(0), + mUncompressedSize(0), + mFileNameLength(0), + mExtraFieldLength(0), + mFileCommentLength(0), + mDiskNumberStart(0), + mInternalAttrs(0), + mExternalAttrs(0), + mLocalHeaderRelOffset(0), + mFileName(NULL), + mExtraField(NULL), + mFileComment(NULL) + {} + virtual ~CentralDirEntry(void) { + delete[] mFileName; + delete[] mExtraField; + delete[] mFileComment; + } + + status_t read(FILE* fp); + status_t write(FILE* fp); + + // unsigned long mSignature; + unsigned short mVersionMadeBy; + unsigned short mVersionToExtract; + unsigned short mGPBitFlag; + unsigned short mCompressionMethod; + unsigned short mLastModFileTime; + unsigned short mLastModFileDate; + unsigned long mCRC32; + unsigned long mCompressedSize; + unsigned long mUncompressedSize; + unsigned short mFileNameLength; + unsigned short mExtraFieldLength; + unsigned short mFileCommentLength; + unsigned short mDiskNumberStart; + unsigned short mInternalAttrs; + unsigned long mExternalAttrs; + unsigned long mLocalHeaderRelOffset; + unsigned char* mFileName; + unsigned char* mExtraField; + unsigned char* mFileComment; + + void dump(void) const; + + enum { + kSignature = 0x02014b50, + kCDELen = 46, // CentralDirEnt len, excl. var fields + }; + }; + + enum { + //kDataDescriptorSignature = 0x08074b50, // currently unused + kDataDescriptorLen = 16, // four 32-bit fields + + kDefaultVersion = 20, // need deflate, nothing much else + kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3 + kUsesDataDescr = 0x0008, // GPBitFlag bit 3 + }; + + LocalFileHeader mLFH; + CentralDirEntry mCDE; +}; + +}; // namespace android + +#endif // __LIBS_ZIPENTRY_H diff --git a/include/utils/ZipFile.h b/include/utils/ZipFile.h new file mode 100644 index 0000000..dbbd072 --- /dev/null +++ b/include/utils/ZipFile.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// +// General-purpose Zip archive access. This class allows both reading and +// writing to Zip archives, including deletion of existing entries. +// +#ifndef __LIBS_ZIPFILE_H +#define __LIBS_ZIPFILE_H + +#include <utils/Vector.h> +#include <utils/Errors.h> +#include <stdio.h> + +#include "ZipEntry.h" + +namespace android { + +/* + * Manipulate a Zip archive. + * + * Some changes will not be visible in the until until "flush" is called. + * + * The correct way to update a file archive is to make all changes to a + * copy of the archive in a temporary file, and then unlink/rename over + * the original after everything completes. Because we're only interested + * in using this for packaging, we don't worry about such things. Crashing + * after making changes and before flush() completes could leave us with + * an unusable Zip archive. + */ +class ZipFile { +public: + ZipFile(void) + : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false) + {} + ~ZipFile(void) { + if (!mReadOnly) + flush(); + if (mZipFp != NULL) + fclose(mZipFp); + discardEntries(); + } + + /* + * Open a new or existing archive. + */ + typedef enum { + kOpenReadOnly = 0x01, + kOpenReadWrite = 0x02, + kOpenCreate = 0x04, // create if it doesn't exist + kOpenTruncate = 0x08, // if it exists, empty it + }; + status_t open(const char* zipFileName, int flags); + + /* + * Add a file to the end of the archive. Specify whether you want the + * library to try to store it compressed. + * + * If "storageName" is specified, the archive will use that instead + * of "fileName". + * + * If there is already an entry with the same name, the call fails. + * Existing entries with the same name must be removed first. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const char* fileName, int compressionMethod, + ZipEntry** ppEntry) + { + return add(fileName, fileName, compressionMethod, ppEntry); + } + status_t add(const char* fileName, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add a file that is already compressed with gzip. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t addGzip(const char* fileName, const char* storageName, + ZipEntry** ppEntry) + { + return addCommon(fileName, NULL, 0, storageName, + ZipEntry::kCompressDeflated, + ZipEntry::kCompressDeflated, ppEntry); + } + + /* + * Add a file from an in-memory data buffer. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const void* data, size_t size, const char* storageName, + int compressionMethod, ZipEntry** ppEntry) + { + return addCommon(NULL, data, size, storageName, + ZipEntry::kCompressStored, + compressionMethod, ppEntry); + } + + /* + * Add an entry by copying it from another zip file. If "padding" is + * nonzero, the specified number of bytes will be added to the "extra" + * field in the header. + * + * If "ppEntry" is non-NULL, a pointer to the new entry will be returned. + */ + status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry, + int padding, ZipEntry** ppEntry); + + /* + * Mark an entry as having been removed. It is not actually deleted + * from the archive or our internal data structures until flush() is + * called. + */ + status_t remove(ZipEntry* pEntry); + + /* + * Flush changes. If mNeedCDRewrite is set, this writes the central dir. + */ + status_t flush(void); + + /* + * Expand the data into the buffer provided. The buffer must hold + * at least <uncompressed len> bytes. Variation expands directly + * to a file. + * + * Returns "false" if an error was encountered in the compressed data. + */ + //bool uncompress(const ZipEntry* pEntry, void* buf) const; + //bool uncompress(const ZipEntry* pEntry, FILE* fp) const; + void* uncompress(const ZipEntry* pEntry); + + /* + * Get an entry, by name. Returns NULL if not found. + * + * Does not return entries pending deletion. + */ + ZipEntry* getEntryByName(const char* fileName) const; + + /* + * Get the Nth entry in the archive. + * + * This will return an entry that is pending deletion. + */ + int getNumEntries(void) const { return mEntries.size(); } + ZipEntry* getEntryByIndex(int idx) const; + +private: + /* these are private and not defined */ + ZipFile(const ZipFile& src); + ZipFile& operator=(const ZipFile& src); + + class EndOfCentralDir { + public: + EndOfCentralDir(void) : + mDiskNumber(0), + mDiskWithCentralDir(0), + mNumEntries(0), + mTotalNumEntries(0), + mCentralDirSize(0), + mCentralDirOffset(0), + mCommentLen(0), + mComment(NULL) + {} + virtual ~EndOfCentralDir(void) { + delete[] mComment; + } + + status_t readBuf(const unsigned char* buf, int len); + status_t write(FILE* fp); + + //unsigned long mSignature; + unsigned short mDiskNumber; + unsigned short mDiskWithCentralDir; + unsigned short mNumEntries; + unsigned short mTotalNumEntries; + unsigned long mCentralDirSize; + unsigned long mCentralDirOffset; // offset from first disk + unsigned short mCommentLen; + unsigned char* mComment; + + enum { + kSignature = 0x06054b50, + kEOCDLen = 22, // EndOfCentralDir len, excl. comment + + kMaxCommentLen = 65535, // longest possible in ushort + kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen, + + }; + + void dump(void) const; + }; + + + /* read all entries in the central dir */ + status_t readCentralDir(void); + + /* crunch deleted entries out */ + status_t crunchArchive(void); + + /* clean up mEntries */ + void discardEntries(void); + + /* common handler for all "add" functions */ + status_t addCommon(const char* fileName, const void* data, size_t size, + const char* storageName, int sourceType, int compressionMethod, + ZipEntry** ppEntry); + + /* copy all of "srcFp" into "dstFp" */ + status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32); + /* copy all of "data" into "dstFp" */ + status_t copyDataToFp(FILE* dstFp, + const void* data, size_t size, unsigned long* pCRC32); + /* copy some of "srcFp" into "dstFp" */ + status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length, + unsigned long* pCRC32); + /* like memmove(), but on parts of a single file */ + status_t filemove(FILE* fp, off_t dest, off_t src, size_t n); + /* compress all of "srcFp" into "dstFp", using Deflate */ + status_t compressFpToFp(FILE* dstFp, FILE* srcFp, + const void* data, size_t size, unsigned long* pCRC32); + + /* get modification date from a file descriptor */ + time_t getModTime(int fd); + + /* + * We use stdio FILE*, which gives us buffering but makes dealing + * with files >2GB awkward. Until we support Zip64, we're fine. + */ + FILE* mZipFp; // Zip file pointer + + /* one of these per file */ + EndOfCentralDir mEOCD; + + /* did we open this read-only? */ + bool mReadOnly; + + /* set this when we trash the central dir */ + bool mNeedCDRewrite; + + /* + * One ZipEntry per entry in the zip file. I'm using pointers instead + * of objects because it's easier than making operator= work for the + * classes and sub-classes. + */ + Vector<ZipEntry*> mEntries; +}; + +}; // namespace android + +#endif // __LIBS_ZIPFILE_H diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk index 831d9e3..58276ba 100644 --- a/libs/utils/Android.mk +++ b/libs/utils/Android.mk @@ -30,6 +30,7 @@ commonSources:= \ LinearTransform.cpp \ ObbFile.cpp \ PropertyMap.cpp \ + PackageRedirectionMap.cpp \ RefBase.cpp \ ResourceTypes.cpp \ SharedBuffer.cpp \ @@ -49,6 +50,8 @@ commonSources:= \ ZipFileCRO.cpp \ ZipFileRO.cpp \ ZipUtils.cpp \ + ../../tools/aapt/ZipFile.cpp \ + ../../tools/aapt/ZipEntry.cpp \ misc.cpp diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp index 22034c5..4733e78 100644 --- a/libs/utils/AssetManager.cpp +++ b/libs/utils/AssetManager.cpp @@ -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. @@ -134,7 +135,7 @@ AssetManager::~AssetManager(void) delete[] mVendor; } -bool AssetManager::addAssetPath(const String8& path, void** cookie) +bool AssetManager::addAssetPath(const String8& path, void** cookie, bool asSkin) { AutoMutex _l(mLock); @@ -144,6 +145,7 @@ bool AssetManager::addAssetPath(const String8& path, void** cookie) if (kAppZipName) { realPath.appendPath(kAppZipName); } + ap.asSkin = asSkin; ap.type = ::getFileType(realPath.string()); if (ap.type == kFileTypeRegular) { ap.path = realPath; @@ -498,9 +500,13 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) size_t i = mAssetPaths.size(); while (i > 0) { i--; + const asset_path& ap = mAssetPaths.itemAt(i); + if (ap.asSkin) { + continue; + } LOGV("Looking for asset '%s' in '%s'\n", - assetName.string(), mAssetPaths.itemAt(i).path.string()); - Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i)); + assetName.string(), ap.path.string()); + Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, ap); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } @@ -532,9 +538,13 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) size_t i = mAssetPaths.size(); while (i > 0) { i--; - LOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string()); + const asset_path& ap = mAssetPaths.itemAt(i); + if (ap.asSkin) { + continue; + } + LOGV("Looking for non-asset '%s' in '%s'\n", fileName, ap.path.string()); Asset* pAsset = openNonAssetInPathLocked( - fileName, mode, mAssetPaths.itemAt(i)); + fileName, mode, ap); if (pAsset != NULL) { return pAsset != kExcludedAsset ? pAsset : NULL; } @@ -614,83 +624,87 @@ const ResTable* AssetManager::getResTable(bool required) const if (mCacheMode != CACHE_OFF && !mCacheValid) const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); - const size_t N = mAssetPaths.size(); - for (size_t i=0; i<N; i++) { - Asset* ass = NULL; - ResTable* sharedRes = NULL; - bool shared = true; - const asset_path& ap = mAssetPaths.itemAt(i); - Asset* idmap = openIdmapLocked(ap); - LOGV("Looking for resource asset in '%s'\n", ap.path.string()); - if (ap.type != kFileTypeDirectory) { - if (i == 0) { - // The first item is typically the framework resources, - // which we want to avoid parsing every time. - sharedRes = const_cast<AssetManager*>(this)-> - mZipSet.getZipResourceTable(ap.path); - } - if (sharedRes == NULL) { + mResources = rt = new ResTable(); + + if (rt) { + const size_t N = mAssetPaths.size(); + for (size_t i=0; i<N; i++) { + const asset_path& ap = mAssetPaths.itemAt(i); + updateResTableFromAssetPath(rt, ap, (void*)(i+1)); + } + } + + if (required && !rt) LOGW("Unable to find resources file resources.arsc"); + if (!rt) { + mResources = rt = new ResTable(); + } + + return rt; +} + +void AssetManager::updateResTableFromAssetPath(ResTable *rt, const asset_path& ap, void *cookie) const +{ + Asset* ass = NULL; + ResTable* sharedRes = NULL; + bool shared = true; + size_t cookiePos = (size_t)cookie; + LOGV("Looking for resource asset in '%s'\n", ap.path.string()); + if (ap.type != kFileTypeDirectory) { + if (cookiePos == 1) { + // The first item is typically the framework resources, + // which we want to avoid parsing every time. + sharedRes = const_cast<AssetManager*>(this)-> + mZipSet.getZipResourceTable(ap.path); + } + if (sharedRes == NULL) { + ass = const_cast<AssetManager*>(this)-> + mZipSet.getZipResourceTableAsset(ap.path); + if (ass == NULL) { + LOGV("loading resource table %s\n", ap.path.string()); ass = const_cast<AssetManager*>(this)-> - mZipSet.getZipResourceTableAsset(ap.path); - if (ass == NULL) { - LOGV("loading resource table %s\n", ap.path.string()); + openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, + ap); + if (ass != NULL && ass != kExcludedAsset) { ass = const_cast<AssetManager*>(this)-> - openNonAssetInPathLocked("resources.arsc", - Asset::ACCESS_BUFFER, - ap); - if (ass != NULL && ass != kExcludedAsset) { - ass = const_cast<AssetManager*>(this)-> - mZipSet.setZipResourceTableAsset(ap.path, ass); - } - } - - if (i == 0 && ass != NULL) { - // If this is the first resource table in the asset - // manager, then we are going to cache it so that we - // can quickly copy it out for others. - LOGV("Creating shared resources for %s", ap.path.string()); - sharedRes = new ResTable(); - sharedRes->add(ass, (void*)(i+1), false, idmap); - sharedRes = const_cast<AssetManager*>(this)-> - mZipSet.setZipResourceTable(ap.path, sharedRes); + mZipSet.setZipResourceTableAsset(ap.path, ass); } } - } else { - LOGV("loading resource table %s\n", ap.path.string()); - Asset* ass = const_cast<AssetManager*>(this)-> - openNonAssetInPathLocked("resources.arsc", - Asset::ACCESS_BUFFER, - ap); - shared = false; - } - if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { - if (rt == NULL) { - mResources = rt = new ResTable(); - updateResourceParamsLocked(); - } - LOGV("Installing resource asset %p in to table %p\n", ass, mResources); - if (sharedRes != NULL) { - LOGV("Copying existing resources for %s", ap.path.string()); - rt->add(sharedRes); - } else { - LOGV("Parsing resources for %s", ap.path.string()); - rt->add(ass, (void*)(i+1), !shared, idmap); - } - if (!shared) { - delete ass; + if (cookiePos == 0 && ass != NULL) { + // If this is the first resource table in the asset + // manager, then we are going to cache it so that we + // can quickly copy it out for others. + LOGV("Creating shared resources for %s", ap.path.string()); + sharedRes = new ResTable(); + sharedRes->add(ass, cookie, false); + sharedRes = const_cast<AssetManager*>(this)-> + mZipSet.setZipResourceTable(ap.path, sharedRes); } } - if (idmap != NULL) { - delete idmap; + } else { + LOGV("loading resource table %s\n", ap.path.string()); + Asset* ass = const_cast<AssetManager*>(this)-> + openNonAssetInPathLocked("resources.arsc", + Asset::ACCESS_BUFFER, + ap); + shared = false; + } + if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { + updateResourceParamsLocked(); + LOGV("Installing resource asset %p in to table %p\n", ass, mResources); + if (sharedRes != NULL) { + LOGV("Copying existing resources for %s", ap.path.string()); + rt->add(sharedRes); + } else { + LOGV("Parsing resources for %s", ap.path.string()); + rt->add(ass, cookie, !shared); } - } - if (required && !rt) LOGW("Unable to find resources file resources.arsc"); - if (!rt) { - mResources = rt = new ResTable(); + if (!shared) { + delete ass; + } } - return rt; } void AssetManager::updateResourceParamsLocked() const @@ -1145,6 +1159,9 @@ AssetDir* AssetManager::openDir(const char* dirName) while (i > 0) { i--; const asset_path& ap = mAssetPaths.itemAt(i); + if (ap.asSkin) { + continue; + } if (ap.type == kFileTypeRegular) { LOGV("Adding directory %s from zip %s", dirName, ap.path.string()); scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); @@ -1999,3 +2016,52 @@ int AssetManager::ZipSet::getIndex(const String8& zip) const return mZipPath.size()-1; } + +bool AssetManager::attachThemePath(const String8& path, void** cookie) +{ + bool res = addAssetPath(path, cookie, true); + ResTable* rt = mResources; + if (res && rt != NULL && ((size_t)*cookie == mAssetPaths.size())) { + AutoMutex _l(mLock); + const asset_path& ap = mAssetPaths.itemAt((size_t)*cookie - 1); + updateResTableFromAssetPath(rt, ap, *cookie); + } + return res; +} + +bool AssetManager::detachThemePath(const String8 &packageName, void* cookie) +{ + AutoMutex _l(mLock); + + const size_t which = ((size_t)cookie)-1; + if (which >= mAssetPaths.size()) { + return false; + } + + /* TODO: Ensure that this cookie is added with asSkin == true. */ + mAssetPaths.removeAt(which); + + ResTable* rt = mResources; + if (rt == NULL) { + LOGV("ResTable must not be NULL"); + return false; + } + + rt->removeAssetsByCookie(packageName, (void *)cookie); + + return true; +} + +void AssetManager::addRedirections(PackageRedirectionMap* resMap) +{ + getResources(); + ResTable* rt = mResources; + rt->addRedirections(resMap); +} + +void AssetManager::clearRedirections() +{ + getResources(); + ResTable* rt = mResources; + rt->clearRedirections(); +} diff --git a/libs/utils/PackageRedirectionMap.cpp b/libs/utils/PackageRedirectionMap.cpp new file mode 100644 index 0000000..bf1062a --- /dev/null +++ b/libs/utils/PackageRedirectionMap.cpp @@ -0,0 +1,191 @@ +/* + * 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. + * 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. + */ + +// +// Provide access to read-only assets. +// + +#define LOG_TAG "packageresmap" + +#include <utils/PackageRedirectionMap.h> +#include <utils/ResourceTypes.h> +#include <utils/misc.h> + +using namespace android; + +PackageRedirectionMap::PackageRedirectionMap() + : mPackage(-1), mEntriesByType(NULL) +{ +} + +static void clearEntriesByType(uint32_t** entriesByType) +{ + SharedBuffer* buf = SharedBuffer::bufferFromData(entriesByType); + const size_t N = buf->size() / sizeof(entriesByType[0]); + for (size_t i = 0; i < N; i++) { + uint32_t* entries = entriesByType[i]; + if (entries != NULL) { + SharedBuffer::bufferFromData(entries)->release(); + } + } + buf->release(); +} + +PackageRedirectionMap::~PackageRedirectionMap() +{ + if (mEntriesByType != NULL) { + clearEntriesByType(mEntriesByType); + } +} + +static void* ensureCapacity(void* data, size_t nmemb, size_t size) +{ + SharedBuffer* buf; + size_t currentSize; + + if (data != NULL) { + buf = SharedBuffer::bufferFromData(data); + currentSize = buf->size(); + } else { + buf = NULL; + currentSize = 0; + } + + size_t minSize = nmemb * size; + if (minSize > currentSize) { + unsigned int requestSize = roundUpPower2(minSize); + if (buf == NULL) { + buf = SharedBuffer::alloc(requestSize); + } else { + buf = buf->editResize(requestSize); + } + memset((unsigned char*)buf->data()+currentSize, 0, requestSize - currentSize); + } + + return buf->data(); +} + +bool PackageRedirectionMap::addRedirection(uint32_t fromIdent, uint32_t toIdent) +{ + const int package = Res_GETPACKAGE(fromIdent); + const int type = Res_GETTYPE(fromIdent); + const int entry = Res_GETENTRY(fromIdent); + + // The first time we add a redirection we can infer the package for all + // future redirections. + if (mPackage == -1) { + mPackage = package+1; + } else if (mPackage != (package+1)) { + LOGW("cannot add redirection for conflicting package 0x%02x (expecting package 0x%02x)\n", package+1, mPackage); + return false; + } + + mEntriesByType = (uint32_t**)ensureCapacity(mEntriesByType, type + 1, sizeof(uint32_t*)); + uint32_t* entries = mEntriesByType[type]; + entries = (uint32_t*)ensureCapacity(entries, entry + 1, sizeof(uint32_t)); + entries[entry] = toIdent; + mEntriesByType[type] = entries; + + return true; +} + +uint32_t PackageRedirectionMap::lookupRedirection(uint32_t fromIdent) +{ + if (mPackage == -1 || mEntriesByType == NULL || fromIdent == 0) { + return 0; + } + + const int package = Res_GETPACKAGE(fromIdent); + const int type = Res_GETTYPE(fromIdent); + const int entry = Res_GETENTRY(fromIdent); + + if (package+1 != mPackage) { + return 0; + } + + size_t nTypes = getNumberOfTypes(); + if (type < 0 || type >= nTypes) { + return 0; + } + uint32_t* entries = mEntriesByType[type]; + if (entries == NULL) { + return 0; + } + size_t nEntries = getNumberOfEntries(type); + if (entry < 0 || entry >= nEntries) { + return 0; + } + return entries[entry]; +} + +int PackageRedirectionMap::getPackage() +{ + return mPackage; +} + +size_t PackageRedirectionMap::getNumberOfTypes() +{ + if (mEntriesByType == NULL) { + return 0; + } else { + return SharedBuffer::bufferFromData(mEntriesByType)->size() / + sizeof(mEntriesByType[0]); + } +} + +size_t PackageRedirectionMap::getNumberOfUsedTypes() +{ + uint32_t** entriesByType = mEntriesByType; + size_t N = getNumberOfTypes(); + size_t count = 0; + for (size_t i=0; i<N; i++) { + if (entriesByType[i] != NULL) { + count++; + } + } + return count; +} + +size_t PackageRedirectionMap::getNumberOfEntries(int type) +{ + uint32_t* entries = mEntriesByType[type]; + if (entries == NULL) { + return 0; + } else { + return SharedBuffer::bufferFromData(entries)->size() / + sizeof(entries[0]); + } +} + +size_t PackageRedirectionMap::getNumberOfUsedEntries(int type) +{ + size_t N = getNumberOfEntries(type); + uint32_t* entries = mEntriesByType[type]; + size_t count = 0; + for (size_t i=0; i<N; i++) { + if (entries[i] != 0) { + count++; + } + } + return count; +} + +uint32_t PackageRedirectionMap::getEntry(int type, int entry) +{ + uint32_t* entries = mEntriesByType[type]; + return entries[entry]; +} diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp index 6cf01c8..6040df1 100644 --- a/libs/utils/ResourceTypes.cpp +++ b/libs/utils/ResourceTypes.cpp @@ -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. @@ -25,6 +26,7 @@ #include <utils/String8.h> #include <utils/TextOutput.h> #include <utils/Log.h> +#include <utils/misc.h> #include <stdlib.h> #include <string.h> @@ -43,6 +45,7 @@ #define TABLE_SUPER_NOISY(x) //x #define LOAD_TABLE_NOISY(x) //x #define TABLE_THEME(x) //x +#define REDIRECT_NOISY(x) //x namespace android { @@ -1550,6 +1553,13 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force) const bag_entry* bag; uint32_t bagTypeSpecFlags = 0; mTable.lock(); + uint32_t redirect = mTable.lookupRedirectionMap(resID); + if (redirect != 0 || resID == 0x01030005) { + REDIRECT_NOISY(LOGW("applyStyle: PERFORMED REDIRECT OF ident=0x%08x FOR redirect=0x%08x\n", resID, redirect)); + } + if (redirect != 0) { + resID = redirect; + } const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags); TABLE_NOISY(LOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N)); if (N < 0) { @@ -1997,6 +2007,8 @@ void ResTable::uninit() mPackageGroups.clear(); mHeaders.clear(); + + clearRedirections(); } bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const @@ -2254,6 +2266,24 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, return blockIndex; } +uint32_t ResTable::lookupRedirectionMap(uint32_t resID) const +{ + if (mError != NO_ERROR) { + return 0; + } + + const int p = Res_GETPACKAGE(resID)+1; + + const size_t N = mRedirectionMap.size(); + for (size_t i=0; i<N; i++) { + PackageRedirectionMap* resMap = mRedirectionMap[i]; + if (resMap->getPackage() == p) { + return resMap->lookupRedirection(resID); + } + } + return 0; +} + const char16_t* ResTable::valueToString( const Res_value* value, size_t stringBlock, char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen) @@ -2461,7 +2491,19 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, if (parent) { const bag_entry* parentBag; uint32_t parentTypeSpecFlags = 0; - const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags); + uint32_t parentRedirect = lookupRedirectionMap(parent); + uint32_t parentActual = parent; + if (parentRedirect != 0 || parent == 0x01030005) { + if (parentRedirect == resID) { + REDIRECT_NOISY(LOGW("applyStyle(parent): ignoring circular redirect from parent=0x%08x to parentRedirect=0x%08x\n", parent, parentRedirect)); + } else { + REDIRECT_NOISY(LOGW("applyStyle(parent): PERFORMED REDIRECT OF parent=0x%08x FOR parentRedirect=0x%08x\n", parent, parentRedirect)); + if (parentRedirect != 0) { + parentActual = parentRedirect; + } + } + } + const ssize_t NP = getBagLocked(parentActual, &parentBag, &parentTypeSpecFlags); const size_t NT = ((NP >= 0) ? NP : 0) + N; set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT); if (set == NULL) { @@ -4442,6 +4484,78 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes, return true; } +void ResTable::removeAssetsByCookie(const String8 &packageName, void* cookie) +{ + mError = NO_ERROR; + + size_t N = mHeaders.size(); + for (size_t i = 0; i < N; i++) { + Header* header = mHeaders[i]; + if ((size_t)header->cookie == (size_t)cookie) { + if (header->ownedData != NULL) { + free(header->ownedData); + } + mHeaders.removeAt(i); + break; + } + } + size_t pgCount = mPackageGroups.size(); + for (size_t pgIndex = 0; pgIndex < pgCount; pgIndex++) { + PackageGroup* pg = mPackageGroups[pgIndex]; + + size_t pkgCount = pg->packages.size(); + size_t index = pkgCount; + for (size_t pkgIndex = 0; pkgIndex < pkgCount; pkgIndex++) { + const Package* pkg = pg->packages[pkgIndex]; + if (String8(String16(pkg->package->name)).compare(packageName) == 0) { + index = pkgIndex; + LOGV("Delete Package %d id=%d name=%s\n", + (int)pkgIndex, pkg->package->id, + String8(String16(pkg->package->name)).string()); + break; + } + } + if (index < pkgCount) { + const Package* pkg = pg->packages[index]; + uint32_t id = dtohl(pkg->package->id); + if (id != 0 && id < 256) { + mPackageMap[id] = 0; + } + if (pkgCount == 1) { + LOGV("Delete Package Group %d id=%d packageCount=%d name=%s\n", + (int)pgIndex, pg->id, (int)pg->packages.size(), + String8(pg->name).string()); + mPackageGroups.removeAt(pgIndex); + delete pg; + } else { + pg->packages.removeAt(index); + delete pkg; + } + return; + } + } +} + +/* + * Load the redirection map from the supplied map path. + * + * The path is expected to be a directory containing individual map cache files + * for each package that is to have resources redirected. Only those packages + * that are included in this ResTable will be loaded into the redirection map. + * For this reason, this method should be called only after all resource + * bundles have been added to the table. + */ +void ResTable::addRedirections(PackageRedirectionMap* resMap) +{ + // TODO: Replace an existing entry matching the same package. + mRedirectionMap.add(resMap); +} + +void ResTable::clearRedirections() +{ + /* This memory is being managed by strong references at the Java layer. */ + mRedirectionMap.clear(); +} #ifndef HAVE_ANDROID_OS #define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string()) diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index f16ba36..8b6fd4a 100644 --- a/media/java/android/media/Ringtone.java +++ b/media/java/android/media/Ringtone.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. @@ -20,8 +21,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.res.AssetFileDescriptor; import android.database.Cursor; -import android.media.AudioManager; -import android.media.MediaPlayer; import android.net.Uri; import android.provider.DrmStore; import android.provider.MediaStore; @@ -113,6 +112,19 @@ public class Ringtone { return mTitle = getTitle(context, mUri, true); } + private static String stringForQuery(Cursor cursor) { + if (cursor != null) { + try { + if (cursor.moveToFirst()) { + return cursor.getString(0); + } + } finally { + cursor.close(); + } + } + return null; + } + private static String getTitle(Context context, Uri uri, boolean followSettingsUri) { Cursor cursor = null; ContentResolver res = context.getContentResolver(); @@ -131,8 +143,15 @@ public class Ringtone { .getString(com.android.internal.R.string.ringtone_default_with_actual, actualTitle); } + } else if (RingtoneManager.THEME_AUTHORITY.equals(authority)) { + Uri themes = Uri.parse("content://com.tmobile.thememanager.themes/themes"); + title = stringForQuery(res.query(themes, new String[] { "ringtone_name" }, + "ringtone_uri = ?", new String[] { uri.toString() }, null)); + if (title == null) { + title = stringForQuery(res.query(themes, new String[] { "notif_ringtone_name" }, + "notif_ringtone_uri = ?", new String[] { uri.toString() }, null)); + } } else { - if (DrmStore.AUTHORITY.equals(authority)) { cursor = res.query(uri, DRM_COLUMNS, null, null, null); } else if (MediaStore.AUTHORITY.equals(authority)) { diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index 7aa4109..7d253f1 100644 --- a/media/java/android/media/RingtoneManager.java +++ b/media/java/android/media/RingtoneManager.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. @@ -25,7 +26,6 @@ import android.content.ContentUris; import android.app.ProfileGroup; import android.app.ProfileManager; import android.content.Context; -import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.net.Uri; import android.os.Environment; @@ -177,23 +177,28 @@ public class RingtoneManager { public static final String EXTRA_RINGTONE_PICKED_URI = "android.intent.extra.ringtone.PICKED_URI"; + /** + * @hide + */ + public static final String THEME_AUTHORITY = "com.tmobile.thememanager.packageresources"; + // Make sure the column ordering and then ..._COLUMN_INDEX are in sync private static final String[] INTERNAL_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, - "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\"", + "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "/\" || " + MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE_KEY }; private static final String[] DRM_COLUMNS = new String[] { DrmStore.Audio._ID, DrmStore.Audio.TITLE, - "\"" + DrmStore.Audio.CONTENT_URI + "\"", + "\"" + DrmStore.Audio.CONTENT_URI + "/\" || " + DrmStore.Audio._ID, DrmStore.Audio.TITLE + " AS " + MediaStore.Audio.Media.TITLE_KEY }; private static final String[] MEDIA_COLUMNS = new String[] { MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, - "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"", + "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/\" || " + MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE_KEY }; @@ -367,8 +372,11 @@ public class RingtoneManager { final Cursor internalCursor = getInternalRingtones(); final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null; final Cursor mediaCursor = getMediaRingtones(); - - return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor }, + final Cursor themeRegularCursor = getThemeRegularRingtones(); + final Cursor themeNotifCursor = getThemeNotificationRingtones(); + + return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor, + themeRegularCursor, themeNotifCursor }, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); } @@ -405,8 +413,7 @@ public class RingtoneManager { } private static Uri getUriFromCursor(Cursor cursor) { - return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor - .getLong(ID_COLUMN_INDEX)); + return Uri.parse(cursor.getString(URI_COLUMN_INDEX)); } /** @@ -426,23 +433,12 @@ public class RingtoneManager { return -1; } - // Only create Uri objects when the actual URI changes - Uri currentUri = null; - String previousUriString = null; for (int i = 0; i < cursorCount; i++) { - String uriString = cursor.getString(URI_COLUMN_INDEX); - if (currentUri == null || !uriString.equals(previousUriString)) { - currentUri = Uri.parse(uriString); - } - - if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor - .getLong(ID_COLUMN_INDEX)))) { + if (ringtoneUri.equals(getUriFromCursor(cursor))) { return i; } cursor.move(1); - - previousUriString = uriString; } return -1; @@ -468,6 +464,14 @@ public class RingtoneManager { uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones()); } + if (uri == null) { + uri = getValidRingtoneUriFromCursorAndClose(context, rm.getThemeRegularRingtones()); + } + + if (uri == null) { + uri = getValidRingtoneUriFromCursorAndClose(context, rm.getThemeNotificationRingtones()); + } + return uri; } @@ -513,6 +517,38 @@ public class RingtoneManager { : null; } + private String getThemeWhereClause(String uriColumn) { + /* Filter out themes with no ringtone and the default theme (which has no package). */ + String clause = uriColumn + " IS NOT NULL AND LENGTH(theme_package) > 0"; + if (mIncludeDrm) { + return clause; + } else { + return clause + " AND " + uriColumn + " NOT LIKE '%/assets/%locked%'"; + } + } + + private Cursor getThemeRegularRingtones() { + if ((mType & TYPE_RINGTONE) != 0) { + return query(Uri.parse("content://com.tmobile.thememanager.themes/themes"), + new String[] { "_id", "ringtone_name AS " + MEDIA_COLUMNS[1], "ringtone_uri", + "ringtone_name_key AS " + MEDIA_COLUMNS[3] }, + getThemeWhereClause("ringtone_uri"), null, MEDIA_COLUMNS[3]); + } else { + return null; + } + } + + private Cursor getThemeNotificationRingtones() { + if ((mType & TYPE_NOTIFICATION) != 0) { + return query(Uri.parse("content://com.tmobile.thememanager.themes/themes"), + new String[] { "_id", "notif_ringtone_name AS " + MEDIA_COLUMNS[1], "notif_ringtone_uri", + "notif_ringtone_name_key AS " + MEDIA_COLUMNS[3] }, + getThemeWhereClause("notif_ringtone_uri"), null, MEDIA_COLUMNS[3]); + } else { + return null; + } + } + private void setFilterColumnsList(int type) { List<String> columns = mFilterColumns; columns.clear(); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 95fd62d..e0f31b7 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.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. @@ -636,7 +637,8 @@ public class SettingsProvider extends ContentProvider { // Only proxy the openFile call to drm or media providers String authority = soundUri.getAuthority(); boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY); - if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) { + if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY) || + authority.equals(RingtoneManager.THEME_AUTHORITY)) { if (isDrmAuthority) { try { @@ -677,7 +679,8 @@ public class SettingsProvider extends ContentProvider { // Only proxy the openFile call to drm or media providers String authority = soundUri.getAuthority(); boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY); - if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) { + if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY) || + authority.equals(RingtoneManager.THEME_AUTHORITY)) { if (isDrmAuthority) { try { @@ -690,10 +693,8 @@ public class SettingsProvider extends ContentProvider { } } - ParcelFileDescriptor pfd = null; try { - pfd = context.getContentResolver().openFileDescriptor(soundUri, mode); - return new AssetFileDescriptor(pfd, 0, -1); + return context.getContentResolver().openAssetFileDescriptor(soundUri, mode); } catch (FileNotFoundException ex) { // fall through and open the fallback ringtone below } diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index cfc48ee..1325142 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -23,7 +23,8 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:background="@drawable/status_bar_background" - android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" android:focusable="true" android:descendantFocusability="afterDescendants" > diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java index 2110483..6862068 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUI.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java @@ -19,6 +19,8 @@ package com.android.systemui; import java.io.FileDescriptor; import java.io.PrintWriter; +import android.widget.FrameLayout; + import android.content.Context; import android.content.res.Configuration; @@ -26,6 +28,8 @@ public abstract class SystemUI { public Context mContext; public abstract void start(); + + public FrameLayout mStatusBarContainer; protected void onConfigurationChanged(Configuration newConfig) { } diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java index d7a5056..7fdafff 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -32,6 +32,8 @@ import android.os.ServiceManager; import android.util.Slog; import android.view.IWindowManager; +import android.widget.FrameLayout; + public class SystemUIService extends Service { static final String TAG = "SystemUIService"; @@ -89,7 +91,9 @@ public class SystemUIService extends Service { throw new RuntimeException(ex); } mServices[i].mContext = this; + mServices[i].mStatusBarContainer = new FrameLayout(this); Slog.d(TAG, "running: " + mServices[i]); + mServices[i].start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index 3d904ee..90ae038 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -107,6 +107,10 @@ public class NotificationData { return e; } + public void clear() { + mEntries.clear(); + } + /** * Return whether there are any visible items (i.e. items without an error). */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java index 2be35b7..371af53 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java @@ -63,6 +63,8 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac // First set up our views and stuff. View sb = makeStatusBarView(); + mStatusBarContainer.addView(sb); + // Connect in to the status bar manager service StatusBarIconList iconList = new StatusBarIconList(); ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); @@ -137,7 +139,7 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac lp.setTitle("StatusBar"); lp.packageName = mContext.getPackageName(); lp.windowAnimations = R.style.Animation_StatusBar; - WindowManagerImpl.getDefault().addView(sb, lp); + WindowManagerImpl.getDefault().addView(mStatusBarContainer, lp); if (SPEW) { Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index 8228df5..1f57bf7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -201,6 +201,10 @@ public class StatusBarIconView extends AnimatedImageView { } } + public String getStatusBarSlot() { + return mSlot; + } + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index d551ef5..ecad4ad 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -33,6 +33,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.database.ContentObserver; import android.graphics.PixelFormat; import android.graphics.Rect; @@ -48,6 +49,7 @@ import android.os.SystemClock; import android.provider.Settings; import android.text.TextUtils; import android.util.DisplayMetrics; +import android.util.Pair; import android.util.Slog; import android.util.Log; import android.view.Display; @@ -66,6 +68,7 @@ import android.view.WindowManagerImpl; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.ImageView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.ScrollView; @@ -235,6 +238,10 @@ public class PhoneStatusBar extends StatusBar { int mLinger = 0; Runnable mPostCollapseCleanup = null; + // last theme that was applied in order to detect theme change (as opposed + // to some other configuration change). + CustomTheme mCurrentTheme; + private boolean mRecreating = false; // for disabling the status bar int mDisabled = 0; @@ -300,6 +307,11 @@ public class PhoneStatusBar extends StatusBar { mWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); + CustomTheme currentTheme = mContext.getResources().getConfiguration().customTheme; + if (currentTheme != null) { + mCurrentTheme = (CustomTheme)currentTheme.clone(); + } + super.start(); // calls makeStatusBarView() addNavigationBar(); @@ -365,6 +377,11 @@ public class PhoneStatusBar extends StatusBar { mIcons = (LinearLayout)sb.findViewById(R.id.icons); mTickerView = sb.findViewById(R.id.ticker); + /* Destroy the old widget before recreating the expanded dialog + to make sure there are no context issues */ + if (mRecreating) + mPowerWidget.destroyWidget(); + mExpandedDialog = new ExpandedDialog(context); mExpandedView = expanded; mPile = (NotificationRowLayout)expanded.findViewById(R.id.latestItems); @@ -533,6 +550,12 @@ public class PhoneStatusBar extends StatusBar { private void repositionNavigationBar() { if (mNavigationBarView == null) return; + CustomTheme newTheme = mContext.getResources().getConfiguration().customTheme; + if (newTheme != null && + (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) { + // Nevermind, this will be re-created + return; + } prepareNavigationBarView(); WindowManagerImpl.getDefault().updateViewLayout( @@ -650,7 +673,7 @@ public class PhoneStatusBar extends StatusBar { notification.notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } - } else { + } else if (!mRecreating) { // usual case: status bar visible & not immersive // show the ticker @@ -1966,6 +1989,10 @@ public class PhoneStatusBar extends StatusBar { WindowManagerImpl.getDefault().addView(mTrackingView, lp); } + void onBarViewDetached() { + WindowManagerImpl.getDefault().removeView(mTrackingView); + } + void onTrackingViewAttached() { WindowManager.LayoutParams lp; int pixelFormat; @@ -2001,6 +2028,9 @@ public class PhoneStatusBar extends StatusBar { mExpandedDialog.show(); } + void onTrackingViewDetached() { + } + void setNotificationIconVisibility(boolean visible, int anim) { int old = mNotificationIcons.getVisibility(); int v = visible ? View.VISIBLE : View.INVISIBLE; @@ -2135,7 +2165,8 @@ public class PhoneStatusBar extends StatusBar { if (DEBUG) { Slog.d(TAG, "updateDisplaySize: " + mDisplayMetrics); } - updateExpandedSize(); + if (!mRecreating) + updateExpandedSize(); } void updateExpandedSize() { @@ -2337,6 +2368,59 @@ public class PhoneStatusBar extends StatusBar { mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE); } + private static void copyNotifications(ArrayList<Pair<IBinder, StatusBarNotification>> dest, + NotificationData source) { + int N = source.size(); + for (int i = 0; i < N; i++) { + NotificationData.Entry entry = source.get(i); + dest.add(Pair.create(entry.key, entry.notification)); + } + } + + private void recreateStatusBar() { + mRecreating = true; + mStatusBarContainer.removeAllViews(); + + // extract icons from the soon-to-be recreated viewgroup. + int nIcons = mStatusIcons.getChildCount(); + ArrayList<StatusBarIcon> icons = new ArrayList<StatusBarIcon>(nIcons); + ArrayList<String> iconSlots = new ArrayList<String>(nIcons); + for (int i = 0; i < nIcons; i++) { + StatusBarIconView iconView = (StatusBarIconView)mStatusIcons.getChildAt(i); + icons.add(iconView.getStatusBarIcon()); + iconSlots.add(iconView.getStatusBarSlot()); + } + + // extract notifications. + int nNotifs = mNotificationData.size(); + ArrayList<Pair<IBinder, StatusBarNotification>> notifications = + new ArrayList<Pair<IBinder, StatusBarNotification>>(nNotifs); + copyNotifications(notifications, mNotificationData); + mNotificationData.clear(); + + View newStatusBarView = makeStatusBarView(); + + // recreate StatusBarIconViews. + for (int i = 0; i < nIcons; i++) { + StatusBarIcon icon = icons.get(i); + String slot = iconSlots.get(i); + addIcon(slot, i, i, icon); + } + + // recreate notifications. + for (int i = 0; i < nNotifs; i++) { + Pair<IBinder, StatusBarNotification> notifData = notifications.get(i); + addNotificationViews(notifData.first, notifData.second); + } + + setAreThereNotifications(); + + mStatusBarContainer.addView(newStatusBarView); + + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + mRecreating = false; + } + /** * Reload some of our resources when the configuration changes. * @@ -2348,12 +2432,21 @@ public class PhoneStatusBar extends StatusBar { final Context context = mContext; final Resources res = context.getResources(); - if (mClearButton instanceof TextView) { - ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button)); - } - mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title)); + // detect theme change. + CustomTheme newTheme = res.getConfiguration().customTheme; + if (newTheme != null && + (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) { + mCurrentTheme = (CustomTheme)newTheme.clone(); + recreateStatusBar(); + } else { - loadDimens(); + if (mClearButton instanceof TextView) { + ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button)); + } + mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title)); + + loadDimens(); + } } protected void loadDimens() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index 809b742..0818370 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -90,7 +90,13 @@ public class PhoneStatusBarView extends FrameLayout { return mEndAlpha - (int)(((mEndAlpha-mStartAlpha) * (mEndTime-time) / DIM_ANIM_TIME)); } - + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mService.onBarViewDetached(); + } + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java index cc23afc..459c376 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java @@ -70,4 +70,10 @@ public class TrackingView extends LinearLayout { }); } } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mService.onTrackingViewDetached(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java b/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java index 17ee782..251d62a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java @@ -84,7 +84,7 @@ public class PowerWidget extends FrameLayout { updateVisibility(); } - public void setupWidget() { + public void destroyWidget() { Log.i(TAG, "Clearing any old widget stuffs"); // remove all views from the layout removeAllViews(); @@ -100,6 +100,10 @@ public class PowerWidget extends FrameLayout { // clear the button instances PowerButton.unloadAllButtons(); + } + + public void setupWidget() { + destroyWidget(); Log.i(TAG, "Setting up widget"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java index 5c1d363..4eadbcc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -36,6 +36,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.content.res.Resources; import android.inputmethodservice.InputMethodService; import android.graphics.PixelFormat; @@ -52,6 +53,7 @@ import android.os.ServiceManager; import android.os.storage.StorageManager; import android.provider.Settings; import android.text.TextUtils; +import android.util.Pair; import android.util.Slog; import android.view.accessibility.AccessibilityEvent; import android.view.Display; @@ -68,6 +70,7 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerImpl; import android.widget.ImageView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.ScrollView; @@ -206,6 +209,12 @@ public class TabletStatusBar extends StatusBar implements private StorageManager mStorageManager; + // last theme that was applied in order to detect theme change (as opposed + // to some other configuration change). + CustomTheme mCurrentTheme; + private boolean mRecreating = false; + + protected void addPanelWindows() { final Context context = mContext; final Resources res = mContext.getResources(); @@ -409,14 +418,54 @@ public class TabletStatusBar extends StatusBar implements super.start(); // will add the main bar view } + private static void copyNotifications(ArrayList<Pair<IBinder, StatusBarNotification>> dest, + NotificationData source) { + int N = source.size(); + for (int i = 0; i < N; i++) { + NotificationData.Entry entry = source.get(i); + dest.add(Pair.create(entry.key, entry.notification)); + } + } + + private void recreateStatusBar() { + mRecreating = true; + mStatusBarContainer.removeAllViews(); + + // extract notifications. + int nNotifs = mNotificationData.size(); + ArrayList<Pair<IBinder, StatusBarNotification>> notifications = + new ArrayList<Pair<IBinder, StatusBarNotification>>(nNotifs); + copyNotifications(notifications, mNotificationData); + mNotificationData.clear(); + + mStatusBarContainer.addView(makeStatusBarView()); + + // recreate notifications. + for (int i = 0; i < nNotifs; i++) { + Pair<IBinder, StatusBarNotification> notifData = notifications.get(i); + addNotificationViews(notifData.first, notifData.second); + } + + setAreThereNotifications(); + + mRecreating = false; + } + @Override protected void onConfigurationChanged(Configuration newConfig) { - mHeightReceiver.updateHeight(); // display size may have changed - loadDimens(); - mNotificationPanelParams.height = getNotificationPanelHeight(); - WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel, - mNotificationPanelParams); - mRecentsPanel.updateValuesFromResources(); + // detect theme change. + CustomTheme newTheme = mContext.getResources().getConfiguration().customTheme; + if (newTheme != null && + (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) { + mCurrentTheme = (CustomTheme)newTheme.clone(); + recreateStatusBar(); + } + mHeightReceiver.updateHeight(); // display size may have changed + loadDimens(); + mNotificationPanelParams.height = getNotificationPanelHeight(); + WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel, + mNotificationPanelParams); + mRecentsPanel.updateValuesFromResources(); } protected void loadDimens() { @@ -451,6 +500,11 @@ public class TabletStatusBar extends StatusBar implements mWindowManager = IWindowManager.Stub.asInterface( ServiceManager.getService(Context.WINDOW_SERVICE)); + CustomTheme currentTheme = mContext.getResources().getConfiguration().customTheme; + if (currentTheme != null) { + mCurrentTheme = (CustomTheme)currentTheme.clone(); + } + // This guy will listen for HDMI plugged broadcasts so we can resize the // status bar as appropriate. mHeightReceiver = new HeightReceiver(mContext); @@ -653,7 +707,7 @@ public class TabletStatusBar extends StatusBar implements public void onBarHeightChanged(int height) { final WindowManager.LayoutParams lp - = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams(); + = (WindowManager.LayoutParams)mStatusBarContainer.getLayoutParams(); if (lp == null) { // haven't been added yet return; @@ -661,7 +715,7 @@ public class TabletStatusBar extends StatusBar implements if (lp.height != height) { lp.height = height; final WindowManager wm = WindowManagerImpl.getDefault(); - wm.updateViewLayout(mStatusBarView, lp); + wm.updateViewLayout(mStatusBarContainer, lp); } } @@ -840,7 +894,7 @@ public class TabletStatusBar extends StatusBar implements notification.notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } - } else { + } else if (!mRecreating) { tick(key, notification, true); } diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index 231606e..9d627ad 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -1365,13 +1365,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { Context context = mContext; //Log.i(TAG, "addStartingWindow " + packageName + ": nonLocalizedLabel=" // + nonLocalizedLabel + " theme=" + Integer.toHexString(theme)); - if (theme != context.getThemeResId() || labelRes != 0) { - try { - context = context.createPackageContext(packageName, 0); + + + try { + context = context.createPackageContext(packageName, 0); + if (theme != context.getThemeResId()) { context.setTheme(theme); - } catch (PackageManager.NameNotFoundException e) { - // Ignore } + } catch (PackageManager.NameNotFoundException e) { + // Ignore } Window win = PolicyManager.makeNewWindow(context); diff --git a/services/java/com/android/server/AppsLaunchFailureReceiver.java b/services/java/com/android/server/AppsLaunchFailureReceiver.java new file mode 100644 index 0000000..6ef07aa --- /dev/null +++ b/services/java/com/android/server/AppsLaunchFailureReceiver.java @@ -0,0 +1,73 @@ +/* + * 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 com.android.server; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.CustomTheme; +import android.util.Log; +import android.app.ActivityManager; +import android.os.SystemClock; + +public class AppsLaunchFailureReceiver extends BroadcastReceiver { + + private static final int FAILURES_THRESHOLD = 5; + private static final int EXPIRATION_TIME_IN_MILLISECONDS = 30000; // 30 seconds + + private int mFailuresCount = 0; + private long mStartTime = 0; + + // This function implements the following logic. + // If after a theme was applied the number of application launch failures + // at any moment was equal to FAILURES_THRESHOLD + // in less than EXPIRATION_TIME_IN_MILLISECONDS + // the default theme is applied unconditionally. + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE)) { + long currentTime = SystemClock.uptimeMillis(); + if (currentTime - mStartTime > EXPIRATION_TIME_IN_MILLISECONDS) { + // reset both the count and the timer + mStartTime = currentTime; + mFailuresCount = 0; + } + if (mFailuresCount <= FAILURES_THRESHOLD) { + mFailuresCount++; + if (mFailuresCount == FAILURES_THRESHOLD) { + CustomTheme defaultTheme = CustomTheme.getSystemTheme(); + ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE); + Configuration currentConfig = am.getConfiguration(); + currentConfig.customTheme = new CustomTheme( + defaultTheme.getThemeId(), + defaultTheme.getThemePackageName()); + am.updateConfiguration(currentConfig); + } + } + } else if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE_RESET)) { + mFailuresCount = 0; + mStartTime = SystemClock.uptimeMillis(); + } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) || + action.equals(Intent.ACTION_PACKAGE_REMOVED)) { + mFailuresCount = 0; + mStartTime = SystemClock.uptimeMillis(); + } + } + +} diff --git a/services/java/com/android/server/AssetRedirectionManagerService.java b/services/java/com/android/server/AssetRedirectionManagerService.java new file mode 100644 index 0000000..1e124b9 --- /dev/null +++ b/services/java/com/android/server/AssetRedirectionManagerService.java @@ -0,0 +1,397 @@ +package com.android.server; + +import com.android.internal.app.IAssetRedirectionManager; +import com.android.internal.util.XmlUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ThemeInfo; +import android.content.res.AssetManager; +import android.content.res.PackageRedirectionMap; +import android.content.res.Resources; +import android.os.RemoteException; +import android.text.TextUtils; +import android.util.Log; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +public class AssetRedirectionManagerService extends IAssetRedirectionManager.Stub { + private static final String TAG = "AssetRedirectionManager"; + + private final Context mContext; + + /* + * TODO: This data structure should have some way to expire very old cache + * entries. Would be nice to optimize for the removal path as well. + */ + private final HashMap<RedirectionKey, PackageRedirectionMap> mRedirections = + new HashMap<RedirectionKey, PackageRedirectionMap>(); + + public AssetRedirectionManagerService(Context context) { + mContext = context; + } + + @Override + public void clearRedirectionMapsByTheme(String themePackageName, String themeId) + throws RemoteException { + synchronized (mRedirections) { + Set<RedirectionKey> keys = mRedirections.keySet(); + Iterator<RedirectionKey> iter = keys.iterator(); + while (iter.hasNext()) { + RedirectionKey key = iter.next(); + if (themePackageName.equals(key.themePackageName) && + (themeId == null || themeId.equals(key.themeId))) { + iter.remove(); + } + } + } + } + + @Override + public void clearPackageRedirectionMap(String targetPackageName) throws RemoteException { + synchronized (mRedirections) { + Set<RedirectionKey> keys = mRedirections.keySet(); + Iterator<RedirectionKey> iter = keys.iterator(); + while (iter.hasNext()) { + RedirectionKey key = iter.next(); + if (targetPackageName.equals(key.targetPackageName)) { + iter.remove(); + } + } + } + } + + @Override + public PackageRedirectionMap getPackageRedirectionMap(String themePackageName, + String themeId, String targetPackageName) throws RemoteException { + synchronized (mRedirections) { + RedirectionKey key = new RedirectionKey(); + key.themePackageName = themePackageName; + key.themeId = themeId; + key.targetPackageName = targetPackageName; + + PackageRedirectionMap map = mRedirections.get(key); + if (map != null) { + return map; + } else { + map = generatePackageRedirectionMap(key); + if (map != null) { + mRedirections.put(key, map); + } + return map; + } + } + } + + private PackageRedirectionMap generatePackageRedirectionMap(RedirectionKey key) { + AssetManager assets = new AssetManager(); + + boolean frameworkAssets = key.targetPackageName.equals("android"); + + if (!frameworkAssets) { + PackageInfo pi = getPackageInfo(mContext, key.targetPackageName); + if (pi == null || pi.applicationInfo == null || + assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) { + Log.w(TAG, "Unable to attach target package assets for " + key.targetPackageName); + return null; + } + } + + PackageInfo pi = getPackageInfo(mContext, key.themePackageName); + if (pi == null || pi.applicationInfo == null || pi.themeInfos == null || + assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) { + Log.w(TAG, "Unable to attach theme package assets from " + key.themePackageName); + return null; + } + + PackageRedirectionMap resMap = new PackageRedirectionMap(); + + /* + * Apply a special redirection hack for the highest level <style> + * replacing @android:style/Theme. + */ + if (frameworkAssets) { + int themeResourceId = findThemeResourceId(pi.themeInfos, key.themeId); + assets.generateStyleRedirections(resMap.getNativePointer(), android.R.style.Theme, + themeResourceId); + } + + Resources res = new Resources(assets, null, null); + generateExplicitRedirections(resMap, res, key.themePackageName, key.targetPackageName); + + return resMap; + } + + private void generateExplicitRedirections(PackageRedirectionMap resMap, Resources res, + String themePackageName, String targetPackageName) { + /* + * XXX: We should be parsing the <theme> tag's <meta-data>! Instead, + * we're just assuming that res/xml/<package>.xml exists and describes + * the redirects we want! + */ + String redirectXmlName = targetPackageName.replace('.', '_'); + int redirectXmlResId = res.getIdentifier(redirectXmlName, "xml", themePackageName); + if (redirectXmlResId == 0) { + return; + } + + ResourceRedirectionsProcessor processor = new ResourceRedirectionsProcessor(res, + redirectXmlResId, themePackageName, targetPackageName, resMap); + processor.process(); + } + + private static PackageInfo getPackageInfo(Context context, String packageName) { + try { + return context.getPackageManager().getPackageInfo(packageName, 0); + } catch (NameNotFoundException e) { + return null; + } + } + + /** + * Searches for the high-level theme resource id for the specific + * <theme> tag being applied. + * <p> + * An individual theme package can contain multiple <theme> tags, each + * representing a separate theme choice from the user's perspective, even + * though the most common case is for there to be only 1. + * + * @return The style resource id or 0 if no match was found. + */ + private static int findThemeResourceId(ThemeInfo[] themeInfos, String needle) { + if (themeInfos != null && !TextUtils.isEmpty(needle)) { + int n = themeInfos.length; + for (int i = 0; i < n; i++) { + ThemeInfo info = themeInfos[i]; + if (needle.equals(info.themeId)) { + return info.styleResourceId; + } + } + } + return 0; + } + + private static Resources getUnredirectedResourcesForPackage(Context context, String packageName) { + AssetManager assets = new AssetManager(); + + if (!packageName.equals("android")) { + PackageInfo pi = getPackageInfo(context, packageName); + if (pi == null || pi.applicationInfo == null || + assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) { + Log.w(TAG, "Unable to get resources for package " + packageName); + return null; + } + } + + return new Resources(assets, null, null); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + synchronized (mRedirections) { + final ArrayList<RedirectionKey> filteredKeySet = new ArrayList<RedirectionKey>(); + for (Map.Entry<RedirectionKey, PackageRedirectionMap> entry: mRedirections.entrySet()) { + PackageRedirectionMap map = entry.getValue(); + if (map != null && map.getPackageId() != -1) { + filteredKeySet.add(entry.getKey()); + } + } + Collections.sort(filteredKeySet, new Comparator<RedirectionKey>() { + @Override + public int compare(RedirectionKey a, RedirectionKey b) { + int comp = a.themePackageName.compareTo(b.themePackageName); + if (comp != 0) { + return comp; + } + comp = a.themeId.compareTo(b.themeId); + if (comp != 0) { + return comp; + } + return a.targetPackageName.compareTo(b.targetPackageName); + } + }); + + pw.println("Theme asset redirections:"); + String lastPackageName = null; + String lastId = null; + Resources themeRes = null; + for (RedirectionKey key: filteredKeySet) { + if (lastPackageName == null || !lastPackageName.equals(key.themePackageName)) { + pw.println("* Theme package " + key.themePackageName + ":"); + lastPackageName = key.themePackageName; + themeRes = getUnredirectedResourcesForPackage(mContext, key.themePackageName); + } + if (lastId == null || !lastId.equals(key.themeId)) { + pw.println(" theme id #" + key.themeId + ":"); + lastId = key.themeId; + } + pw.println(" " + key.targetPackageName + ":"); + Resources targetRes = getUnredirectedResourcesForPackage(mContext, key.targetPackageName); + PackageRedirectionMap resMap = mRedirections.get(key); + int[] fromIdents = resMap.getRedirectionKeys(); + int N = fromIdents.length; + for (int i = 0; i < N; i++) { + int fromIdent = fromIdents[i]; + int toIdent = resMap.lookupRedirection(fromIdent); + String fromName = targetRes != null ? targetRes.getResourceName(fromIdent) : null; + String toName = themeRes != null ? themeRes.getResourceName(toIdent) : null; + pw.println(String.format(" %s (0x%08x) => %s (0x%08x)", fromName, fromIdent, + toName, toIdent)); + } + } + } + } + + private static class RedirectionKey { + public String themePackageName; + public String themeId; + public String targetPackageName; + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if (!(o instanceof RedirectionKey)) return false; + final RedirectionKey oo = (RedirectionKey)o; + if (!nullSafeEquals(themePackageName, oo.themePackageName)) { + return false; + } + if (!nullSafeEquals(themeId, oo.themeId)) { + return false; + } + if (!nullSafeEquals(targetPackageName, oo.targetPackageName)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return themePackageName.hashCode() + + themeId.hashCode() + + targetPackageName.hashCode(); + } + + private static boolean nullSafeEquals(Object a, Object b) { + if (a == null) { + return b == a; + } else if (b == null) { + return false; + } else { + return a.equals(b); + } + } + } + + /** + * Parses and processes explicit redirection XML files. + */ + private static class ResourceRedirectionsProcessor { + private final Resources mResources; + private final XmlPullParser mParser; + private final int mResourceId; + private final String mThemePackageName; + private final String mTargetPackageName; + private final PackageRedirectionMap mResMap; + + public ResourceRedirectionsProcessor(Resources res, int resourceId, + String themePackageName, String targetPackageName, + PackageRedirectionMap outMap) { + mResources = res; + mParser = res.getXml(resourceId); + mResourceId = resourceId; + mThemePackageName = themePackageName; + mTargetPackageName = targetPackageName; + mResMap = outMap; + } + + public void process() { + XmlPullParser parser = mParser; + int type; + try { + while ((type = parser.next()) != XmlPullParser.START_TAG + && type != XmlPullParser.END_DOCUMENT) { + // just loop... + } + + String tagName = parser.getName(); + if (parser.getName().equals("resource-redirections")) { + processResourceRedirectionsTag(); + } else { + Log.w(TAG, "Unknown root element: " + tagName + " at " + getResourceLabel() + " " + + parser.getPositionDescription()); + } + } catch (XmlPullParserException e) { + Log.w(TAG, "Malformed theme redirection meta at " + getResourceLabel()); + } catch (IOException e) { + Log.w(TAG, "Unknown error reading redirection meta at " + getResourceLabel()); + } + } + + private void processResourceRedirectionsTag() throws XmlPullParserException, IOException { + XmlPullParser parser = mParser; + int type; + final int innerDepth = parser.getDepth(); + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && + (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + + String tagName = parser.getName(); + if (tagName.equals("item")) { + processItemTag(); + } else { + Log.w(TAG, "Unknown element under <resource-redirections>: " + tagName + + " at " + getResourceLabel() + " " + + parser.getPositionDescription()); + XmlUtils.skipCurrentTag(parser); + continue; + } + } + } + + private void processItemTag() throws XmlPullParserException, IOException { + XmlPullParser parser = mParser; + String fromName = parser.getAttributeValue(null, "name"); + if (TextUtils.isEmpty(fromName)) { + Log.w(TAG, "Missing android:name attribute on <item> tag at " + getResourceLabel() + " " + + parser.getPositionDescription()); + return; + } + String toName = parser.nextText(); + if (TextUtils.isEmpty(toName)) { + Log.w(TAG, "Missing <item> text at " + getResourceLabel() + " " + + parser.getPositionDescription()); + return; + } + int fromIdent = mResources.getIdentifier(fromName, null, mTargetPackageName); + if (fromIdent == 0) { + Log.w(TAG, "No such resource found for " + mTargetPackageName + ":" + fromName); + return; + } + int toIdent = mResources.getIdentifier(toName, null, mThemePackageName); + if (toIdent == 0) { + Log.w(TAG, "No such resource found for " + mThemePackageName + ":" + toName); + return; + } + mResMap.addRedirection(fromIdent, toIdent); + } + + private String getResourceLabel() { + return "resource #0x" + Integer.toHexString(mResourceId); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index d8ea9bf..ad78a3c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -1,6 +1,7 @@ /* * Copyright (C) 2006 The Android Open Source Project * Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved. + * 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. @@ -25,6 +26,7 @@ import android.content.ContentResolver; import android.content.ContentService; import android.content.Context; import android.content.Intent; +import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.res.Configuration; import android.database.ContentObserver; @@ -601,6 +603,13 @@ class ServerThread extends Thread { } catch (Throwable e) { reportWtf("starting NetworkTimeUpdate service", e); } + + try { + Slog.i(TAG, "AssetRedirectionManager Service"); + ServiceManager.addService("assetredirection", new AssetRedirectionManagerService(context)); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting AssetRedirectionManager Service", e); + } } // make sure the ADB_ENABLED setting value matches the secure property value @@ -670,6 +679,15 @@ class ServerThread extends Thread { reportWtf("making Package Manager Service ready", e); } + IntentFilter filter = new IntentFilter(); + filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE); + filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE_RESET); + filter.addAction(Intent.ACTION_PACKAGE_ADDED); + filter.addAction(Intent.ACTION_PACKAGE_REMOVED); + filter.addCategory(Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE); + filter.addDataScheme("package"); + context.registerReceiver(new AppsLaunchFailureReceiver(), filter); + // These are needed to propagate to the runnable below. final Context contextF = context; final BatteryService batteryF = battery; diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index cffb391..2d06e8c 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -1,5 +1,6 @@ /* * Copyright (C) 2006-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. @@ -78,6 +79,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.graphics.Bitmap; import android.net.Proxy; import android.net.ProxyProperties; @@ -147,6 +149,7 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; +import dalvik.system.Zygote; public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { @@ -13462,6 +13465,11 @@ public final class ActivityManagerService extends ActivityManagerNative values.userSetLocale); } + if (values.customTheme != null) { + saveThemeResourceLocked(values.customTheme, + !values.customTheme.equals(mConfiguration.customTheme)); + } + mConfigurationSeq++; if (mConfigurationSeq <= 0) { mConfigurationSeq = 1; @@ -13554,6 +13562,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void saveThemeResourceLocked(CustomTheme t, boolean isDiff){ + if(isDiff){ + SystemProperties.set(Configuration.THEME_ID_PERSISTENCE_PROPERTY, t.getThemeId()); + SystemProperties.set(Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY, t.getThemePackageName()); + } + } + // ========================================================= // LIFETIME MANAGEMENT // ========================================================= diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 0eeb377..8719e8e 100644 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.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. @@ -22,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static libcore.io.OsConstants.S_ISLNK; +import com.android.internal.app.IAssetRedirectionManager; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; @@ -183,6 +185,8 @@ public class PackageManagerService extends IPackageManager.Stub { // package apks to install directory. private static final String INSTALL_PACKAGE_SUFFIX = "-"; + private static final int THEME_MAMANER_GUID = 1300; + static final int SCAN_MONITOR = 1<<0; static final int SCAN_NO_DEX = 1<<1; static final int SCAN_FORCE_DEX = 1<<2; @@ -374,6 +378,8 @@ public class PackageManagerService extends IPackageManager.Stub { ComponentName mResolveComponentName; PackageParser.Package mPlatformPackage; + IAssetRedirectionManager mAssetRedirectionManager; + // Set of pending broadcasts for aggregating enable/disable of components. final HashMap<String, ArrayList<String>> mPendingBroadcasts = new HashMap<String, ArrayList<String>>(); @@ -663,22 +669,26 @@ public class PackageManagerService extends IPackageManager.Stub { PackageInstalledInfo res = data.res; if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - res.removedInfo.sendBroadcast(false, true); + res.removedInfo.sendBroadcast(false, true, false); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); final boolean update = res.removedInfo.removedPackage != null; if (update) { extras.putBoolean(Intent.EXTRA_REPLACING, true); } + String category = null; + if(res.pkg.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - res.pkg.applicationInfo.packageName, + res.pkg.applicationInfo.packageName, category, extras, null, null); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - res.pkg.applicationInfo.packageName, + res.pkg.applicationInfo.packageName, category, extras, null, null); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, - null, null, + null, null, null, res.pkg.applicationInfo.packageName, null); } if (res.removedInfo.args != null) { @@ -881,6 +891,9 @@ public class PackageManagerService extends IPackageManager.Stub { MULTIPLE_APPLICATION_UIDS ? LOG_UID : FIRST_APPLICATION_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLPw("com.tmobile.thememanager", + THEME_MAMANER_GUID, + ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.nfc", MULTIPLE_APPLICATION_UIDS ? NFC_UID : FIRST_APPLICATION_UID, @@ -2586,6 +2599,20 @@ public class PackageManagerService extends IPackageManager.Stub { return list; } + public List<PackageInfo> getInstalledThemePackages() { + // Returns a list of theme APKs. + ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>(); + List<PackageInfo> installedPackagesList = mContext.getPackageManager().getInstalledPackages(0); + Iterator<PackageInfo> i = installedPackagesList.iterator(); + while (i.hasNext()) { + final PackageInfo pi = i.next(); + if (pi != null && pi.isThemeApk) { + finalList.add(pi); + } + } + return finalList; + } + public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, String lastRead) { final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>(); @@ -3952,6 +3979,32 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // NOTE: this method can return null if the SystemServer is still + // initializing + public IAssetRedirectionManager getAssetRedirectionManager() { + if (mAssetRedirectionManager != null) { + return mAssetRedirectionManager; + } + IBinder b = ServiceManager.getService("assetredirection"); + mAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b); + return mAssetRedirectionManager; + } + + private void cleanAssetRedirections(PackageParser.Package pkg) { + IAssetRedirectionManager rm = getAssetRedirectionManager(); + if (rm == null) { + return; + } + try { + if (pkg.mIsThemeApk) { + rm.clearRedirectionMapsByTheme(pkg.packageName, null); + } else { + rm.clearPackageRedirectionMap(pkg.packageName); + } + } catch (RemoteException e) { + } + } + void removePackageLI(PackageParser.Package pkg, boolean chatty) { if (DEBUG_INSTALL) { if (chatty) @@ -3960,6 +4013,8 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { + cleanAssetRedirections(pkg); + clearPackagePreferredActivitiesLPw(pkg.packageName); mPackages.remove(pkg.applicationInfo.packageName); @@ -4742,7 +4797,7 @@ public class PackageManagerService extends IPackageManager.Stub { } }; - static final void sendPackageBroadcast(String action, String pkg, + static final void sendPackageBroadcast(String action, String pkg, String intentCategory, Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) { IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { @@ -4756,6 +4811,9 @@ public class PackageManagerService extends IPackageManager.Stub { intent.setPackage(targetPkg); } intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); + if (intentCategory != null) { + intent.addCategory(intentCategory); + } am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, finishedReceiver != null, false); } catch (RemoteException ex) { @@ -4826,6 +4884,7 @@ public class PackageManagerService extends IPackageManager.Stub { int removedUid = -1; String addedPackage = null; int addedUid = -1; + String category = null; // TODO post a message to the handler to obtain serial ordering synchronized (mInstallLock) { @@ -4857,6 +4916,9 @@ public class PackageManagerService extends IPackageManager.Stub { } if ((event&REMOVE_EVENTS) != 0) { if (p != null) { + if (p.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } removePackageLI(p, true); removedPackage = p.applicationInfo.packageName; removedUid = p.applicationInfo.uid; @@ -4887,6 +4949,9 @@ public class PackageManagerService extends IPackageManager.Stub { addedUid = p.applicationInfo.uid; } } + if (p != null && p.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } } // reader @@ -4899,13 +4964,13 @@ public class PackageManagerService extends IPackageManager.Stub { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedUid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category, extras, null, null); } if (addedPackage != null) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, addedUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, category, extras, null, null); } } @@ -6591,6 +6656,23 @@ public class PackageManagerService extends IPackageManager.Stub { } else { Log.d(TAG, "New package installed in " + newPackage.mPath); } + cleanAssetRedirections(newPackage); + + if (newPackage.mIsThemeApk) { + /* DBS-TODO + boolean isThemePackageDrmProtected = false; + int N = newPackage.mThemeInfos.size(); + for (int i = 0; i < N; i++) { + if (newPackage.mThemeInfos.get(i).isDrmProtected) { + isThemePackageDrmProtected = true; + break; + } + } + if (isThemePackageDrmProtected) { + splitThemePackage(newPackage.mPath); + } + */ + } synchronized (mPackages) { updatePermissionsLPw(newPackage.packageName, newPackage, newPackage.permissions.size() > 0, true, false); @@ -6605,6 +6687,66 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private void deleteLockedZipFileIfExists(String originalPackagePath) { + String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath); + File zipFile = new File(lockedZipFilePath); + if (zipFile.exists() && zipFile.isFile()) { + if (!zipFile.delete()) { + Log.w(TAG, "Couldn't delete locked zip file: " + originalPackagePath); + } + } + } + + private void splitThemePackage(File originalFile) { + final String originalPackagePath = originalFile.getPath(); + final String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath); + + try { + final List<String> drmProtectedEntries = new ArrayList<String>(); + final ZipFile privateZip = new ZipFile(originalFile.getPath()); + + final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries(); + while (privateZipEntries.hasMoreElements()) { + final ZipEntry zipEntry = privateZipEntries.nextElement(); + final String zipEntryName = zipEntry.getName(); + if (zipEntryName.startsWith("assets/") && zipEntryName.contains("/locked/")) { + drmProtectedEntries.add(zipEntryName); + } + } + privateZip.close(); + + String [] args = new String[0]; + args = drmProtectedEntries.toArray(args); + int code = mContext.getAssets().splitDrmProtectedThemePackage( + originalPackagePath, + lockedZipFilePath, + args); + if (code != 0) { + Log.e("PackageManagerService", + "splitDrmProtectedThemePackage returned = " + code); + } + code = FileUtils.setPermissions( + lockedZipFilePath, + 0640, + -1, + THEME_MAMANER_GUID); + if (code != 0) { + Log.e("PackageManagerService", + "Set permissions for " + lockedZipFilePath + " returned = " + code); + } + code = FileUtils.setPermissions( + originalPackagePath, + 0644, + -1, -1); + if (code != 0) { + Log.e("PackageManagerService", + "Set permissions for " + originalPackagePath + " returned = " + code); + } + } catch (IOException e) { + Log.e(TAG, "Failure to generate new zip files for theme"); + } + } + private void installPackageLI(InstallArgs args, boolean newInstall, PackageInstalledInfo res) { int pFlags = args.flags; @@ -6915,7 +7057,18 @@ public class PackageManagerService extends IPackageManager.Stub { } } catch (RemoteException e) { } - + + synchronized (mPackages) { + PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + info.isThemeApk = p.mIsThemeApk; + if (info.isThemeApk && deleteCodeAndResources && + !info.isRemovedPackageSystemUpdate && sendBroadCast) { + deleteLockedZipFileIfExists(p.mPath); + } + } + } + synchronized (mInstallLock) { res = deletePackageLI(packageName, deleteCodeAndResources, flags | REMOVE_CHATTY, info, true); @@ -6923,7 +7076,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (res && sendBroadCast) { boolean systemUpdate = info.isRemovedPackageSystemUpdate; - info.sendBroadcast(deleteCodeAndResources, systemUpdate); + info.sendBroadcast(deleteCodeAndResources, systemUpdate, true); // If the removed package was a system update, the old system packaged // was re-enabled; we need to broadcast this information @@ -6932,11 +7085,16 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putInt(Intent.EXTRA_UID, info.removedUid >= 0 ? info.removedUid : info.uid); extras.putBoolean(Intent.EXTRA_REPLACING, true); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, + String category = null; + if (info.isThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, category, extras, null, null); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, category, extras, null, null); - sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, null, packageName, null); } } @@ -6960,8 +7118,10 @@ public class PackageManagerService extends IPackageManager.Stub { boolean isRemovedPackageSystemUpdate = false; // Clean up resources deleted packages. InstallArgs args = null; + boolean isThemeApk = false; - void sendBroadcast(boolean fullRemove, boolean replacing) { + void sendBroadcast(boolean fullRemove, boolean replacing, + boolean deleteLockedZipFileIfExists) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedUid >= 0 ? removedUid : uid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove); @@ -6969,15 +7129,19 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putBoolean(Intent.EXTRA_REPLACING, true); } if (removedPackage != null) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, + String category = null; + if (isThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category, extras, null, null); if (fullRemove && !replacing) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, + sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, category, extras, null, null); } } if (removedUid >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null); + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, null, extras, null, null); } } } @@ -7684,7 +7848,7 @@ public class PackageManagerService extends IPackageManager.Stub { extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList); extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag); extras.putInt(Intent.EXTRA_UID, packageUid); - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null); + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, null, extras, null, null); } public void setPackageStoppedState(String packageName, boolean stopped) { @@ -8240,7 +8404,7 @@ public class PackageManagerService extends IPackageManager.Stub { } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; - sendPackageBroadcast(action, null, extras, null, finishedReceiver); + sendPackageBroadcast(action, null, null, extras, null, finishedReceiver); } } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 36442a0..46f10d2 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -2010,7 +2010,7 @@ final class Settings { if (pkgSetting.notLaunched) { if (pkgSetting.installerPackageName != null) { PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, + pkgSetting.name, null, null, pkgSetting.installerPackageName, null); } pkgSetting.notLaunched = false; @@ -2261,4 +2261,4 @@ final class Settings { pw.println("Settings parse messages:"); pw.print(mReadMessages.toString()); } -}
\ No newline at end of file +} diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 58680ea..567aa21 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -554,6 +554,14 @@ public class MockPackageManager extends PackageManager { } /** + * @hide - to match hiding in superclass + */ + @Override + public List<PackageInfo> getInstalledThemePackages() { + throw new UnsupportedOperationException(); + } + + /** * @hide */ @Override diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index 2d1060b..7ea277a 100644 --- a/tools/aapt/Bundle.h +++ b/tools/aapt/Bundle.h @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // State bundle. Used to pass around stuff like command-line args. // @@ -36,7 +37,7 @@ public: Bundle(void) : mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false), mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false), - mUpdate(false), mExtending(false), + mUpdate(false), mExtending(false), mExtendedPackageId(0), mRequireLocalization(false), mPseudolocalize(false), mWantUTF16(false), mValues(false), mCompressionMethod(0), mOutputAPKFile(NULL), @@ -78,6 +79,8 @@ public: void setUpdate(bool val) { mUpdate = val; } bool getExtending(void) const { return mExtending; } void setExtending(bool val) { mExtending = val; } + int getExtendedPackageId(void) const { return mExtendedPackageId; } + void setExtendedPackageId(int val) { mExtendedPackageId = val; } bool getRequireLocalization(void) const { return mRequireLocalization; } void setRequireLocalization(bool val) { mRequireLocalization = val; } bool getPseudolocalize(void) const { return mPseudolocalize; } @@ -226,6 +229,7 @@ private: bool mMakePackageDirs; bool mUpdate; bool mExtending; + int mExtendedPackageId; bool mRequireLocalization; bool mPseudolocalize; bool mWantUTF16; diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp index 50c828d..77d5dd6 100644 --- a/tools/aapt/Main.cpp +++ b/tools/aapt/Main.cpp @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // Android Asset Packaging Tool main entry point. // @@ -14,6 +15,7 @@ #include <stdlib.h> #include <getopt.h> #include <assert.h> +#include <ctype.h> using namespace android; @@ -55,7 +57,7 @@ void usage(void) " xmltree Print the compiled xmls in the given assets.\n" " xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName); fprintf(stderr, - " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n" + " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n" " [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n" " [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n" " [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n" @@ -116,7 +118,7 @@ void usage(void) #endif " -u update existing packages (add new, replace older, remove deleted files)\n" " -v verbose output\n" - " -x create extending (non-application) resource IDs\n" + " -x either create or assign (if specified) extending (non-application) resource IDs\n" " -z require localization of resource attributes marked with\n" " localization=\"suggested\"\n" " -A additional directory in which to find raw asset files\n" @@ -305,6 +307,14 @@ int main(int argc, char* const argv[]) break; case 'x': bundle.setExtending(true); + argc--; + argv++; + if (!argc || !isdigit(argv[0][0])) { + argc++; + argv--; + } else { + bundle.setExtendedPackageId(atoi(argv[0])); + } break; case 'z': bundle.setRequireLocalization(true); diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp index fdb39ca..df117db 100644 --- a/tools/aapt/ResourceTable.cpp +++ b/tools/aapt/ResourceTable.cpp @@ -1,5 +1,6 @@ // // Copyright 2006 The Android Open Source Project +// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc. // // Build resource files from raw assets. // @@ -3620,7 +3621,16 @@ sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package) mHaveAppPackage = true; p = new Package(package, 127); } else { - p = new Package(package, mNextPackageId); + int extendedPackageId = mBundle->getExtendedPackageId(); + if (extendedPackageId != 0) { + if ((uint32_t)extendedPackageId < mNextPackageId) { + fprintf(stderr, "Package ID %d already in use!\n", mNextPackageId); + return NULL; + } + p = new Package(package, extendedPackageId); + } else { + p = new Package(package, mNextPackageId); + } } //printf("*** NEW PACKAGE: \"%s\" id=%d\n", // String8(package).string(), p->getAssignedId()); |