diff options
75 files changed, 4504 insertions, 208 deletions
@@ -170,6 +170,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 594be68..e4e0d8e 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. @@ -28,6 +29,7 @@ import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.PackageManager; import android.content.pm.UserInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Point; @@ -1620,6 +1622,16 @@ public class ActivityManager { return null; } } + /** + * @hide + */ + public Configuration getConfiguration() { + try { + return ActivityManagerNative.getDefault().getConfiguration(); + } catch (RemoteException e) { + return null; + } + } /** * Returns a list of application processes that are running on the device. @@ -1986,4 +1998,17 @@ public class ActivityManager { return false; } } + + /** + * @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 456d757..3c13fbb 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,15 +17,23 @@ 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; @@ -37,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; @@ -50,6 +61,7 @@ import android.net.Proxy; import android.net.ProxyProperties; import android.opengl.GLUtils; import android.os.AsyncTask; +import android.net.Uri; import android.os.Binder; import android.os.Bundle; import android.os.Debug; @@ -69,6 +81,7 @@ import android.os.SystemClock; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; +import android.text.TextUtils; import android.util.AndroidRuntimeException; import android.util.DisplayMetrics; import android.util.EventLog; @@ -79,6 +92,7 @@ import android.util.Slog; import android.view.CompatibilityInfoHolder; import android.view.Display; import android.view.HardwareRenderer; +import android.view.InflateException; import android.view.View; import android.view.ViewDebug; import android.view.ViewManager; @@ -88,13 +102,8 @@ import android.view.WindowManager; import android.view.WindowManagerGlobal; import android.renderscript.RenderScript; -import com.android.internal.os.BinderInternal; -import com.android.internal.os.RuntimeInit; -import com.android.internal.os.SamplingProfilerIntegration; import com.android.internal.util.Objects; -import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; - import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -161,6 +170,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(); @@ -1510,9 +1520,10 @@ public final class ActivityThread { final private int mDisplayId; final private Configuration mOverrideConfiguration; final private float mScale; + final private boolean mIsThemeable; final private int mHash; - ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale) { + ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, float scale, boolean isThemeable) { mResDir = resDir; mDisplayId = displayId; if (overrideConfiguration != null) { @@ -1522,12 +1533,14 @@ public final class ActivityThread { } mOverrideConfiguration = overrideConfiguration; mScale = scale; + mIsThemeable = isThemeable; int hash = 17; hash = 31 * hash + mResDir.hashCode(); hash = 31 * hash + mDisplayId; hash = 31 * hash + (mOverrideConfiguration != null ? mOverrideConfiguration.hashCode() : 0); hash = 31 * hash + Float.floatToIntBits(mScale); + hash = 31 * hash + (mIsThemeable ? 1 : 0); mHash = hash; } @@ -1559,7 +1572,7 @@ public final class ActivityThread { if (mScale != peer.mScale) { return false; } - return true; + return mIsThemeable == peer.mIsThemeable; } } @@ -1594,6 +1607,18 @@ public final class ActivityThread { mDefaultDisplayMetrics.clear(); } + // 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(int displayId, CompatibilityInfo ci) { boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = isDefaultDisplay ? mDefaultDisplayMetrics.get(ci) : null; @@ -1657,7 +1682,8 @@ public final class ActivityThread { CompatibilityInfo compInfo) { ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfiguration, - compInfo.applicationScale); + compInfo.applicationScale, + compInfo.isThemeable); Resources r; synchronized (mPackages) { // Resources is app scale dependent. @@ -1683,6 +1709,7 @@ public final class ActivityThread { //} AssetManager assets = new AssetManager(); + assets.setThemeSupport(compInfo.isThemeable); if (assets.addAssetPath(resDir) == 0) { return null; } @@ -1702,6 +1729,18 @@ public final class ActivityThread { } else { config = getConfiguration(); } + + /* Attach theme information to the resulting AssetManager when appropriate. */ + if (compInfo.isThemeable && config != null) { + if (config.customTheme == null) { + config.customTheme = CustomTheme.getBootTheme(); + } + + if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) { + attachThemeAssets(assets, config.customTheme); + } + } + r = new Resources(assets, dm, config, compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " @@ -1725,6 +1764,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, 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. */ @@ -2177,6 +2291,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); @@ -3817,7 +3941,7 @@ public final class ActivityThread { } } - final boolean applyConfigurationToResourcesLocked(Configuration config, + final int applyConfigurationToResourcesLocked(Configuration config, CompatibilityInfo compat) { if (mResConfiguration == null) { mResConfiguration = new Configuration(); @@ -3825,7 +3949,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); flushDisplayMetricsLocked(); @@ -3864,6 +3988,16 @@ public final class ActivityThread { boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); DisplayMetrics dm = defaultDisplayMetrics; Configuration overrideConfig = entry.getKey().mOverrideConfiguration; + 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); + } + } + } if (!isDefaultDisplay || overrideConfig != null) { if (tmpConfig == null) { tmpConfig = new Configuration(); @@ -3880,6 +4014,9 @@ public final class ActivityThread { } else { r.updateConfiguration(config, dm, compat); } + if (themeChanged) { + r.updateStringCache(); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { @@ -3888,7 +4025,7 @@ public final class ActivityThread { } } - return changes != 0; + return changes; } final void applyNonDefaultDisplayMetricsToConfigurationLocked( @@ -3930,6 +4067,8 @@ public final class ActivityThread { int configDiff = 0; + int diff = 0; + synchronized (mPackages) { if (mPendingConfiguration != null) { if (!mPendingConfiguration.isOtherSeqNewer(config)) { @@ -3947,7 +4086,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(); @@ -3970,7 +4109,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 ContextWrapper) { + Context context = ((ContextWrapper)cb).getBaseContext(); + if (context instanceof ContextImpl) { + ((ContextImpl)context).refreshResourcesIfNecessary(); + } + } + } + + performConfigurationChanged(cb, config); } } } @@ -4937,7 +5089,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 7431765..b93771f 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -429,6 +429,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) { final int userId = mContext.getUserId(); try { diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 20ed560..8f2ab62 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. @@ -19,6 +20,8 @@ package android.app; import com.android.internal.policy.PolicyManager; import com.android.internal.util.Preconditions; +import android.accounts.AccountManager; +import android.accounts.IAccountManager; import android.bluetooth.BluetoothAdapter; import android.content.BroadcastReceiver; import android.content.ComponentName; @@ -26,9 +29,9 @@ 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; @@ -40,6 +43,7 @@ import android.content.pm.PackageManager.NameNotFoundException; 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; @@ -567,6 +571,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 cf0603e..5f6aa83 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. @@ -2465,6 +2466,19 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_QUICK_CLOCK = "android.intent.action.QUICK_CLOCK"; + /** + * 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()). @@ -2597,6 +2611,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 @@ -2633,6 +2648,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/SyncManager.java b/core/java/android/content/SyncManager.java index 977b461..bc8f18b 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -16,6 +16,7 @@ package android.content; +import com.android.internal.app.ThemeUtils; import android.accounts.Account; import android.accounts.AccountAndUser; import android.accounts.AccountManager; @@ -139,6 +140,7 @@ public class SyncManager { private static final int MAX_SIMULTANEOUS_INITIALIZATION_SYNCS; private Context mContext; + private Context mUiContext; private static final AccountAndUser[] INITIAL_ACCOUNTS_ARRAY = new AccountAndUser[0]; @@ -197,6 +199,12 @@ public class SyncManager { } }; + private BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { public void onReceive(Context context, Intent intent) { if (getConnectivityManager().getBackgroundDataSetting()) { @@ -403,6 +411,8 @@ public class SyncManager { mContext.registerReceiverAsUser( mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); + ThemeUtils.registerThemeChangeReceiver(mContext, mThemeChangeReceiver); + if (!factoryTest) { mNotificationMgr = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); @@ -937,6 +947,13 @@ public class SyncManager { } } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + /** * @hide */ @@ -2566,7 +2583,7 @@ public class SyncManager { new Notification(R.drawable.stat_notify_sync_error, mContext.getString(R.string.contentServiceSync), System.currentTimeMillis()); - notification.setLatestEventInfo(mContext, + notification.setLatestEventInfo(getUiContext(), mContext.getString(R.string.contentServiceSyncNotificationTitle), String.format(tooManyDeletesDescFormat.toString(), authorityName), pendingIntent); diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index e2ca1dd..f1d678c 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. @@ -341,6 +342,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 32cc7fd..fa85145 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. @@ -444,6 +445,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. @@ -555,6 +580,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { descriptionRes = orig.descriptionRes; uiOptions = orig.uiOptions; backupAgentName = orig.backupAgentName; + isThemeable = orig.isThemeable; } @@ -594,6 +620,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 @@ -632,6 +659,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 b0ae5da..ab27c8a 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -42,6 +42,7 @@ import android.content.pm.ServiceInfo; import android.content.pm.UserInfo; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; +import android.content.pm.ThemeInfo; import android.net.Uri; import android.os.ParcelFileDescriptor; import android.content.IntentSender; @@ -129,6 +130,8 @@ interface IPackageManager { */ ParceledListSlice getInstalledPackages(int flags, in String lastRead, in int userId); + 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 85f7aa5..79cc528 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. @@ -218,9 +219,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)) @@ -258,6 +319,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 @@ -296,5 +363,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 8ba1988..ba85efe 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. @@ -1540,6 +1541,17 @@ public abstract class PackageManager { public abstract List<PackageInfo> getInstalledPackages(int flags, int userId); /** + * 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 3e8c2a8..452a74d 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. @@ -256,6 +257,17 @@ public class PackageParser { } */ + 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. * @@ -287,6 +299,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, state, userId); pi.installLocation = p.installLocation; pi.firstInstallTime = firstInstallTime; @@ -1287,7 +1314,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(); @@ -1378,6 +1408,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; } @@ -1683,12 +1716,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); @@ -2229,6 +2293,8 @@ public class PackageParser { return null; } + parseActivityThemeAttributes(parser, attrs, a.info); + int outerDepth = parser.getDepth(); int type; while ((type=parser.next()) != XmlPullParser.END_DOCUMENT @@ -3222,6 +3288,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 28c751c..789d25e 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); } /** @@ -524,6 +532,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; @@ -561,6 +570,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; } @@ -575,6 +585,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 @@ -593,5 +604,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 86d6ee7..3a6d307 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. @@ -21,6 +22,9 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.view.View; +import android.util.Log; +import android.os.SystemProperties; +import android.text.TextUtils; import java.util.Locale; @@ -66,6 +70,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 @@ -396,6 +405,22 @@ public final class Configuration implements Parcelable, Comparable<Configuration public static final int ORIENTATION_LANDSCAPE = 2; /** @deprecated Not currently supported or used. */ @Deprecated 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 @@ -578,6 +603,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() { @@ -713,6 +741,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(); } @@ -739,6 +769,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED; densityDpi = DENSITY_DPI_UNDEFINED; seq = 0; + customTheme = null; } /** {@hide} */ @@ -873,7 +904,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; } @@ -978,7 +1015,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration && densityDpi != delta.densityDpi) { changed |= ActivityInfo.CONFIG_DENSITY; } - + if (delta.customTheme != null && + (customTheme == null || !customTheme.equals(delta.customTheme))) { + changed |= ActivityInfo.CONFIG_THEME_RESOURCE; + } return changed; } @@ -994,7 +1034,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; } /** @@ -1067,6 +1109,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration dest.writeInt(compatScreenHeightDp); dest.writeInt(compatSmallestScreenWidthDp); 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) { @@ -1095,6 +1145,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration compatScreenHeightDp = source.readInt(); compatSmallestScreenWidthDp = 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 @@ -1163,6 +1219,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration if (n != 0) return n; n = this.densityDpi - that.densityDpi; //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; } @@ -1199,6 +1266,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration result = 31 * result + screenHeightDp; result = 31 * result + smallestScreenWidthDp; result = 31 * result + densityDpi; + 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 b316f23..5072133 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1447,7 +1447,15 @@ public class Resources { mTmpConfig.setLayoutDirection(mTmpConfig.locale); } 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(); @@ -1514,6 +1522,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" @@ -1886,6 +1906,12 @@ public class Resources { return true; } + 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 156600e..5a79122 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. @@ -153,4 +154,49 @@ public class SystemProperties } } } + + /** + * 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/app/ThemeUtils.java b/core/java/com/android/internal/app/ThemeUtils.java new file mode 100644 index 0000000..4265fd5 --- /dev/null +++ b/core/java/com/android/internal/app/ThemeUtils.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 The CyanogenMod 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. + */ + +package com.android.internal.app; + +import android.content.Context; +import android.content.BroadcastReceiver; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.util.Log; + +/** + * @hide + */ + +public class ThemeUtils { + private static final String TAG = "ThemeUtils"; + private static final String DATA_TYPE_TMOBILE_STYLE = "vnd.tmobile.cursor.item/style"; + private static final String DATA_TYPE_TMOBILE_THEME = "vnd.tmobile.cursor.item/theme"; + private static final String ACTION_TMOBILE_THEME_CHANGED = "com.tmobile.intent.action.THEME_CHANGED"; + + public static Context createUiContext(final Context context) { + try { + return context.createPackageContext("com.android.systemui", Context.CONTEXT_RESTRICTED); + } catch (PackageManager.NameNotFoundException e) { + } + + return null; + } + + public static void registerThemeChangeReceiver(final Context context, final BroadcastReceiver receiver) { + IntentFilter filter = new IntentFilter(ACTION_TMOBILE_THEME_CHANGED); + try { + filter.addDataType(DATA_TYPE_TMOBILE_THEME); + filter.addDataType(DATA_TYPE_TMOBILE_STYLE); + } catch (IntentFilter.MalformedMimeTypeException e) { + Log.e(TAG, "Could not add MIME types to filter", e); + } + + context.registerReceiver(receiver, filter); + } +} + diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java index 9e43749..4597a54 100644 --- a/core/java/com/android/internal/os/ZygoteInit.java +++ b/core/java/com/android/internal/os/ZygoteInit.java @@ -99,7 +99,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". @@ -361,6 +361,8 @@ public class ZygoteInit { ar.recycle(); 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 3ca085b..0312e73 100644 --- 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 9820e60..d0068ec 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -173,6 +173,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); static AndroidRuntime* gCurRuntime = NULL; @@ -1212,6 +1213,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), }; /* diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index d422951..e29ea70 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 <androidfw/Asset.h> #include <androidfw/AssetManager.h> #include <androidfw/ResourceTypes.h> +#include <androidfw/PackageRedirectionMap.h> +#include <androidfw/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(ALOGW("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, &typeSpecFlags, &config); #if THROW_ON_BAD_ID @@ -885,7 +896,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla return JNI_FALSE; } - DEBUG_STYLES(LOGI("APPLY STYLE: theme=0x%x defStyleAttr=0x%x defStyleRes=0x%x xml=0x%x", + DEBUG_STYLES(ALOGI("APPLY STYLE: theme=0x%x defStyleAttr=0x%x defStyleRes=0x%x xml=0x%x", themeToken, defStyleAttr, defStyleRes, xmlParserToken)); ResTable::Theme* theme = (ResTable::Theme*)themeToken; @@ -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; @@ -983,7 +1008,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla for (jsize ii=0; ii<NI; ii++) { const uint32_t curIdent = (uint32_t)src[ii]; - DEBUG_STYLES(LOGI("RETRIEVING ATTR 0x%08x...", curIdent)); + DEBUG_STYLES(ALOGI("RETRIEVING ATTR 0x%08x...", curIdent)); // Try to find a value for this attribute... we prioritize values // coming from, first XML attributes, then XML style, then default @@ -1004,7 +1029,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla xmlParser->getAttributeValue(ix, &value); ix++; curXmlAttr = xmlParser->getAttributeNameResID(ix); - DEBUG_STYLES(LOGI("-> From XML: type=0x%x, data=0x%08x", + DEBUG_STYLES(ALOGI("-> From XML: type=0x%x, data=0x%08x", value.dataType, value.data)); } @@ -1018,7 +1043,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla block = styleEnt->stringBlock; typeSetFlags = styleTypeSetFlags; value = styleEnt->map.value; - DEBUG_STYLES(LOGI("-> From style: type=0x%x, data=0x%08x", + DEBUG_STYLES(ALOGI("-> From style: type=0x%x, data=0x%08x", value.dataType, value.data)); } styleEnt++; @@ -1034,7 +1059,7 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla block = defStyleEnt->stringBlock; typeSetFlags = defStyleTypeSetFlags; value = defStyleEnt->map.value; - DEBUG_STYLES(LOGI("-> From def style: type=0x%x, data=0x%08x", + DEBUG_STYLES(ALOGI("-> From def style: type=0x%x, data=0x%08x", value.dataType, value.data)); } defStyleEnt++; @@ -1046,14 +1071,14 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla ssize_t newBlock = theme->resolveAttributeReference(&value, block, &resid, &typeSetFlags, &config); if (newBlock >= 0) block = newBlock; - DEBUG_STYLES(LOGI("-> Resolved attr: type=0x%x, data=0x%08x", + DEBUG_STYLES(ALOGI("-> Resolved attr: type=0x%x, data=0x%08x", value.dataType, value.data)); } else { // If we still don't have a value for this attribute, try to find // it in the theme! ssize_t newBlock = theme->getAttribute(curIdent, &value, &typeSetFlags); if (newBlock >= 0) { - DEBUG_STYLES(LOGI("-> From theme: type=0x%x, data=0x%08x", + DEBUG_STYLES(ALOGI("-> From theme: type=0x%x, data=0x%08x", value.dataType, value.data)); newBlock = res.resolveReference(&value, block, &resid, &typeSetFlags, &config); @@ -1064,19 +1089,44 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla } #endif if (newBlock >= 0) block = newBlock; - DEBUG_STYLES(LOGI("-> Resolved theme: type=0x%x, data=0x%08x", + DEBUG_STYLES(ALOGI("-> Resolved theme: type=0x%x, data=0x%08x", value.dataType, value.data)); } } // Deal with the special @null value -- it turns back to TYPE_NULL. if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { - DEBUG_STYLES(LOGI("-> Setting to @null!")); + DEBUG_STYLES(ALOGI("-> Setting to @null!")); value.dataType = Res_value::TYPE_NULL; block = kXmlBlock; } - DEBUG_STYLES(LOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", + // 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(ALOGW("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) { + ALOGW("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(ALOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", curIdent, value.dataType, value.data)); // Write the final value back to Java. @@ -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(ALOGW("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) { + ALOGW("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; + } + + ALOGV("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) { + ALOGV("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) { + ALOGV("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; + ALOGV("Invalid asset entry %s\n", drmProtectedAssetFileName8); + } else { + status_t loc_result = dstZip->add(srcZip, assetEntry, 0, NULL); + if (loc_result != NO_ERROR) { + ALOGV("error copying zip entry %s\n", drmProtectedAssetFileName8); + result = result | 2; + } else { + loc_result = srcZip->remove(assetEntry); + if (loc_result != NO_ERROR) { + ALOGV("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(ALOGW("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(ALOGW(" 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..97380f3 --- /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 <androidfw/PackageRedirectionMap.h> + +#include "jni.h" +#include "JNIHelp.h" +#include <utils/misc.h> +#include <android_runtime/AndroidRuntime.h> + +#include "android_os_Parcel.h" +#include <binder/Parcel.h> + +#include <androidfw/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 7971ccb..7eddc85 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"> @@ -2241,6 +2242,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/platform.xml b/data/etc/platform.xml index 13d1791..559b5e4 100644 --- a/data/etc/platform.xml +++ b/data/etc/platform.xml @@ -106,6 +106,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/androidfw/AssetManager.h b/include/androidfw/AssetManager.h index d153c31..2717de9 100644 --- a/include/androidfw/AssetManager.h +++ b/include/androidfw/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 <androidfw/Asset.h> #include <androidfw/AssetDir.h> +#include <androidfw/PackageRedirectionMap.h> #include <utils/KeyedVector.h> #include <utils/SortedVector.h> #include <utils/String16.h> @@ -92,7 +94,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 @@ -218,14 +220,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/androidfw/PackageRedirectionMap.h b/include/androidfw/PackageRedirectionMap.h new file mode 100644 index 0000000..9e6435b --- /dev/null +++ b/include/androidfw/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/androidfw/ResourceTypes.h b/include/androidfw/ResourceTypes.h index 48f5bf3..ddefff4 100644 --- a/include/androidfw/ResourceTypes.h +++ b/include/androidfw/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 <androidfw/Asset.h> #include <utils/ByteOrder.h> #include <utils/Errors.h> +#include <androidfw/PackageRedirectionMap.h> #include <utils/String16.h> #include <utils/Vector.h> @@ -1284,6 +1286,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(); @@ -1331,6 +1336,8 @@ public: uint32_t* inoutTypeSpecFlags = NULL, ResTable_config* outConfig = NULL) const; + uint32_t lookupRedirectionMap(uint32_t resID) const; + enum { TMP_BUFFER_SIZE = 16 }; @@ -1551,6 +1558,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; @@ -1593,6 +1601,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/androidfw/ZipEntry.h b/include/androidfw/ZipEntry.h new file mode 100644 index 0000000..7f721b4 --- /dev/null +++ b/include/androidfw/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/androidfw/ZipFile.h b/include/androidfw/ZipFile.h new file mode 100644 index 0000000..dbbd072 --- /dev/null +++ b/include/androidfw/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/androidfw/Android.mk b/libs/androidfw/Android.mk index 3ed75a2..7e53000 100644 --- a/libs/androidfw/Android.mk +++ b/libs/androidfw/Android.mk @@ -22,8 +22,11 @@ commonUtilsSources:= \ Asset.cpp \ AssetDir.cpp \ AssetManager.cpp \ + PackageRedirectionMap.cpp \ ObbFile.cpp \ ResourceTypes.cpp \ + ../../tools/aapt/ZipFile.cpp \ + ../../tools/aapt/ZipEntry.cpp \ StreamingZipInflater.cpp # formerly in libui diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index 8bd805c..806e716 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/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. @@ -152,7 +153,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); @@ -162,6 +163,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; @@ -516,9 +518,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; + } ALOGV("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; } @@ -550,9 +556,13 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) size_t i = mAssetPaths.size(); while (i > 0) { i--; - ALOGV("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; + } + ALOGV("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; } @@ -631,76 +641,13 @@ const ResTable* AssetManager::getResTable(bool required) const if (mCacheMode != CACHE_OFF && !mCacheValid) const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); + mResources = rt = new ResTable(); - 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); - ALOGV("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) { - ass = const_cast<AssetManager*>(this)-> - mZipSet.getZipResourceTableAsset(ap.path); - if (ass == NULL) { - ALOGV("loading resource table %s\n", ap.path.string()); - 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. - ALOGV("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); - } - } - } else { - ALOGV("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(); - } - ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); - if (sharedRes != NULL) { - ALOGV("Copying existing resources for %s", ap.path.string()); - rt->add(sharedRes); - } else { - ALOGV("Parsing resources for %s", ap.path.string()); - rt->add(ass, (void*)(i+1), !shared, idmap); - } - - if (!shared) { - delete ass; - } - } - if (idmap != NULL) { - delete idmap; + 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)); } } @@ -708,9 +655,73 @@ const ResTable* AssetManager::getResTable(bool required) const 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; + ALOGV("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) { + ALOGV("loading resource table %s\n", ap.path.string()); + 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 (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. + ALOGV("Creating shared resources for %s", ap.path.string()); + sharedRes = new ResTable(); + sharedRes->add(ass, cookie, false); + sharedRes = const_cast<AssetManager*>(this)-> + mZipSet.setZipResourceTable(ap.path, sharedRes); + } + } + } else { + ALOGV("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(); + ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); + if (sharedRes != NULL) { + ALOGV("Copying existing resources for %s", ap.path.string()); + rt->add(sharedRes); + } else { + ALOGV("Parsing resources for %s", ap.path.string()); + rt->add(ass, cookie, !shared); + } + if (!shared) { + delete ass; + } + } +} + void AssetManager::updateResourceParamsLocked() const { ResTable* res = mResources; @@ -1163,6 +1174,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) { ALOGV("Adding directory %s from zip %s", dirName, ap.path.string()); scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName); @@ -2017,3 +2031,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) { + ALOGV("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/androidfw/PackageRedirectionMap.cpp b/libs/androidfw/PackageRedirectionMap.cpp new file mode 100644 index 0000000..92cf312 --- /dev/null +++ b/libs/androidfw/PackageRedirectionMap.cpp @@ -0,0 +1,203 @@ +/* + * 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 <androidfw/PackageRedirectionMap.h> +#include <androidfw/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); + } +} + +unsigned int roundUpPower2(unsigned int val) +{ + val--; + val |= val >> 1; + val |= val >> 2; + val |= val >> 4; + val |= val >> 8; + val |= val >> 16; + val++; + + return val; +} +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)) { + ALOGW("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/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 0107da4..44316d4 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/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/String16.h> #include <utils/String8.h> #include <utils/TextOutput.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 { @@ -2487,6 +2490,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(ALOGW("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(ALOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N)); if (N < 0) { @@ -2934,6 +2944,8 @@ void ResTable::uninit() mPackageGroups.clear(); mHeaders.clear(); + + clearRedirections(); } bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const @@ -3191,6 +3203,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) @@ -3396,7 +3426,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(ALOGW("applyStyle(parent): ignoring circular redirect from parent=0x%08x to parentRedirect=0x%08x\n", parent, parentRedirect)); + } else { + REDIRECT_NOISY(ALOGW("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) { @@ -5318,6 +5360,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; + ALOGV("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) { + ALOGV("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/AudioService.java b/media/java/android/media/AudioService.java index fe98458..9dba53a 100644 --- a/media/java/android/media/AudioService.java +++ b/media/java/android/media/AudioService.java @@ -70,6 +70,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.VolumePanel; +import com.android.internal.app.ThemeUtils; import com.android.internal.telephony.ITelephony; import java.io.FileDescriptor; @@ -115,6 +116,8 @@ public class AudioService extends IAudioService.Stub implements OnFinished { /** The UI */ private VolumePanel mVolumePanel; + private Context mUiContext; + private Handler mHandler; // sendMsg() flags /** If the msg is already queued, replace it with this one. */ @@ -446,6 +449,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { public AudioService(Context context) { mContext = context; mContentResolver = context.getContentResolver(); + mHandler = new Handler(); mVoiceCapable = mContext.getResources().getBoolean( com.android.internal.R.bool.config_voice_capable); @@ -463,7 +467,6 @@ public class AudioService extends IAudioService.Stub implements OnFinished { sSoundEffectVolumeDb = context.getResources().getInteger( com.android.internal.R.integer.config_soundEffectVolumeDb); - mVolumePanel = new VolumePanel(context, this); mMode = AudioSystem.MODE_NORMAL; mForcedUseForComm = AudioSystem.FORCE_NONE; @@ -527,6 +530,13 @@ public class AudioService extends IAudioService.Stub implements OnFinished { pkgFilter.addDataScheme("package"); context.registerReceiver(mReceiver, pkgFilter); + ThemeUtils.registerThemeChangeReceiver(context, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }); + // Register for phone state monitoring TelephonyManager tmgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); @@ -1081,7 +1091,7 @@ public class AudioService extends IAudioService.Stub implements OnFinished { streamType = AudioSystem.STREAM_NOTIFICATION; } - mVolumePanel.postVolumeChanged(streamType, flags); + showVolumeChangeUi(streamType, flags); if ((flags & AudioManager.FLAG_FIXED_VOLUME) == 0) { oldIndex = (oldIndex + 5) / 10; @@ -3960,6 +3970,25 @@ public class AudioService extends IAudioService.Stub implements OnFinished { } } + private void showVolumeChangeUi(final int streamType, final int flags) { + if (mUiContext != null && mVolumePanel != null) { + mVolumePanel.postVolumeChanged(streamType, flags); + } else { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + + final Context context = mUiContext != null ? mUiContext : mContext; + mVolumePanel = new VolumePanel(context, AudioService.this); + mVolumePanel.postVolumeChanged(streamType, flags); + } + }); + } + } + //========================================================================================== // AudioFocus //========================================================================================== diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java index f190eb9..4cad61c 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. @@ -109,6 +110,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(); @@ -127,6 +141,14 @@ 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 { try { if (DrmStore.AUTHORITY.equals(authority)) { diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java index b5a672a..a0ea993 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 }; @@ -368,7 +373,11 @@ public class RingtoneManager { 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 +414,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 +434,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 +465,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; } @@ -512,7 +517,39 @@ public class RingtoneManager { MediaStore.Audio.Media.DEFAULT_SORT_ORDER) : 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 8086bbc..e61135e 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. @@ -974,7 +975,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 { @@ -1015,7 +1017,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 { @@ -1028,10 +1031,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 bf20e9d..267b62d 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -24,7 +24,8 @@ xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui" android:id="@+id/status_bar" 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" android:fitsSystemWindows="true" diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java index 2110483..14f6345 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUI.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java @@ -26,7 +26,7 @@ public abstract class SystemUI { public Context mContext; public abstract void start(); - + 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 427fe91..18bedf1 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java @@ -96,6 +96,7 @@ public class SystemUIService extends Service { } mServices[i].mContext = this; Slog.d(TAG, "running: " + mServices[i]); + mServices[i].start(); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index fe33b02..8743a90 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -73,6 +73,7 @@ import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.WindowManagerGlobal; +import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupMenu; @@ -124,6 +125,8 @@ public abstract class BaseStatusBar extends SystemUI implements protected int mCurrentUserId = 0; + protected FrameLayout mStatusBarContainer; + // UI-specific methods /** @@ -203,6 +206,8 @@ public abstract class BaseStatusBar extends SystemUI implements mBarService = IStatusBarService.Stub.asInterface( ServiceManager.getService(Context.STATUS_BAR_SERVICE)); + mStatusBarContainer = new FrameLayout(mContext); + // Connect in to the status bar manager service StatusBarIconList iconList = new StatusBarIconList(); ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java index c82f250..9c947ae 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java @@ -147,6 +147,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/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java index dbc55c8..3bc3b65 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java @@ -207,6 +207,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 337aa0d..d21492b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -31,6 +31,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.res.CustomTheme; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Canvas; @@ -53,6 +54,7 @@ import android.service.dreams.DreamService; import android.service.dreams.IDreamManager; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.view.Display; import android.view.Gravity; @@ -70,6 +72,7 @@ import android.view.animation.AnimationUtils; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -266,6 +269,11 @@ public class PhoneStatusBar extends BaseStatusBar { private Animator mLightsOutAnimation; private Animator mLightsOnAnimation; + // 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; @@ -326,6 +334,11 @@ public class PhoneStatusBar extends BaseStatusBar { mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); + CustomTheme currentTheme = mContext.getResources().getConfiguration().customTheme; + if (currentTheme != null) { + mCurrentTheme = (CustomTheme)currentTheme.clone(); + } + super.start(); // calls createAndAddWindows() addNavigationBar(); @@ -404,7 +417,7 @@ public class PhoneStatusBar extends BaseStatusBar { try { boolean showNav = mWindowManagerService.hasNavigationBar(); if (DEBUG) Slog.v(TAG, "hasNavigationBar=" + showNav); - if (showNav) { + if (showNav && !mRecreating) { mNavigationBarView = (NavigationBarView) View.inflate(context, R.layout.navigation_bar, null); @@ -425,6 +438,11 @@ public class PhoneStatusBar extends BaseStatusBar { mStatusBarContents = (LinearLayout)mStatusBarView.findViewById(R.id.status_bar_contents); mTickerView = mStatusBarView.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(); + mPile = (NotificationRowLayout)mStatusBarWindow.findViewById(R.id.latestItems); mPile.setLayoutTransitionsEnabled(false); mPile.setLongPressListener(getNotificationLongClicker()); @@ -505,7 +523,6 @@ public class PhoneStatusBar extends BaseStatusBar { mTicker = new MyTicker(context, mStatusBarView); - TickerView tickerView = (TickerView)mStatusBarView.findViewById(R.id.tickerText); tickerView.mTicker = mTicker; @@ -696,6 +713,10 @@ public class PhoneStatusBar extends BaseStatusBar { return lp; } + void onBarViewDetached() { + // WindowManagerImpl.getDefault().removeView(mStatusBarWindow); + } + @Override protected void updateSearchPanel() { super.updateSearchPanel(); @@ -806,6 +827,12 @@ public class PhoneStatusBar extends BaseStatusBar { 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(); mWindowManager.updateViewLayout(mNavigationBarView, getNavigationBarLayoutParams()); @@ -941,7 +968,7 @@ public class PhoneStatusBar extends BaseStatusBar { notification.notification.fullScreenIntent.send(); } catch (PendingIntent.CanceledException e) { } - } else { + } else if (!mRecreating) { // usual case: status bar visible & not immersive // show the ticker if there isn't an intruder too @@ -1362,11 +1389,11 @@ public class PhoneStatusBar extends BaseStatusBar { // Expand the window to encompass the full screen in anticipation of the drag. // This is only possible to do atomically because the status bar is at the top of the screen! - WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); + WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarContainer.getLayoutParams(); lp.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; lp.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; lp.height = ViewGroup.LayoutParams.MATCH_PARENT; - mWindowManager.updateViewLayout(mStatusBarWindow, lp); + mWindowManager.updateViewLayout(mStatusBarContainer, lp); // Updating the window layout will force an expensive traversal/redraw. // Kick off the reveal animation after this is complete to avoid animation latency. @@ -1640,11 +1667,11 @@ public class PhoneStatusBar extends BaseStatusBar { visibilityChanged(false); // Shrink the window to the size of the status bar only - WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarWindow.getLayoutParams(); + WindowManager.LayoutParams lp = (WindowManager.LayoutParams) mStatusBarContainer.getLayoutParams(); lp.height = getStatusBarHeight(); lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; lp.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; - mWindowManager.updateViewLayout(mStatusBarWindow, lp); + mWindowManager.updateViewLayout(mStatusBarContainer, lp); if ((mDisabled & StatusBarManager.DISABLE_NOTIFICATION_ICONS) == 0) { setNotificationIconVisibility(true, com.android.internal.R.anim.fade_in); @@ -1944,7 +1971,7 @@ public class PhoneStatusBar extends BaseStatusBar { // until status bar window is attached to the window manager, // because... well, what's the point otherwise? And trying to // run a ticker without being attached will crash! - if (n.notification.tickerText != null && mStatusBarWindow.getWindowToken() != null) { + if (n.notification.tickerText != null && mStatusBarContainer.getWindowToken() != null) { if (0 == (mDisabled & (StatusBarManager.DISABLE_NOTIFICATION_ICONS | StatusBarManager.DISABLE_NOTIFICATION_TICKER))) { mTicker.addEntry(n); @@ -2089,7 +2116,6 @@ public class PhoneStatusBar extends BaseStatusBar { private void addStatusBarWindow() { // Put up the view final int height = getStatusBarHeight(); - // Now that the status bar window encompasses the sliding panel and its // translucent backdrop, the entire thing is made TRANSLUCENT and is // hardware-accelerated. @@ -2109,7 +2135,8 @@ public class PhoneStatusBar extends BaseStatusBar { lp.packageName = mContext.getPackageName(); makeStatusBarView(); - mWindowManager.addView(mStatusBarWindow, lp); + mStatusBarContainer.addView(mStatusBarWindow); + mWindowManager.addView(mStatusBarContainer, lp); } void setNotificationIconVisibility(boolean visible, int anim) { @@ -2355,6 +2382,60 @@ public class PhoneStatusBar extends BaseStatusBar { } } + 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(); + + makeStatusBarView(); + repositionNavigationBar(); + + // 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(mStatusBarWindow); + + updateExpandedViewPos(EXPANDED_LEAVE_ALONE); + mRecreating = false; + } + /** * Reload some of our resources when the configuration changes. * @@ -2366,14 +2447,23 @@ public class PhoneStatusBar extends BaseStatusBar { 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)); + // detect theme change. + CustomTheme newTheme = res.getConfiguration().customTheme; + if (newTheme != null && + (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) { + mCurrentTheme = (CustomTheme)newTheme.clone(); + recreateStatusBar(); + } else { + + if (mClearButton instanceof TextView) { + ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button)); + } + loadDimens(); } // Update the QuickSettings container if (mQS != null) mQS.updateResources(); - 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 af6a149..a86a869 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -84,6 +84,12 @@ public class PhoneStatusBarView extends PanelBar { } @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mBar.onBarViewDetached(); + } + + @Override public boolean panelsEnabled() { return ((mBar.mDisabled & StatusBarManager.DISABLE_EXPAND) == 0); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index f526f0c..874ca5e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -66,6 +66,12 @@ public class StatusBarWindowView extends FrameLayout } @Override + public void dispatchWindowFocusChanged(boolean hasFocus) { + this.setFocusableInTouchMode(hasFocus); + this.requestFocus(); + } + + @Override public boolean dispatchKeyEvent(KeyEvent event) { boolean down = event.getAction() == KeyEvent.ACTION_DOWN; switch (event.getKeyCode()) { 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 86c247a..028528c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java @@ -29,6 +29,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.res.Configuration; +import android.content.res.CustomTheme; import android.content.res.Resources; import android.graphics.PixelFormat; import android.graphics.Point; @@ -39,6 +40,7 @@ import android.os.IBinder; import android.os.Message; import android.os.RemoteException; import android.text.TextUtils; +import android.util.Pair; import android.util.Slog; import android.view.Display; import android.view.Gravity; @@ -53,6 +55,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; import android.widget.TextView; @@ -236,6 +239,12 @@ public class TabletStatusBar extends BaseStatusBar implements mWindowManager.addView(sb, lp); } + // 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(); @@ -379,8 +388,49 @@ public class TabletStatusBar extends BaseStatusBar 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) { + // detect theme change. + CustomTheme newTheme = mContext.getResources().getConfiguration().customTheme; + if (newTheme != null && + (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) { + mCurrentTheme = (CustomTheme)newTheme.clone(); + recreateStatusBar(); + } loadDimens(); mNotificationPanelParams.height = getNotificationPanelHeight(); mWindowManager.updateViewLayout(mNotificationPanel, mNotificationPanelParams); @@ -443,6 +493,11 @@ public class TabletStatusBar extends BaseStatusBar implements protected View makeStatusBarView() { final Context context = mContext; + CustomTheme currentTheme = mContext.getResources().getConfiguration().customTheme; + if (currentTheme != null) { + mCurrentTheme = (CustomTheme)currentTheme.clone(); + } + loadDimens(); final TabletStatusBarView sb = (TabletStatusBarView)View.inflate( @@ -684,14 +739,14 @@ public class TabletStatusBar extends BaseStatusBar 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; } if (lp.height != height) { lp.height = height; - mWindowManager.updateViewLayout(mStatusBarView, lp); + mWindowManager.updateViewLayout(mStatusBarContainer, lp); } } @@ -861,7 +916,7 @@ public class TabletStatusBar extends BaseStatusBar 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/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java index 06a7803..378c51b 100644 --- a/policy/src/com/android/internal/policy/impl/GlobalActions.java +++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java @@ -72,6 +72,8 @@ import android.widget.ImageView.ScaleType; import android.widget.ListView; import android.widget.TextView; +import com.android.internal.app.ThemeUtils; + import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -100,6 +102,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac private final Context mContext; private final WindowManagerFuncs mWindowManagerFuncs; + private Context mUiContext; private final AudioManager mAudioManager; private final IDreamManager mDreamManager; @@ -139,6 +142,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED); context.registerReceiver(mBroadcastReceiver, filter); + ThemeUtils.registerThemeChangeReceiver(context, mThemeChangeReceiver); + // get notified of phone state changes TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); @@ -160,7 +165,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public void showDialog(boolean keyguardShowing, boolean isDeviceProvisioned) { mKeyguardShowing = keyguardShowing; mDeviceProvisioned = isDeviceProvisioned; - if (mDialog != null) { + if (mDialog != null && mUiContext == null) { mDialog.dismiss(); mDialog = null; // Show delayed, so that the dismiss of the previous dialog completes @@ -194,6 +199,13 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mDialog.getWindow().getDecorView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_EXPAND); } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + /** * Create the global actions dialog. * @return A new dialog. @@ -393,12 +405,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac mAdapter = new MyAdapter(); - AlertParams params = new AlertParams(mContext); + AlertParams params = new AlertParams(getUiContext()); params.mAdapter = mAdapter; params.mOnClickListener = this; params.mForceInverseBackground = true; - GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params); + GlobalActionsDialog dialog = new GlobalActionsDialog(getUiContext(), params); dialog.setCanceledOnTouchOutside(false); // Handled by the custom class. dialog.getListView().setItemsCanFocus(true); @@ -476,7 +488,7 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac names[i++] = profile.getName(); } - final AlertDialog.Builder ab = new AlertDialog.Builder(mContext); + final AlertDialog.Builder ab = new AlertDialog.Builder(getUiContext()); AlertDialog dialog = ab .setTitle(R.string.global_action_choose_profile) @@ -689,7 +701,8 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac public View getView(int position, View convertView, ViewGroup parent) { Action action = getItem(position); - return action.create(mContext, convertView, parent, LayoutInflater.from(mContext)); + final Context context = getUiContext(); + return action.create(context, convertView, parent, LayoutInflater.from(context)); } } @@ -1070,6 +1083,12 @@ class GlobalActions implements DialogInterface.OnDismissListener, DialogInterfac } }; + private BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + PhoneStateListener mPhoneStateListener = new PhoneStateListener() { @Override public void onServiceStateChanged(ServiceState serviceState) { diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java index d5279ae..4480284 100755 --- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java @@ -231,6 +231,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { final Object mLock = new Object(); Context mContext; + Context mUiContext; IWindowManager mWindowManager; WindowManagerFuncs mWindowManagerFuncs; PowerManager mPowerManager; @@ -1591,13 +1592,15 @@ public class PhoneWindowManager implements WindowManagerPolicy { if (DEBUG_STARTING_WINDOW) Slog.d(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); @@ -3842,6 +3845,12 @@ public class PhoneWindowManager implements WindowManagerPolicy { } }; + BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + @Override public void screenTurnedOff(int why) { EventLog.writeEvent(70000, 0); 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/DeviceStorageMonitorService.java b/services/java/com/android/server/DeviceStorageMonitorService.java index 94a087a..cce5653 100644 --- a/services/java/com/android/server/DeviceStorageMonitorService.java +++ b/services/java/com/android/server/DeviceStorageMonitorService.java @@ -19,9 +19,11 @@ package com.android.server; import java.io.FileDescriptor; import java.io.PrintWriter; +import com.android.internal.app.ThemeUtils; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -84,6 +86,7 @@ public class DeviceStorageMonitorService extends Binder { private boolean mLowMemFlag=false; private boolean mMemFullFlag=false; private Context mContext; + private Context mUiContext; private ContentResolver mContentResolver; private long mTotalMemory; // on /data private StatFs mDataFileStats; @@ -345,6 +348,14 @@ public class DeviceStorageMonitorService extends Binder { mLastReportedFreeMemTime = 0; mContext = context; mContentResolver = mContext.getContentResolver(); + + ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() { + @Override + public void onReceive(Context content, Intent intent) { + mUiContext = null; + } + }); + //create StatFs object mDataFileStats = new StatFs(DATA_PATH); mSystemFileStats = new StatFs(SYSTEM_PATH); @@ -402,7 +413,7 @@ public class DeviceStorageMonitorService extends Binder { notification.icon = com.android.internal.R.drawable.stat_notify_disk_full; notification.tickerText = title; notification.flags |= Notification.FLAG_NO_CLEAR; - notification.setLatestEventInfo(mContext, title, details, intent); + notification.setLatestEventInfo(getUiContext(), title, details, intent); mNotificationMgr.notifyAsUser(null, LOW_MEMORY_NOTIFICATION_ID, notification, UserHandle.ALL); mContext.sendStickyBroadcastAsUser(mStorageLowIntent, UserHandle.ALL); @@ -515,4 +526,11 @@ public class DeviceStorageMonitorService extends Binder { pw.print(" mMemCacheTrimToThreshold="); pw.println(Formatter.formatFileSize(mContext, mMemCacheTrimToThreshold)); } + + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } } diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index b11432d..445a4bb 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -15,6 +15,7 @@ package com.android.server; +import com.android.internal.app.ThemeUtils; import com.android.internal.content.PackageMonitor; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; @@ -165,6 +166,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private static final Locale ENGLISH_LOCALE = new Locale("en"); final Context mContext; + private Context mUiContext; final Resources mRes; final Handler mHandler; final InputMethodSettings mSettings; @@ -848,6 +850,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }); + mStatusBar = statusBar; statusBar.setIconVisibility("ime", false); updateImeWindowStatusLocked(); @@ -2525,6 +2534,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + // ---------------------------------------------------------------------- private void showInputMethodMenu() { @@ -2561,7 +2577,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub private void showInputMethodMenuInternal(boolean showSubtypes) { if (DEBUG) Slog.v(TAG, "Show switching menu"); - final Context context = mContext; + final Context context = getUiContext(); final boolean isScreenLocked = isScreenLocked(); final String lastInputMethodId = mSettings.getSelectedInputMethod(); diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index af49135..5f0187c 100755 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -79,6 +79,9 @@ import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; import java.io.File; + +import com.android.internal.app.ThemeUtils; + import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -122,6 +125,7 @@ public class NotificationManagerService extends INotificationManager.Stub private static final boolean ENABLE_BLOCKED_TOASTS = true; final Context mContext; + Context mUiContext; final IActivityManager mAm; final IBinder mForegroundToken = new Binder(); @@ -533,6 +537,13 @@ public class NotificationManagerService extends INotificationManager.Stub } }; + private BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -766,6 +777,7 @@ public class NotificationManagerService extends INotificationManager.Stub ledObserver.observe(); QuietHoursSettingsObserver qhObserver = new QuietHoursSettingsObserver(mHandler); qhObserver.observe(); + ThemeUtils.registerThemeChangeReceiver(mContext, mThemeChangeReceiver); } void systemReady() { @@ -1634,6 +1646,13 @@ public class NotificationManagerService extends INotificationManager.Stub return -1; } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + private void updateNotificationPulse() { synchronized (mNotificationList) { updateLightsLocked(); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 3dd22b0..cec8a4c 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.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. @@ -24,6 +25,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.media.AudioService; @@ -757,6 +759,13 @@ class ServerThread extends Thread { reportWtf("starting DreamManagerService", e); } } + + try { + Slog.i(TAG, "AssetRedirectionManager Service"); + ServiceManager.addService("assetredirection", new AssetRedirectionManagerService(context)); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting AssetRedirectionManager Service", e); + } } // Before things start rolling, be sure we have decided whether @@ -840,6 +849,15 @@ class ServerThread extends Thread { reportWtf("making Display 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 MountService mountServiceF = mountService; diff --git a/services/java/com/android/server/ThrottleService.java b/services/java/com/android/server/ThrottleService.java index 75eb3c4..9bc3a91 100644 --- a/services/java/com/android/server/ThrottleService.java +++ b/services/java/com/android/server/ThrottleService.java @@ -53,6 +53,7 @@ import android.util.Slog; import android.util.TrustedTime; import com.android.internal.R; +import com.android.internal.app.ThemeUtils; import com.android.internal.telephony.TelephonyProperties; import java.io.BufferedWriter; @@ -80,6 +81,7 @@ public class ThrottleService extends IThrottleManager.Stub { private HandlerThread mThread; private Context mContext; + private Context mUiContext; private static final int INITIAL_POLL_DELAY_SEC = 90; private static final int TESTING_POLLING_PERIOD_SEC = 60 * 1; @@ -339,6 +341,13 @@ public class ThrottleService extends IThrottleManager.Stub { } }, new IntentFilter(ACTION_RESET)); + ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }); + // use a new thread as we don't want to stall the system for file writes mThread = new HandlerThread(TAG); mThread.start(); @@ -685,12 +694,18 @@ public class ThrottleService extends IThrottleManager.Stub { } mThrottlingNotification.flags = flags; mThrottlingNotification.tickerText = title; - mThrottlingNotification.setLatestEventInfo(mContext, title, message, pi); + mThrottlingNotification.setLatestEventInfo(getUiContext(), title, message, pi); mNotificationManager.notifyAsUser(null, mThrottlingNotification.icon, mThrottlingNotification, UserHandle.ALL); } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } private void clearThrottleAndNotification() { if (mThrottleIndex.get() != THROTTLE_INDEX_UNTHROTTLED) { diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java index 0e456f1..797990e 100644 --- a/services/java/com/android/server/UiModeManagerService.java +++ b/services/java/com/android/server/UiModeManagerService.java @@ -48,6 +48,7 @@ import java.io.PrintWriter; import com.android.internal.R; import com.android.internal.app.DisableCarModeActivity; import com.android.server.TwilightService.TwilightState; +import com.android.internal.app.ThemeUtils; final class UiModeManagerService extends IUiModeManager.Stub { private static final String TAG = UiModeManager.class.getSimpleName(); @@ -60,6 +61,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { private final Context mContext; private final TwilightService mTwilightService; private final Handler mHandler = new Handler(); + private Context mUiContext; final Object mLock = new Object(); @@ -142,6 +144,13 @@ final class UiModeManagerService extends IUiModeManager.Stub { } }; + private final BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + private final TwilightService.TwilightListener mTwilightListener = new TwilightService.TwilightListener() { @Override @@ -161,6 +170,8 @@ final class UiModeManagerService extends IUiModeManager.Stub { mContext.registerReceiver(mBatteryReceiver, new IntentFilter(Intent.ACTION_BATTERY_CHANGED)); + ThemeUtils.registerThemeChangeReceiver(mContext, mThemeChangeReceiver); + mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, TAG); @@ -549,7 +560,7 @@ final class UiModeManagerService extends IUiModeManager.Stub { n.flags = Notification.FLAG_ONGOING_EVENT; n.when = 0; n.setLatestEventInfo( - mContext, + getUiContext(), mContext.getString(R.string.car_mode_disable_notification_title), mContext.getString(R.string.car_mode_disable_notification_message), PendingIntent.getActivityAsUser(mContext, 0, carModeOffIntent, 0, @@ -579,6 +590,13 @@ final class UiModeManagerService extends IUiModeManager.Stub { } } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index d2cd646..a747f0b 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. @@ -19,6 +20,7 @@ package com.android.server.am; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import com.android.internal.R; +import com.android.internal.app.ThemeUtils; import com.android.internal.os.BatteryStatsImpl; import com.android.internal.os.ProcessStats; import com.android.internal.widget.LockPatternUtils; @@ -87,6 +89,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; 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; @@ -160,6 +163,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 { @@ -667,6 +671,7 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mLaunchWarningShown = false; Context mContext; + Context mUiContext; int mFactoryTest; @@ -936,7 +941,7 @@ public final class ActivityManagerService extends ActivityManagerNative return; } if (mShowDialogs && !mSleeping && !mShuttingDown) { - Dialog d = new AppErrorDialog(mContext, + Dialog d = new AppErrorDialog(getUiContext(), ActivityManagerService.this, res, proc); d.show(); proc.crashDialog = d; @@ -971,7 +976,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mShowDialogs) { Dialog d = new AppNotRespondingDialog(ActivityManagerService.this, - mContext, proc, (ActivityRecord)data.get("activity"), + getUiContext(), proc, (ActivityRecord)data.get("activity"), msg.arg1 != 0); d.show(); proc.anrDialog = d; @@ -997,7 +1002,7 @@ public final class ActivityManagerService extends ActivityManagerNative } AppErrorResult res = (AppErrorResult) data.get("result"); if (mShowDialogs && !mSleeping && !mShuttingDown) { - Dialog d = new StrictModeViolationDialog(mContext, + Dialog d = new StrictModeViolationDialog(getUiContext(), ActivityManagerService.this, res, proc); d.show(); proc.crashDialog = d; @@ -1011,7 +1016,7 @@ public final class ActivityManagerService extends ActivityManagerNative } break; case SHOW_FACTORY_ERROR_MSG: { Dialog d = new FactoryErrorDialog( - mContext, msg.getData().getCharSequence("msg")); + getUiContext(), msg.getData().getCharSequence("msg")); d.show(); ensureBootCompleted(); } break; @@ -1031,7 +1036,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!app.waitedForDebugger) { Dialog d = new AppWaitingForDebuggerDialog( ActivityManagerService.this, - mContext, app); + getUiContext(), app); app.waitDialog = d; app.waitedForDebugger = true; d.show(); @@ -1113,7 +1118,7 @@ public final class ActivityManagerService extends ActivityManagerNative Log.e(TAG, title + ": " + text); if (mShowDialogs) { // XXX This is a temporary dialog, no need to localize. - AlertDialog d = new BaseErrorDialog(mContext); + AlertDialog d = new BaseErrorDialog(getUiContext()); d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); d.setCancelable(false); d.setTitle(title); @@ -1184,7 +1189,7 @@ public final class ActivityManagerService extends ActivityManagerNative notification.defaults = 0; // please be quiet notification.sound = null; notification.vibrate = null; - notification.setLatestEventInfo(context, text, + notification.setLatestEventInfo(getUiContext(), text, mContext.getText(R.string.heavy_weight_notification_detail), PendingIntent.getActivityAsUser(mContext, 0, root.intent, PendingIntent.FLAG_CANCEL_CURRENT, null, @@ -1799,6 +1804,15 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private Context getUiContext() { + synchronized (this) { + if (mUiContext == null && mBooted) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + } + /** * Initialize the application bind args. These are passed to each * process when the bindApplication() IPC is sent to the process. They're @@ -3407,7 +3421,7 @@ public final class ActivityManagerService extends ActivityManagerNative @Override public void run() { synchronized (ActivityManagerService.this) { - final Dialog d = new LaunchWarningWindow(mContext, cur, next); + final Dialog d = new LaunchWarningWindow(getUiContext(), cur, next); d.show(); mHandler.postDelayed(new Runnable() { @Override @@ -4390,6 +4404,13 @@ public final class ActivityManagerService extends ActivityManagerNative } }, pkgFilter); + ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }); + synchronized (this) { // Ensure that any processes we had put on hold are now started // up. @@ -12310,6 +12331,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; @@ -12529,6 +12555,13 @@ public final class ActivityManagerService extends ActivityManagerNative return srec.launchedFromUid; } + 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/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index e4a7ead..8f154b1 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -47,6 +47,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.util.Log; +import com.android.internal.app.ThemeUtils; import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.IState; @@ -74,6 +75,7 @@ import java.util.Set; public class Tethering extends INetworkManagementEventObserver.Stub { private Context mContext; + private Context mUiContext; private final static String TAG = "Tethering"; private final static boolean DBG = true; private final static boolean VDBG = false; @@ -158,6 +160,13 @@ public class Tethering extends INetworkManagementEventObserver.Stub { filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); mContext.registerReceiver(mStateReceiver, filter); + ThemeUtils.registerThemeChangeReceiver(mContext, new BroadcastReceiver() { + @Override + public void onReceive(Context content, Intent intent) { + mUiContext = null; + } + }); + filter = new IntentFilter(); filter.addAction(Intent.ACTION_MEDIA_SHARED); filter.addAction(Intent.ACTION_MEDIA_UNSHARED); @@ -478,12 +487,19 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mTetheredNotification.defaults &= ~Notification.DEFAULT_SOUND; mTetheredNotification.flags = Notification.FLAG_ONGOING_EVENT; mTetheredNotification.tickerText = title; - mTetheredNotification.setLatestEventInfo(mContext, title, message, pi); + mTetheredNotification.setLatestEventInfo(getUiContext(), title, message, pi); notificationManager.notifyAsUser(null, mTetheredNotification.icon, mTetheredNotification, UserHandle.ALL); } + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; + } + private void clearTetheredNotification() { NotificationManager notificationManager = (NotificationManager)mContext.getSystemService(Context.NOTIFICATION_SERVICE); diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index 83672c5..0cbfa6f 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. @@ -31,6 +32,7 @@ import static libcore.io.OsConstants.S_IXGRP; import static libcore.io.OsConstants.S_IROTH; import static libcore.io.OsConstants.S_IXOTH; +import com.android.internal.app.IAssetRedirectionManager; import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; @@ -143,6 +145,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -150,6 +153,9 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipOutputStream; import libcore.io.ErrnoException; import libcore.io.IoUtils; @@ -201,6 +207,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; @@ -407,6 +415,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>>(); @@ -706,7 +716,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageInstalledInfo res = data.res; if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) { - res.removedInfo.sendBroadcast(false, true, false); + res.removedInfo.sendBroadcast(false, true, false, false); Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, res.uid); // Determine the set of users who are adding this @@ -743,21 +753,25 @@ public class PackageManagerService extends IPackageManager.Stub { } } sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - res.pkg.applicationInfo.packageName, + res.pkg.applicationInfo.packageName, null, extras, null, null, firstUsers); 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, updateUsers); if (update) { sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, - res.pkg.applicationInfo.packageName, + res.pkg.applicationInfo.packageName, category, extras, null, null, updateUsers); sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, - null, null, + null, null, null, res.pkg.applicationInfo.packageName, null, updateUsers); } if (res.removedInfo.args != null) { @@ -975,6 +989,7 @@ public class PackageManagerService extends IPackageManager.Stub { Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLPw("com.tmobile.thememanager", THEME_MAMANER_GUID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM); mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM); @@ -2938,6 +2953,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; + } + @Override public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags, String lastRead, int userId) { @@ -4495,6 +4524,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(PackageSetting ps, boolean chatty) { if (DEBUG_INSTALL) { if (chatty) @@ -4523,6 +4578,8 @@ public class PackageManagerService extends IPackageManager.Stub { // writer synchronized (mPackages) { + cleanAssetRedirections(pkg); + mPackages.remove(pkg.applicationInfo.packageName); if (pkg.mPath != null) { mAppDirs.remove(pkg.mPath); @@ -5367,7 +5424,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, int[] userIds) { IActivityManager am = ActivityManagerNative.getDefault(); @@ -5400,6 +5457,9 @@ public class PackageManagerService extends IPackageManager.Stub { + intent.toShortString(false, true, false, false) + " " + intent.getExtras(), here); } + if (intentCategory != null) { + intent.addCategory(intentCategory); + } am.broadcastIntent(null, intent, null, finishedReceiver, 0, null, null, null, finishedReceiver != null, false, id); } @@ -5483,6 +5543,7 @@ public class PackageManagerService extends IPackageManager.Stub { String addedPackage = null; int addedAppId = -1; int[] addedUsers = null; + String category = null; // TODO post a message to the handler to obtain serial ordering synchronized (mInstallLock) { @@ -5513,6 +5574,9 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { p = mAppDirs.get(fullPathStr); if (p != null) { + if (p.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } ps = mSettings.mPackages.get(p.applicationInfo.packageName); if (ps != null) { removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true); @@ -5554,6 +5618,9 @@ public class PackageManagerService extends IPackageManager.Stub { addedAppId = UserHandle.getAppId(p.applicationInfo.uid); } } + if (p != null && p.mIsThemeApk) { + category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE; + } } // reader @@ -5566,13 +5633,13 @@ public class PackageManagerService extends IPackageManager.Stub { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedAppId); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, + sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category, extras, null, null, removedUsers); } if (addedPackage != null) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, addedAppId); - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, category, extras, null, null, addedUsers); } } @@ -5669,7 +5736,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (sendAdded) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, null, packageName, extras, null, null, new int[] {userId}); } } finally { @@ -7791,6 +7858,24 @@ public class PackageManagerService extends IPackageManager.Stub { 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, UPDATE_PERMISSIONS_REPLACE_PKG | (newPackage.permissions.size() > 0 @@ -7806,6 +7891,65 @@ 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; @@ -8066,6 +8210,18 @@ public class PackageManagerService extends IPackageManager.Stub { boolean removedForAllUsers = false; boolean systemUpdate = false; + + synchronized (mPackages) { + PackageParser.Package p = mPackages.get(packageName); + if (p != null) { + info.isThemeApk = p.mIsThemeApk; + if (info.isThemeApk && + !info.isRemovedPackageSystemUpdate) { + deleteLockedZipFileIfExists(p.mPath); + } + } + } + synchronized (mInstallLock) { res = deletePackageLI(packageName, (flags & PackageManager.DELETE_ALL_USERS) != 0 @@ -8078,7 +8234,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (res) { - info.sendBroadcast(true, systemUpdate, removedForAllUsers); + info.sendBroadcast(true, systemUpdate, removedForAllUsers, true); // If the removed package was a system update, the old system package // was re-enabled; we need to broadcast this information @@ -8088,11 +8244,16 @@ public class PackageManagerService extends IPackageManager.Stub { ? info.removedAppId : 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, null); - sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, + sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, category, extras, null, null, null); - sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, + sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, null, packageName, null, null); } } @@ -8117,8 +8278,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, boolean removedForAllUsers) { + void sendBroadcast(boolean fullRemove, boolean replacing, boolean removedForAllUsers, + boolean deleteLockedZipFileIfExists) { Bundle extras = new Bundle(1); extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid); extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove); @@ -8127,15 +8290,19 @@ public class PackageManagerService extends IPackageManager.Stub { } extras.putBoolean(Intent.EXTRA_REMOVED_FOR_ALL_USERS, removedForAllUsers); 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, removedUsers); if (fullRemove && !replacing) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, + sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, category, extras, null, null, removedUsers); } } if (removedAppId >= 0) { - sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null, + sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, null, extras, null, null, removedUsers); } } @@ -8714,6 +8881,7 @@ public class PackageManagerService extends IPackageManager.Stub { "replacePreferredActivity expects filter to have no data authorities, " + "paths, schemes or types."); } + synchronized (mPackages) { if (mContext.checkCallingOrSelfPermission( android.Manifest.permission.SET_PREFERRED_APPLICATIONS) @@ -8989,7 +9157,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, new int[] {UserHandle.getUserId(packageUid)}); } @@ -9654,7 +9822,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, null); + sendPackageBroadcast(action, null, null, extras, null, finishedReceiver, null); } } diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index 94494ae..15395d2 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -2478,7 +2478,7 @@ final class Settings { if (pkgSetting.getNotLaunched(userId)) { if (pkgSetting.installerPackageName != null) { PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, - pkgSetting.name, null, + pkgSetting.name, null, null, pkgSetting.installerPackageName, null, new int[] {userId}); } pkgSetting.setNotLaunched(false, userId); diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 38233d0..0e5adbb 100755 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -41,6 +41,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND; import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; import com.android.internal.app.IBatteryStats; +import com.android.internal.app.ThemeUtils; + import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; import com.android.internal.view.IInputContext; @@ -295,6 +297,13 @@ public class WindowManagerService extends IWindowManager.Stub private static final float THUMBNAIL_ANIMATION_DECELERATE_FACTOR = 1.5f; + + private BroadcastReceiver mThemeChangeReceiver = new BroadcastReceiver() { + public void onReceive(Context context, Intent intent) { + mUiContext = null; + } + }; + final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -310,6 +319,7 @@ public class WindowManagerService extends IWindowManager.Stub int mCurrentUserId; final Context mContext; + private Context mUiContext; final boolean mHaveInputMethods; @@ -847,6 +857,15 @@ public class WindowManagerService extends IWindowManager.Stub } finally { Surface.closeTransaction(); } + + ThemeUtils.registerThemeChangeReceiver(mContext, mThemeChangeReceiver); + } + + private Context getUiContext() { + if (mUiContext == null) { + mUiContext = ThemeUtils.createUiContext(mContext); + } + return mUiContext != null ? mUiContext : mContext; } public InputMonitor getInputMonitor() { @@ -5414,13 +5433,13 @@ public class WindowManagerService extends IWindowManager.Stub // Called by window manager policy. Not exposed externally. @Override public void shutdown(boolean confirm) { - ShutdownThread.shutdown(mContext, confirm); + ShutdownThread.shutdown(getUiContext(), confirm); } // Called by window manager policy. Not exposed externally. @Override public void rebootSafeMode(boolean confirm) { - ShutdownThread.rebootSafeMode(mContext, confirm); + ShutdownThread.rebootSafeMode(getUiContext(), confirm); } public void setInputFilter(IInputFilter filter) { @@ -5433,7 +5452,7 @@ public class WindowManagerService extends IWindowManager.Stub // Called by window manager policy. Not exposed externally. @Override public void reboot() { - ShutdownThread.reboot(mContext, null, true); + ShutdownThread.reboot(getUiContext(), null, true); } public void setCurrentUser(final int newUserId) { diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java index 2eba4e1..35148e6 100644 --- a/test-runner/src/android/test/mock/MockPackageManager.java +++ b/test-runner/src/android/test/mock/MockPackageManager.java @@ -577,6 +577,14 @@ public class MockPackageManager extends PackageManager { throw new UnsupportedOperationException(); } + /** + * @hide - to match hiding in superclass + */ + @Override + public List<PackageInfo> getInstalledThemePackages() { + throw new UnsupportedOperationException(); + } + @Override public void verifyPendingInstall(int id, int verificationCode) { throw new UnsupportedOperationException(); diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h index fde3bd6..a39f667 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. // @@ -48,7 +49,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), mJunkPath(false), mOutputAPKFile(NULL), @@ -90,6 +91,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; } @@ -243,6 +246,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 f398de0..946eaae 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" @@ -115,7 +117,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" @@ -313,6 +315,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 3d7b088..b370dca 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. // @@ -3750,7 +3751,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()); |