summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorRicardo Cerqueira <github@cerqueira.org>2012-05-05 18:49:09 +0100
committerRicardo Cerqueira <github@cerqueira.org>2012-05-05 18:49:17 +0100
commite821f5b727ef247a789557badb6640880cf992dd (patch)
treefc74bf9cc07a3eac04166477bed32ee5f9583d40
parentf0b7008ac173a4402b982b0f50ca3db8b0659bb7 (diff)
parentfd6ee4e7d2f1600021c0d2f47914c01600bd1b97 (diff)
downloadframeworks_base-e821f5b727ef247a789557badb6640880cf992dd.zip
frameworks_base-e821f5b727ef247a789557badb6640880cf992dd.tar.gz
frameworks_base-e821f5b727ef247a789557badb6640880cf992dd.tar.bz2
Merge branch 'themes-4.0' into 'ics'
Change-Id: Idc363f8140be2d252bee2aeba46c944032fb0ae9
-rw-r--r--Android.mk1
-rw-r--r--core/java/android/app/ActivityManager.java23
-rw-r--r--core/java/android/app/ActivityThread.java185
-rw-r--r--core/java/android/app/ApplicationPackageManager.java10
-rw-r--r--core/java/android/app/ContextImpl.java21
-rw-r--r--core/java/android/content/Intent.java23
-rw-r--r--core/java/android/content/pm/ActivityInfo.java5
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java28
-rw-r--r--core/java/android/content/pm/BaseThemeInfo.java244
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl3
-rw-r--r--core/java/android/content/pm/PackageInfo.java73
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/pm/PackageParser.java74
-rwxr-xr-xcore/java/android/content/pm/ThemeInfo.aidl3
-rw-r--r--core/java/android/content/pm/ThemeInfo.java205
-rw-r--r--core/java/android/content/res/AssetManager.java158
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java16
-rw-r--r--core/java/android/content/res/Configuration.java75
-rw-r--r--core/java/android/content/res/CustomTheme.java117
-rw-r--r--core/java/android/content/res/PackageRedirectionMap.aidl22
-rw-r--r--core/java/android/content/res/PackageRedirectionMap.java90
-rwxr-xr-xcore/java/android/content/res/Resources.java30
-rw-r--r--core/java/android/os/SystemProperties.java46
-rw-r--r--core/java/com/android/internal/app/IAssetRedirectionManager.aidl42
-rw-r--r--core/java/com/android/internal/os/ZygoteInit.java4
-rwxr-xr-xcore/jni/Android.mk1
-rw-r--r--core/jni/AndroidRuntime.cpp2
-rw-r--r--core/jni/android_util_AssetManager.cpp346
-rw-r--r--core/jni/android_util_PackageRedirectionMap.cpp176
-rw-r--r--core/res/AndroidManifest.xml13
-rw-r--r--data/etc/com.tmobile.software.themes.xml20
-rw-r--r--data/etc/platform.xml5
-rw-r--r--include/utils/AssetManager.h18
-rw-r--r--include/utils/PackageRedirectionMap.h68
-rw-r--r--include/utils/ResourceTypes.h13
-rw-r--r--include/utils/ZipEntry.h345
-rw-r--r--include/utils/ZipFile.h270
-rw-r--r--libs/utils/Android.mk3
-rw-r--r--libs/utils/AssetManager.cpp210
-rw-r--r--libs/utils/PackageRedirectionMap.cpp191
-rw-r--r--libs/utils/ResourceTypes.cpp116
-rw-r--r--media/java/android/media/Ringtone.java25
-rw-r--r--media/java/android/media/RingtoneManager.java76
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java11
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml3
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUI.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java107
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java72
-rwxr-xr-xpolicy/src/com/android/internal/policy/impl/PhoneWindowManager.java12
-rw-r--r--services/java/com/android/server/AppsLaunchFailureReceiver.java73
-rw-r--r--services/java/com/android/server/AssetRedirectionManagerService.java397
-rw-r--r--services/java/com/android/server/SystemServer.java18
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java15
-rw-r--r--services/java/com/android/server/pm/PackageManagerService.java200
-rw-r--r--services/java/com/android/server/pm/Settings.java4
-rw-r--r--test-runner/src/android/test/mock/MockPackageManager.java8
-rw-r--r--tools/aapt/Bundle.h6
-rw-r--r--tools/aapt/Main.cpp14
-rw-r--r--tools/aapt/ResourceTable.cpp12
66 files changed, 4220 insertions, 180 deletions
diff --git a/Android.mk b/Android.mk
index 7370455..5ea36b1 100644
--- a/Android.mk
+++ b/Android.mk
@@ -149,6 +149,7 @@ LOCAL_SRC_FILES += \
core/java/com/android/internal/app/IBatteryStats.aidl \
core/java/com/android/internal/app/IUsageStats.aidl \
core/java/com/android/internal/app/IMediaContainerService.aidl \
+ core/java/com/android/internal/app/IAssetRedirectionManager.aidl \
core/java/com/android/internal/appwidget/IAppWidgetService.aidl \
core/java/com/android/internal/appwidget/IAppWidgetHost.aidl \
core/java/com/android/internal/backup/IBackupTransport.aidl \
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index fdf8921..0f20809 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1548,6 +1549,16 @@ public class ActivityManager {
return new HashMap<String, Integer>();
}
}
+ /**
+ * @hide
+ */
+ public Configuration getConfiguration() {
+ try {
+ return ActivityManagerNative.getDefault().getConfiguration();
+ } catch (RemoteException e) {
+ return null;
+ }
+ }
/**
* Returns the usage statistics of each installed package.
@@ -1579,4 +1590,16 @@ public class ActivityManager {
}
}
+ /**
+ * @throws SecurityException Throws SecurityException if the caller does
+ * not hold the {@link android.Manifest.permission#CHANGE_CONFIGURATION} permission.
+ *
+ * @hide
+ */
+ public void updateConfiguration(Configuration values) throws SecurityException {
+ try {
+ ActivityManagerNative.getDefault().updateConfiguration(values);
+ } catch (RemoteException e) {
+ }
+ }
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 0c761fc..a0ea99e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,19 +17,28 @@
package android.app;
+import com.android.internal.app.IAssetRedirectionManager;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SamplingProfilerIntegration;
+
+import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
+
import android.app.backup.BackupAgent;
import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks2;
import android.content.ComponentName;
import android.content.ContentProvider;
import android.content.Context;
+import android.content.ContextWrapper;
import android.content.IContentProvider;
-import android.content.Intent;
import android.content.IIntentReceiver;
+import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ProviderInfo;
@@ -36,6 +46,8 @@ import android.content.pm.ServiceInfo;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.CustomTheme;
+import android.content.res.PackageRedirectionMap;
import android.content.res.Resources;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDebug;
@@ -47,6 +59,7 @@ import android.net.Proxy;
import android.net.ProxyProperties;
import android.opengl.GLUtils;
import android.os.AsyncTask;
+import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
@@ -60,6 +73,7 @@ import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.StrictMode;
import android.os.SystemClock;
+import android.text.TextUtils;
import android.util.AndroidRuntimeException;
import android.util.DisplayMetrics;
import android.util.EventLog;
@@ -68,6 +82,7 @@ import android.util.LogPrinter;
import android.util.Slog;
import android.view.Display;
import android.view.HardwareRenderer;
+import android.view.InflateException;
import android.view.View;
import android.view.ViewDebug;
import android.view.ViewManager;
@@ -76,12 +91,6 @@ import android.view.Window;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
-import com.android.internal.os.BinderInternal;
-import com.android.internal.os.RuntimeInit;
-import com.android.internal.os.SamplingProfilerIntegration;
-
-import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl;
-
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
@@ -140,6 +149,7 @@ public final class ActivityThread {
static ContextImpl mSystemContext = null;
static IPackageManager sPackageManager;
+ static IAssetRedirectionManager sAssetRedirectionManager;
final ApplicationThread mAppThread = new ApplicationThread();
final Looper mLooper = Looper.myLooper();
@@ -1355,12 +1365,14 @@ public final class ActivityThread {
private static class ResourcesKey {
final private String mResDir;
final private float mScale;
+ final private boolean mIsThemeable;
final private int mHash;
- ResourcesKey(String resDir, float scale) {
+ ResourcesKey(String resDir, float scale, boolean isThemeable) {
mResDir = resDir;
mScale = scale;
- mHash = mResDir.hashCode() << 2 + (int) (mScale * 2);
+ mIsThemeable = isThemeable;
+ mHash = mResDir.hashCode() << 3 + ((mIsThemeable ? 1 : 0) << 2) + (int) (mScale * 2);
}
@Override
@@ -1374,7 +1386,8 @@ public final class ActivityThread {
return false;
}
ResourcesKey peer = (ResourcesKey) obj;
- return mResDir.equals(peer.mResDir) && mScale == peer.mScale;
+ return mResDir.equals(peer.mResDir) && mScale == peer.mScale &&
+ mIsThemeable == peer.mIsThemeable;
}
}
@@ -1405,6 +1418,18 @@ public final class ActivityThread {
return sPackageManager;
}
+ // NOTE: this method can return null if the SystemServer is still
+ // initializing (for example, of another SystemServer component is accessing
+ // a resources object)
+ public static IAssetRedirectionManager getAssetRedirectionManager() {
+ if (sAssetRedirectionManager != null) {
+ return sAssetRedirectionManager;
+ }
+ IBinder b = ServiceManager.getService("assetredirection");
+ sAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b);
+ return sAssetRedirectionManager;
+ }
+
DisplayMetrics getDisplayMetricsLocked(CompatibilityInfo ci, boolean forceUpdate) {
DisplayMetrics dm = mDisplayMetrics.get(ci);
if (dm != null && !forceUpdate) {
@@ -1454,7 +1479,7 @@ public final class ActivityThread {
* null.
*/
Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
- ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
+ ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale, compInfo.isThemeable);
Resources r;
synchronized (mPackages) {
// Resources is app scale dependent.
@@ -1480,10 +1505,23 @@ public final class ActivityThread {
//}
AssetManager assets = new AssetManager();
+ assets.setThemeSupport(compInfo.isThemeable);
if (assets.addAssetPath(resDir) == 0) {
return null;
}
+ /* Attach theme information to the resulting AssetManager when appropriate. */
+ Configuration config = getConfiguration();
+ if (compInfo.isThemeable && config != null) {
+ if (config.customTheme == null) {
+ config.customTheme = CustomTheme.getBootTheme();
+ }
+
+ if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) {
+ attachThemeAssets(assets, config.customTheme);
+ }
+ }
+
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
r = new Resources(assets, metrics, getConfiguration(), compInfo);
@@ -1509,6 +1547,81 @@ public final class ActivityThread {
}
}
+ private void detachThemeAssets(AssetManager assets) {
+ String themePackageName = assets.getThemePackageName();
+ int themeCookie = assets.getThemeCookie();
+ if (!TextUtils.isEmpty(themePackageName) && themeCookie != 0) {
+ assets.detachThemePath(themePackageName, themeCookie);
+ assets.setThemePackageName(null);
+ assets.setThemeCookie(0);
+ assets.clearRedirections();
+ }
+ }
+
+ /**
+ * Attach the necessary theme asset paths and meta information to convert an
+ * AssetManager to being globally "theme-aware".
+ *
+ * @param assets
+ * @param theme
+ * @return true if the AssetManager is now theme-aware; false otherwise.
+ * This can fail, for example, if the theme package has been been
+ * removed and the theme manager has yet to revert formally back to
+ * the framework default.
+ */
+ private boolean attachThemeAssets(AssetManager assets, CustomTheme theme) {
+ IAssetRedirectionManager rm = getAssetRedirectionManager();
+ if (rm == null) {
+ return false;
+ }
+ PackageInfo pi = null;
+ try {
+ pi = getPackageManager().getPackageInfo(theme.getThemePackageName(), 0);
+ } catch (RemoteException e) {
+ }
+ if (pi != null && pi.applicationInfo != null && pi.themeInfos != null) {
+ String themeResDir = pi.applicationInfo.publicSourceDir;
+ int cookie = assets.attachThemePath(themeResDir);
+ if (cookie != 0) {
+ String themePackageName = theme.getThemePackageName();
+ String themeId = theme.getThemeId();
+ int N = assets.getBasePackageCount();
+ for (int i = 0; i < N; i++) {
+ String packageName = assets.getBasePackageName(i);
+ int packageId = assets.getBasePackageId(i);
+
+ /*
+ * For now, we only consider redirections coming from the
+ * framework or regular android packages. This excludes
+ * themes and other specialty APKs we are not aware of.
+ */
+ if (packageId != 0x01 && packageId != 0x7f) {
+ continue;
+ }
+
+ try {
+ PackageRedirectionMap map = rm.getPackageRedirectionMap(themePackageName, themeId,
+ packageName);
+ if (map != null) {
+ assets.addRedirections(map);
+ }
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failure accessing package redirection map, removing theme support.");
+ assets.detachThemePath(themePackageName, cookie);
+ return false;
+ }
+ }
+
+ assets.setThemePackageName(theme.getThemePackageName());
+ assets.setThemeCookie(cookie);
+ return true;
+ } else {
+ Log.e(TAG, "Unable to attach theme assets at " + themeResDir);
+ }
+ }
+ return false;
+ }
+
/**
* Creates the top level resources for the given package.
*/
@@ -1953,6 +2066,16 @@ public final class ActivityThread {
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
+ if (e instanceof InflateException) {
+ Log.e(TAG, "Failed to inflate", e);
+ String pkg = null;
+ if (r.packageInfo != null && !TextUtils.isEmpty(r.packageInfo.getPackageName())) {
+ pkg = r.packageInfo.getPackageName();
+ }
+ Intent intent = new Intent(Intent.ACTION_APP_LAUNCH_FAILURE,
+ (pkg != null)? Uri.fromParts("package", pkg, null) : null);
+ getSystemContext().sendBroadcast(intent);
+ }
throw new RuntimeException(
"Unable to start activity " + component
+ ": " + e.toString(), e);
@@ -3478,7 +3601,7 @@ public final class ActivityThread {
}
}
- final boolean applyConfigurationToResourcesLocked(Configuration config,
+ final int applyConfigurationToResourcesLocked(Configuration config,
CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
@@ -3486,7 +3609,7 @@ public final class ActivityThread {
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq);
- return false;
+ return 0;
}
int changes = mResConfiguration.updateFrom(config);
DisplayMetrics dm = getDisplayMetricsLocked(null, true);
@@ -3519,7 +3642,20 @@ public final class ActivityThread {
if (r != null) {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + config);
+ boolean themeChanged = (changes & ActivityInfo.CONFIG_THEME_RESOURCE) != 0;
+ if (themeChanged) {
+ AssetManager am = r.getAssets();
+ if (am.hasThemeSupport()) {
+ detachThemeAssets(am);
+ if (!TextUtils.isEmpty(config.customTheme.getThemePackageName())) {
+ attachThemeAssets(am, config.customTheme);
+ }
+ }
+ }
r.updateConfiguration(config, dm, compat);
+ if (themeChanged) {
+ r.updateStringCache();
+ }
//Slog.i(TAG, "Updated app resources " + v.getKey()
// + " " + r + ": " + r.getConfiguration());
} else {
@@ -3528,7 +3664,7 @@ public final class ActivityThread {
}
}
- return changes != 0;
+ return changes;
}
final Configuration applyCompatConfiguration() {
@@ -3548,6 +3684,8 @@ public final class ActivityThread {
ArrayList<ComponentCallbacks2> callbacks = null;
+ int diff = 0;
+
synchronized (mPackages) {
if (mPendingConfiguration != null) {
if (!mPendingConfiguration.isOtherSeqNewer(config)) {
@@ -3563,7 +3701,7 @@ public final class ActivityThread {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+ config);
- applyConfigurationToResourcesLocked(config, compat);
+ diff = applyConfigurationToResourcesLocked(config, compat);
if (mConfiguration == null) {
mConfiguration = new Configuration();
@@ -3582,7 +3720,20 @@ public final class ActivityThread {
if (callbacks != null) {
final int N = callbacks.size();
for (int i=0; i<N; i++) {
- performConfigurationChanged(callbacks.get(i), config);
+ ComponentCallbacks2 cb = callbacks.get(i);
+
+ // We removed the old resources object from the mActiveResources
+ // cache, now we need to trigger an update for each application.
+ if ((diff & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) {
+ if (cb instanceof Activity || cb instanceof Application) {
+ Context context = ((ContextWrapper)cb).getBaseContext();
+ if (context instanceof ContextImpl) {
+ ((ContextImpl)context).refreshResourcesIfNecessary();
+ }
+ }
+ }
+
+ performConfigurationChanged(cb, config);
}
}
}
@@ -4356,7 +4507,7 @@ public final class ActivityThread {
// We need to apply this change to the resources
// immediately, because upon returning the view
// hierarchy will be informed about it.
- if (applyConfigurationToResourcesLocked(newConfig, null)) {
+ if (applyConfigurationToResourcesLocked(newConfig, null) != 0) {
// This actually changed the resources! Tell
// everyone about it.
if (mPendingConfiguration == null ||
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 180a442..ce8fd39 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -403,6 +403,16 @@ final class ApplicationPackageManager extends PackageManager {
@SuppressWarnings("unchecked")
@Override
+ public List<PackageInfo> getInstalledThemePackages() {
+ try {
+ return mPM.getInstalledThemePackages();
+ } catch (RemoteException e) {
+ throw new RuntimeException("Package manager has died", e);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
public List<ApplicationInfo> getInstalledApplications(int flags) {
try {
final List<ApplicationInfo> applicationInfos = new ArrayList<ApplicationInfo>();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 9d972b9..a8e40fd 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,15 +19,17 @@ package android.app;
import com.android.internal.policy.PolicyManager;
+import android.accounts.AccountManager;
+import android.accounts.IAccountManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.IContentProvider;
+import android.content.IIntentReceiver;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.IIntentReceiver;
import android.content.IntentSender;
import android.content.ReceiverCallNotAllowedException;
import android.content.ServiceConnection;
@@ -36,6 +39,8 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.CustomTheme;
import android.content.res.Resources;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
@@ -491,6 +496,20 @@ class ContextImpl extends Context {
return mResources;
}
+ /**
+ * Refresh resources object which may have been changed by a theme
+ * configuration change.
+ */
+ /* package */ void refreshResourcesIfNecessary() {
+ if (mResources == Resources.getSystem()) {
+ return;
+ }
+
+ if (mPackageInfo.mCompatibilityInfo.get().isThemeable) {
+ mTheme = null;
+ }
+ }
+
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index 34966bb..af6f3b3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -2140,6 +2141,19 @@ public class Intent implements Parcelable, Cloneable {
public static final String ACTION_PRE_BOOT_COMPLETED =
"android.intent.action.PRE_BOOT_COMPLETED";
+ /**
+ * Broadcast Action: Indicate that unrecoverable error happened during app launch.
+ * Could indicate that curently applied theme is malicious.
+ * @hide
+ */
+ public static final String ACTION_APP_LAUNCH_FAILURE = "com.tmobile.intent.action.APP_LAUNCH_FAILURE";
+
+ /**
+ * Broadcast Action: Request to reset the unrecoverable errors count to 0.
+ * @hide
+ */
+ public static final String ACTION_APP_LAUNCH_FAILURE_RESET = "com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Standard intent categories (see addCategory()).
@@ -2272,6 +2286,7 @@ public class Intent implements Parcelable, Cloneable {
*/
public static final String CATEGORY_FRAMEWORK_INSTRUMENTATION_TEST =
"android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST";
+
/**
* An activity to run when device is inserted into a car dock.
* Used with {@link #ACTION_MAIN} to launch an activity. For more
@@ -2308,6 +2323,14 @@ public class Intent implements Parcelable, Cloneable {
@SdkConstant(SdkConstantType.INTENT_CATEGORY)
public static final String CATEGORY_CAR_MODE = "android.intent.category.CAR_MODE";
+ /**
+ * Used to indicate that a theme package has been installed or un-installed.
+ *
+ * @hide
+ */
+ public static final String CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE =
+ "com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE";
+
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Application launch intent categories (see addCategory()).
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0e6694d..d320768 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -321,6 +322,10 @@ public class ActivityInfo extends ComponentInfo
*/
public static final int CONFIG_ORIENTATION = 0x0080;
/**
+ * @hide
+ */
+ public static final int CONFIG_THEME_RESOURCE = 0x008000;
+ /**
* Bit in {@link #configChanges} that indicates that the activity
* can itself handle changes to the screen layout. Set from the
* {@link android.R.attr#configChanges} attribute.
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 65a8750..af7dcd2 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -419,6 +420,30 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
* @hide
*/
public int enabledSetting = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+ /**
+ * Is given application theme agnostic, i.e. behaves properly when default theme is changed.
+ * {@hide}
+ */
+ public boolean isThemeable = false;
+
+ private static final String PLUTO_SCHEMA = "http://www.w3.org/2001/pluto.html";
+
+ /**
+ * @hide
+ */
+ public static final String PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME = "isThemeable";
+
+ /**
+ * @hide
+ */
+ public static final String PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME = "handleThemeConfigChanges";
+
+ /**
+ * @hide
+ */
+ public static boolean isPlutoNamespace(String namespace) {
+ return namespace != null && namespace.equalsIgnoreCase(PLUTO_SCHEMA);
+ }
/**
* For convenient access to package's install location.
@@ -520,6 +545,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
manageSpaceActivityName = orig.manageSpaceActivityName;
descriptionRes = orig.descriptionRes;
uiOptions = orig.uiOptions;
+ isThemeable = orig.isThemeable;
}
@@ -559,6 +585,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
dest.writeString(backupAgentName);
dest.writeInt(descriptionRes);
dest.writeInt(uiOptions);
+ dest.writeInt(isThemeable? 1 : 0);
}
public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -597,6 +624,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
backupAgentName = source.readString();
descriptionRes = source.readInt();
uiOptions = source.readInt();
+ isThemeable = source.readInt() != 0;
}
/**
diff --git a/core/java/android/content/pm/BaseThemeInfo.java b/core/java/android/content/pm/BaseThemeInfo.java
new file mode 100644
index 0000000..0171137b
--- /dev/null
+++ b/core/java/android/content/pm/BaseThemeInfo.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.os.Parcelable;
+import android.os.Parcel;
+import android.util.Log;
+import android.util.AttributeSet;
+import android.content.res.Resources;
+
+/**
+ * @hide
+ */
+public class BaseThemeInfo implements Parcelable {
+
+ /**
+ * Wallpaper drawable.
+ *
+ * @see wallpaperImage attribute
+ */
+ public int wallpaperResourceId;
+
+ /**
+ * The resource id of theme thumbnail.
+ * Specifies a theme thumbnail image resource as @drawable/foo.
+ *
+ * @see thumbnail attribute
+ *
+ */
+ public int thumbnailResourceId;
+
+ /**
+ * The theme id, which does not change when the theme is modified.
+ * Specifies an Android UI Style using style name.
+ *
+ * @see themeId attribute
+ *
+ */
+ public String themeId;
+
+ /**
+ * The style resource id of Android UI Style, supplied by the resource commpiler.
+ * Specifies an Android UI Style id.
+ *
+ * @see styleId attribute
+ *
+ */
+ public int styleResourceId = 0;
+
+ /**
+ * The name of the theme (as displayed by UI).
+ *
+ * @see name attribute
+ *
+ */
+ public String name;
+
+ /**
+ * The name of the call ringtone audio file.
+ * Specifies a relative path in assets subfolder.
+ * If the parent's name is "locked" - DRM protected.
+ *
+ * @see ringtoneFileName attribute
+ *
+ */
+ public String ringtoneFileName;
+
+ /**
+ * The name of the call ringtone as shown to user.
+ *
+ * @see ringtoneName attribute
+ *
+ */
+ public String ringtoneName;
+
+ /**
+ * The name of the notification ringtone audio file.
+ * Specifies a relative path in assets subfolder.
+ * If the parent's name is "locked" - DRM protected.
+ *
+ * @see notificationRingtoneFileName attribute
+ *
+ */
+ public String notificationRingtoneFileName;
+
+ /**
+ * The name of the notification ringtone as shown to user.
+ *
+ * @see notificationRingtoneName attribute
+ *
+ */
+ public String notificationRingtoneName;
+
+ /**
+ * The author name of the theme package.
+ *
+ * @see author attribute
+ *
+ */
+ public String author;
+
+ /**
+ * The copyright text.
+ *
+ * @see copyright attribute
+ *
+ */
+ public String copyright;
+
+ /**
+ * {@hide}
+ */
+ // There is no corresposponding flag in manifest file
+ // This flag is set to true iff any media resource is DRM protected
+ public boolean isDrmProtected = false;
+
+ /**
+ * The name of the "main" theme style (as displayed by UI).
+ *
+ * @see themeStyleName attribute
+ *
+ */
+ public String themeStyleName;
+
+ /**
+ * Preview image drawable.
+ *
+ * @see preview attribute
+ */
+ public int previewResourceId;
+
+ /**
+ * The name of a sound pack.
+ *
+ * @see soundpack attribute
+ *
+ */
+ public String soundPackName;
+
+
+ private static final String LOCKED_NAME = "locked/";
+
+ /*
+ * Describe the kinds of special objects contained in this Parcelable's
+ * marshalled representation.
+ *
+ * @return a bitmask indicating the set of special object types marshalled
+ * by the Parcelable.
+ *
+ * @see android.os.Parcelable#describeContents()
+ */
+ public int describeContents() {
+ return 0;
+ }
+
+ /*
+ * Flatten this object in to a Parcel.
+ *
+ * @param dest The Parcel in which the object should be written.
+ * @param flags Additional flags about how the object should be written.
+ * May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+ *
+ * @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
+ */
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(wallpaperResourceId);
+ dest.writeInt(thumbnailResourceId);
+ dest.writeString(themeId);
+ dest.writeInt(styleResourceId);
+ dest.writeString(name);
+ dest.writeString(ringtoneFileName);
+ dest.writeString(notificationRingtoneFileName);
+ dest.writeString(ringtoneName);
+ dest.writeString(notificationRingtoneName);
+ dest.writeString(author);
+ dest.writeString(copyright);
+ dest.writeInt(isDrmProtected? 1 : 0);
+ dest.writeString(soundPackName);
+ dest.writeString(themeStyleName);
+ dest.writeInt(previewResourceId);
+ }
+
+ /** @hide */
+ public static final Parcelable.Creator<BaseThemeInfo> CREATOR
+ = new Parcelable.Creator<BaseThemeInfo>() {
+ public BaseThemeInfo createFromParcel(Parcel source) {
+ return new BaseThemeInfo(source);
+ }
+
+ public BaseThemeInfo[] newArray(int size) {
+ return new BaseThemeInfo[size];
+ }
+ };
+
+ /** @hide */
+ public final String getResolvedString(Resources res, AttributeSet attrs, int index) {
+ int resId = attrs.getAttributeResourceValue(index, 0);
+ if (resId !=0 ) {
+ return res.getString(resId);
+ }
+ return attrs.getAttributeValue(index);
+ }
+
+ protected BaseThemeInfo() {
+ }
+
+ protected BaseThemeInfo(Parcel source) {
+ wallpaperResourceId = source.readInt();
+ thumbnailResourceId = source.readInt();
+ themeId = source.readString();
+ styleResourceId = source.readInt();
+ name = source.readString();
+ ringtoneFileName = source.readString();
+ notificationRingtoneFileName = source.readString();
+ ringtoneName = source.readString();
+ notificationRingtoneName = source.readString();
+ author = source.readString();
+ copyright = source.readString();
+ isDrmProtected = (source.readInt() != 0);
+ soundPackName = source.readString();
+ themeStyleName = source.readString();
+ previewResourceId = source.readInt();
+ }
+
+ protected void changeDrmFlagIfNeeded(String resourcePath) {
+ if (resourcePath != null && resourcePath.contains(LOCKED_NAME)) {
+ isDrmProtected = true;
+ }
+ }
+}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index decb974..0842820 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -39,6 +39,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.UserInfo;
import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.ThemeInfo;
import android.net.Uri;
import android.content.IntentSender;
@@ -121,6 +122,8 @@ interface IPackageManager {
*/
ParceledListSlice getInstalledPackages(int flags, in String lastRead);
+ List<PackageInfo> getInstalledThemePackages();
+
/**
* This implements getInstalledApplications via a "last returned row"
* mechanism that is not exposed in the API. This is to get around the IPC
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index eb05d76..42dd621 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -194,9 +195,69 @@ public class PackageInfo implements Parcelable {
*/
public int installLocation = INSTALL_LOCATION_INTERNAL_ONLY;
+ // Is Theme Apk
+ /**
+ * {@hide}
+ */
+ public boolean isThemeApk = false;
+
+ // ThemeInfo
+ /**
+ * {@hide}
+ */
+ public ThemeInfo [] themeInfos;
+
public PackageInfo() {
}
+ /*
+ * Is Theme Apk is DRM protected (contains DRM-protected resources)
+ *
+ */
+ private boolean drmProtectedThemeApk = false;
+
+ /**
+ * @hide
+ *
+ * @return Is Theme Apk is DRM protected (contains DRM-protected resources)
+ */
+ public boolean isDrmProtectedThemeApk() {
+ return drmProtectedThemeApk;
+ }
+
+ /**
+ * @hide
+ *
+ * @param value if Theme Apk is DRM protected (contains DRM-protected resources)
+ */
+ public void setDrmProtectedThemeApk(boolean value) {
+ drmProtectedThemeApk = value;
+ }
+
+ /*
+ * If isThemeApk and isDrmProtectedThemeApk are true - path to hidden locked zip file
+ *
+ */
+ private String lockedZipFilePath;
+
+ /**
+ * @hide
+ *
+ * @return path for hidden locked zip file
+ */
+ public String getLockedZipFilePath() {
+ return lockedZipFilePath;
+ }
+
+ /**
+ * @hide
+ *
+ * @param value path for hidden locked zip file
+ */
+ public void setLockedZipFilePath(String value) {
+ lockedZipFilePath = value;
+ }
+
public String toString() {
return "PackageInfo{"
+ Integer.toHexString(System.identityHashCode(this))
@@ -233,6 +294,12 @@ public class PackageInfo implements Parcelable {
dest.writeTypedArray(configPreferences, parcelableFlags);
dest.writeTypedArray(reqFeatures, parcelableFlags);
dest.writeInt(installLocation);
+
+ /* Theme-specific. */
+ dest.writeInt((isThemeApk)? 1 : 0);
+ dest.writeInt((drmProtectedThemeApk)? 1 : 0);
+ dest.writeTypedArray(themeInfos, parcelableFlags);
+ dest.writeString(lockedZipFilePath);
}
public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -270,5 +337,11 @@ public class PackageInfo implements Parcelable {
configPreferences = source.createTypedArray(ConfigurationInfo.CREATOR);
reqFeatures = source.createTypedArray(FeatureInfo.CREATOR);
installLocation = source.readInt();
+
+ /* Theme-specific. */
+ isThemeApk = (source.readInt() != 0);
+ drmProtectedThemeApk = (source.readInt() != 0);
+ themeInfos = source.createTypedArray(ThemeInfo.CREATOR);
+ lockedZipFilePath = source.readString();
}
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 8541748..c249a53 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -1407,6 +1408,17 @@ public abstract class PackageManager {
public abstract List<PackageInfo> getInstalledPackages(int flags);
/**
+ * Return a List of all theme packages that are installed
+ * on the device.
+ *
+ * @return A List of PackageInfo objects, one for each theme package
+ * that is installed on the device.
+ *
+ * @hide
+ */
+ public abstract List<PackageInfo> getInstalledThemePackages();
+
+ /**
* Check whether a particular package has been granted a particular
* permission.
*
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e593d5b..fddd0bc 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -200,6 +201,17 @@ public class PackageParser {
return name.endsWith(".apk");
}
+ public static String getLockedZipFilePath(String path) {
+ if (path == null) {
+ return null;
+ }
+ if (isPackageFilename(path)) {
+ return path.substring(0, path.length() - 4) + ".locked.zip";
+ } else {
+ return path + ".locked.zip";
+ }
+ }
+
/**
* Generate and return the {@link PackageInfo} for a parsed package.
*
@@ -215,6 +227,21 @@ public class PackageParser {
pi.versionName = p.mVersionName;
pi.sharedUserId = p.mSharedUserId;
pi.sharedUserLabel = p.mSharedUserLabel;
+ pi.isThemeApk = p.mIsThemeApk;
+ pi.setDrmProtectedThemeApk(false);
+ if (pi.isThemeApk) {
+ int N = p.mThemeInfos.size();
+ if (N > 0) {
+ pi.themeInfos = new ThemeInfo[N];
+ for (int i = 0; i < N; i++) {
+ pi.themeInfos[i] = p.mThemeInfos.get(i);
+ pi.setDrmProtectedThemeApk(pi.isDrmProtectedThemeApk() || pi.themeInfos[i].isDrmProtected);
+ }
+ if (pi.isDrmProtectedThemeApk()) {
+ pi.setLockedZipFilePath(PackageParser.getLockedZipFilePath(p.mPath));
+ }
+ }
+ }
pi.applicationInfo = generateApplicationInfo(p, flags);
pi.installLocation = p.installLocation;
pi.firstInstallTime = firstInstallTime;
@@ -1180,7 +1207,10 @@ public class PackageParser {
// Just skip this tag
XmlUtils.skipCurrentTag(parser);
continue;
-
+ } else if (tagName.equals("theme")) {
+ // this is a theme apk.
+ pkg.mIsThemeApk = true;
+ pkg.mThemeInfos.add(new ThemeInfo(parser, res, attrs));
} else if (RIGID_PARSER) {
outError[0] = "Bad element under <manifest>: "
+ parser.getName();
@@ -1253,6 +1283,9 @@ public class PackageParser {
>= android.os.Build.VERSION_CODES.DONUT)) {
pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES;
}
+ if (pkg.mIsThemeApk) {
+ pkg.applicationInfo.isThemeable = false;
+ }
return pkg;
}
@@ -1536,12 +1569,43 @@ public class PackageParser {
return a;
}
+ private void parseApplicationThemeAttributes(XmlPullParser parser, AttributeSet attrs,
+ ApplicationInfo appInfo) {
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) {
+ continue;
+ }
+ String attrName = attrs.getAttributeName(i);
+ if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_ISTHEMEABLE_ATTRIBUTE_NAME)) {
+ appInfo.isThemeable = attrs.getAttributeBooleanValue(i, false);
+ return;
+ }
+ }
+ }
+
+ private void parseActivityThemeAttributes(XmlPullParser parser, AttributeSet attrs,
+ ActivityInfo ai) {
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) {
+ continue;
+ }
+ String attrName = attrs.getAttributeName(i);
+ if (attrName.equalsIgnoreCase(ApplicationInfo.PLUTO_HANDLE_THEME_CONFIG_CHANGES_ATTRIBUTE_NAME)) {
+ ai.configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ }
+ }
+ }
+
private boolean parseApplication(Package owner, Resources res,
XmlPullParser parser, AttributeSet attrs, int flags, String[] outError)
throws XmlPullParserException, IOException {
final ApplicationInfo ai = owner.applicationInfo;
final String pkgName = owner.applicationInfo.packageName;
+ // assume that this package is themeable unless explicitly set to false.
+ ai.isThemeable = true;
+ parseApplicationThemeAttributes(parser, attrs, ai);
+
TypedArray sa = res.obtainAttributes(attrs,
com.android.internal.R.styleable.AndroidManifestApplication);
@@ -2045,6 +2109,8 @@ public class PackageParser {
return null;
}
+ parseActivityThemeAttributes(parser, attrs, a.info);
+
int outerDepth = parser.getDepth();
int type;
while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
@@ -2982,6 +3048,12 @@ public class PackageParser {
// For use by package manager to keep track of where it has done dexopt.
public boolean mDidDexOpt;
+ // Is Theme Apk
+ public boolean mIsThemeApk = false;
+
+ // Theme info
+ public final ArrayList<ThemeInfo> mThemeInfos = new ArrayList<ThemeInfo>(0);
+
// User set enabled state.
public int mSetEnabled = PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
diff --git a/core/java/android/content/pm/ThemeInfo.aidl b/core/java/android/content/pm/ThemeInfo.aidl
new file mode 100755
index 0000000..acbc85e
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.aidl
@@ -0,0 +1,3 @@
+package android.content.pm;
+
+parcelable ThemeInfo;
diff --git a/core/java/android/content/pm/ThemeInfo.java b/core/java/android/content/pm/ThemeInfo.java
new file mode 100644
index 0000000..e51dbb6
--- /dev/null
+++ b/core/java/android/content/pm/ThemeInfo.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParser;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.content.res.Resources;
+
+/**
+ * Overall information about "theme" package. This corresponds
+ * to the information collected from AndroidManifest.xml (theme tag).
+ *
+ * Below is an example of theme tag
+ * <theme
+ * pluto:name="Pluto Default"
+ * pluto:preview="@drawable/preview"
+ * pluto:author="John Doe"
+ * pluto:ringtoneFileName="media/audio/ringtone.mp3"
+ * pluto:notificationRingtoneFileName="media/audio/locked/notification.mp3"
+ * pluto:copyright="T-Mobile, 2009"
+ * />
+ *
+ * @hide
+ */
+public final class ThemeInfo extends BaseThemeInfo {
+ private enum AttributeIndex {
+ THEME_PACKAGE_INDEX,
+ PREVIEW_INDEX,
+ AUTHOR_INDEX,
+ THEME_INDEX,
+ THEME_STYLE_NAME_INDEX,
+ THUMBNAIL_INDEX,
+ RINGTONE_FILE_NAME_INDEX,
+ NOTIFICATION_RINGTONE_FILE_NAME_INDEX,
+ WALLPAPER_IMAGE_INDEX,
+ COPYRIGHT_INDEX,
+ RINGTONE_NAME_INDEX,
+ NOTIFICATION_RINGTONE_NAME_INDEX,
+ STYLE_INDEX;
+
+ public static AttributeIndex get(int ordinal) {
+ return values()[ordinal];
+ }
+ };
+
+ private static final String [] compulsoryAttributes = new String [] {
+ "name",
+ "preview",
+ "author",
+ "themeId",
+ "styleName",
+ };
+
+ private static final String [] optionalAttributes = new String [] {
+ "thumbnail",
+ "ringtoneFileName",
+ "notificationRingtoneFileName",
+ "wallpaperImage",
+ "copyright",
+ "ringtoneName",
+ "notificationRingtoneName",
+ "styleId",
+ };
+
+ private static final Map<String, AttributeIndex> sAttributesLookupTable;
+
+ static {
+ sAttributesLookupTable = new HashMap<String, AttributeIndex>();
+ for (int i = 0; i < compulsoryAttributes.length; i++) {
+ sAttributesLookupTable.put(compulsoryAttributes[i], AttributeIndex.get(i));
+ }
+
+ for (int i = 0; i < optionalAttributes.length; i++) {
+ sAttributesLookupTable.put(optionalAttributes[i],
+ AttributeIndex.get(compulsoryAttributes.length + i));
+ }
+ }
+
+ public ThemeInfo(XmlPullParser parser, Resources res, AttributeSet attrs) throws XmlPullParserException {
+ super();
+
+ Map<String, AttributeIndex> tempMap =
+ new HashMap<String, AttributeIndex>(sAttributesLookupTable);
+ int numberOfCompulsoryAttributes = 0;
+ for (int i = 0; i < attrs.getAttributeCount(); i++) {
+ if (!ApplicationInfo.isPlutoNamespace(parser.getAttributeNamespace(i))) {
+ continue;
+ }
+ String key = attrs.getAttributeName(i);
+ if (tempMap.containsKey(key)) {
+ AttributeIndex index = tempMap.get(key);
+ tempMap.remove(key);
+
+ if (index.ordinal() < compulsoryAttributes.length) {
+ numberOfCompulsoryAttributes++;
+ }
+ switch (index) {
+ case THEME_PACKAGE_INDEX:
+ // theme name
+ name = getResolvedString(res, attrs, i);
+ break;
+
+ case THUMBNAIL_INDEX:
+ // theme thumbprint
+ thumbnailResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case AUTHOR_INDEX:
+ // theme author
+ author = getResolvedString(res, attrs, i);
+ break;
+
+ case THEME_INDEX:
+ // androidUiStyle attribute
+ themeId = attrs.getAttributeValue(i);
+ break;
+
+ case THEME_STYLE_NAME_INDEX:
+ themeStyleName = getResolvedString(res, attrs, i);
+ break;
+
+ case RINGTONE_FILE_NAME_INDEX:
+ // ringtone
+ ringtoneFileName = attrs.getAttributeValue(i);
+ changeDrmFlagIfNeeded(ringtoneFileName);
+ break;
+
+ case NOTIFICATION_RINGTONE_FILE_NAME_INDEX:
+ // notification ringtone
+ notificationRingtoneFileName = attrs.getAttributeValue(i);
+ changeDrmFlagIfNeeded(notificationRingtoneFileName);
+ break;
+
+ case WALLPAPER_IMAGE_INDEX:
+ // wallpaperImage attribute
+ wallpaperResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case COPYRIGHT_INDEX:
+ // themeCopyright attribute
+ copyright = getResolvedString(res, attrs, i);
+ break;
+
+ case RINGTONE_NAME_INDEX:
+ // ringtone UI name
+ ringtoneName = attrs.getAttributeValue(i);
+ break;
+
+ case NOTIFICATION_RINGTONE_NAME_INDEX:
+ // notification ringtone UI name
+ notificationRingtoneName = attrs.getAttributeValue(i);
+ break;
+
+ case STYLE_INDEX:
+ styleResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+
+ case PREVIEW_INDEX:
+ // theme thumbprint
+ previewResourceId = attrs.getAttributeResourceValue(i, 0);
+ break;
+ }
+ }
+ }
+ if (numberOfCompulsoryAttributes < compulsoryAttributes.length) {
+ throw new XmlPullParserException("Not all compulsory attributes are specified in <theme>");
+ }
+ }
+
+ public static final Parcelable.Creator<ThemeInfo> CREATOR
+ = new Parcelable.Creator<ThemeInfo>() {
+ public ThemeInfo createFromParcel(Parcel source) {
+ return new ThemeInfo(source);
+ }
+
+ public ThemeInfo[] newArray(int size) {
+ return new ThemeInfo[size];
+ }
+ };
+
+ private ThemeInfo(Parcel source) {
+ super(source);
+ }
+}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index ffefaa2..80d0946 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -18,6 +19,7 @@ package android.content.res;
import android.os.ParcelFileDescriptor;
import android.util.Log;
+import android.util.SparseArray;
import android.util.TypedValue;
import java.io.FileNotFoundException;
@@ -77,6 +79,20 @@ public final class AssetManager {
private boolean mOpen = true;
private HashMap<Integer, RuntimeException> mRefStacks;
+ private String mAssetDir;
+ private String mAppName;
+
+ private boolean mThemeSupport;
+ private String mThemePackageName;
+ private int mThemeCookie;
+
+ /**
+ * Organize all added redirection maps using Java strong references to keep
+ * the native layer cleanup simple (that is, finalize() in Java will be
+ * responsible for delete in C++).
+ */
+ private SparseArray<PackageRedirectionMap> mRedirections;
+
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
@@ -252,6 +268,12 @@ public final class AssetManager {
}
}
+ /*package*/ final void recreateStringBlocks() {
+ synchronized (this) {
+ makeStringBlocks(true);
+ }
+ }
+
/*package*/ final void makeStringBlocks(boolean copyFromSystem) {
final int sysNum = copyFromSystem ? sSystem.mStringBlocks.length : 0;
final int num = getStringBlockCount();
@@ -460,6 +482,18 @@ public final class AssetManager {
/**
* {@hide}
+ * Split a theme package with DRM-protected resources into two files.
+ *
+ * @param packageFileName Original theme package file name.
+ * @param lockedFileName Name of the new "locked" file with DRM resources.
+ * @param drmProtectedresources Array of names of DRM-protected assets.
+ */
+ public final int splitDrmProtectedThemePackage(String packageFileName, String lockedFileName, String [] drmProtectedresources) {
+ return splitThemePackage(packageFileName, lockedFileName, drmProtectedresources);
+ }
+
+ /**
+ * {@hide}
* Retrieve a non-asset as a compiled XML file. Not for use by
* applications.
*
@@ -625,6 +659,110 @@ public final class AssetManager {
}
/**
+ * Delete a set of theme assets from the asset manager. Not for use by
+ * applications. Returns true if succeeded or false on failure.
+ *
+ * @hide
+ */
+ public native final boolean detachThemePath(String packageName, int cookie);
+
+ /**
+ * Attach a set of theme assets to the asset manager. If necessary, this
+ * method will forcefully update the internal ResTable data structure.
+ *
+ * @return Cookie of the added asset or 0 on failure.
+ * @hide
+ */
+ public native final int attachThemePath(String path);
+
+ /**
+ * Sets a flag indicating that this AssetManager should have themes
+ * attached, according to the initial request to create it by the
+ * ApplicationContext.
+ *
+ * {@hide}
+ */
+ public final void setThemeSupport(boolean themeSupport) {
+ mThemeSupport = themeSupport;
+ }
+
+ /**
+ * Should this AssetManager have themes attached, according to the initial
+ * request to create it by the ApplicationContext?
+ *
+ * {@hide}
+ */
+ public final boolean hasThemeSupport() {
+ return mThemeSupport;
+ }
+
+ /**
+ * Apply a heuristic to match-up all attributes from the source style with
+ * attributes in the destination style. For each match, an entry in the
+ * package redirection map will be inserted.
+ *
+ * {@hide}
+ */
+ public native final boolean generateStyleRedirections(int resMapNative, int sourceStyle,
+ int destStyle);
+
+ /**
+ * Get package name of current theme (may return null).
+ * {@hide}
+ */
+ public String getThemePackageName() {
+ return mThemePackageName;
+ }
+
+ /**
+ * Sets package name and highest level style id for current theme (null, 0 is allowed).
+ * {@hide}
+ */
+ public void setThemePackageName(String packageName) {
+ mThemePackageName = packageName;
+ }
+
+ /**
+ * Get asset cookie for current theme (may return 0).
+ * {@hide}
+ */
+ public int getThemeCookie() {
+ return mThemeCookie;
+ }
+
+ /**
+ * Sets asset cookie for current theme (0 if not a themed asset manager).
+ * {@hide}
+ */
+ public void setThemeCookie(int cookie) {
+ mThemeCookie = cookie;
+ }
+
+ /**
+ * Add a redirection map to the asset manager. All future resource lookups
+ * will consult this map.
+ * {@hide}
+ */
+ public void addRedirections(PackageRedirectionMap map) {
+ if (mRedirections == null) {
+ mRedirections = new SparseArray<PackageRedirectionMap>(2);
+ }
+ mRedirections.append(map.getPackageId(), map);
+ addRedirectionsNative(map.getNativePointer());
+ }
+
+ /**
+ * Clear redirection map for the asset manager.
+ * {@hide}
+ */
+ public void clearRedirections() {
+ if (mRedirections != null) {
+ mRedirections.clear();
+ }
+ clearRedirectionsNative();
+ }
+
+ /**
* Determine whether the state in this asset manager is up-to-date with
* the files on the filesystem. If false is returned, you need to
* instantiate a new AssetManager class to see the new data.
@@ -741,6 +879,26 @@ public final class AssetManager {
private native final int[] getArrayStringInfo(int arrayRes);
/*package*/ native final int[] getArrayIntResource(int arrayRes);
+ private native final int splitThemePackage(String srcFileName, String dstFileName, String [] drmProtectedAssetNames);
+
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageCount();
+
+ /**
+ * {@hide}
+ */
+ public native final String getBasePackageName(int index);
+
+ /**
+ * {@hide}
+ */
+ public native final int getBasePackageId(int index);
+
+ private native final void addRedirectionsNative(int redirectionMapNativePointer);
+ private native final void clearRedirectionsNative();
+
private native final void init();
private native final void destroy();
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 1c9285e..d6856c9 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -92,9 +92,15 @@ public class CompatibilityInfo implements Parcelable {
*/
public final float applicationInvertedScale;
+ /**
+ * Whether the application supports third-party theming.
+ */
+ public final boolean isThemeable;
+
public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
boolean forceCompat) {
int compatFlags = 0;
+ isThemeable = appInfo.isThemeable;
if (appInfo.requiresSmallestWidthDp != 0 || appInfo.compatibleWidthLimitDp != 0
|| appInfo.largestWidthLimitDp != 0) {
@@ -242,17 +248,19 @@ public class CompatibilityInfo implements Parcelable {
}
private CompatibilityInfo(int compFlags,
- int dens, float scale, float invertedScale) {
+ int dens, float scale, float invertedScale, boolean isThemeable) {
mCompatibilityFlags = compFlags;
applicationDensity = dens;
applicationScale = scale;
applicationInvertedScale = invertedScale;
+ this.isThemeable = isThemeable;
}
private CompatibilityInfo() {
this(NEVER_NEEDS_COMPAT, DisplayMetrics.DENSITY_DEVICE,
1.0f,
- 1.0f);
+ 1.0f,
+ true);
}
/**
@@ -519,6 +527,7 @@ public class CompatibilityInfo implements Parcelable {
if (applicationDensity != oc.applicationDensity) return false;
if (applicationScale != oc.applicationScale) return false;
if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+ if (isThemeable != oc.isThemeable) return false;
return true;
} catch (ClassCastException e) {
return false;
@@ -556,6 +565,7 @@ public class CompatibilityInfo implements Parcelable {
result = 31 * result + applicationDensity;
result = 31 * result + Float.floatToIntBits(applicationScale);
result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+ result = 31 * result + (isThemeable ? 1 : 0);
return result;
}
@@ -570,6 +580,7 @@ public class CompatibilityInfo implements Parcelable {
dest.writeInt(applicationDensity);
dest.writeFloat(applicationScale);
dest.writeFloat(applicationInvertedScale);
+ dest.writeInt(isThemeable ? 1 : 0);
}
public static final Parcelable.Creator<CompatibilityInfo> CREATOR
@@ -588,5 +599,6 @@ public class CompatibilityInfo implements Parcelable {
applicationDensity = source.readInt();
applicationScale = source.readFloat();
applicationInvertedScale = source.readFloat();
+ isThemeable = source.readInt() == 1 ? true : false;
}
}
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 5c3a17a..9822936 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2008 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +21,9 @@ import android.content.pm.ActivityInfo;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.LocaleUtil;
+import android.util.Log;
+import android.os.SystemProperties;
+import android.text.TextUtils;
import java.util.Locale;
@@ -56,6 +60,11 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public Locale locale;
/**
+ * @hide
+ */
+ public CustomTheme customTheme;
+
+ /**
* Locale should persist on setting. This is hidden because it is really
* questionable whether this is the right way to expose the functionality.
* @hide
@@ -214,7 +223,22 @@ public final class Configuration implements Parcelable, Comparable<Configuration
public static final int ORIENTATION_PORTRAIT = 1;
public static final int ORIENTATION_LANDSCAPE = 2;
public static final int ORIENTATION_SQUARE = 3;
-
+
+ /**
+ * @hide
+ */
+ public static final int THEME_UNDEFINED = 0;
+
+ /**
+ * @hide
+ */
+ public static final String THEME_ID_PERSISTENCE_PROPERTY = "persist.sys.themeId";
+
+ /**
+ * @hide
+ */
+ public static final String THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY = "persist.sys.themePackageName";
+
/**
* Overall orientation of the screen. May be one of
* {@link #ORIENTATION_LANDSCAPE}, {@link #ORIENTATION_PORTRAIT},
@@ -327,6 +351,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatScreenHeightDp = o.compatScreenHeightDp;
compatSmallestScreenWidthDp = o.compatSmallestScreenWidthDp;
seq = o.seq;
+ if (o.customTheme != null) {
+ customTheme = (CustomTheme) o.customTheme.clone();
+ }
}
public String toString() {
@@ -444,6 +471,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
sb.append(" s.");
sb.append(seq);
}
+ sb.append(" themeResource=");
+ sb.append(customTheme);
sb.append('}');
return sb.toString();
}
@@ -470,6 +499,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration
smallestScreenWidthDp = compatSmallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
textLayoutDirection = LocaleUtil.TEXT_LAYOUT_DIRECTION_LTR_DO_NOT_USE;
seq = 0;
+ customTheme = null;
}
/** {@hide} */
@@ -589,7 +619,13 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (delta.seq != 0) {
seq = delta.seq;
}
-
+
+ if (delta.customTheme != null
+ && (customTheme == null || !customTheme.equals(delta.customTheme))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ customTheme = (CustomTheme)delta.customTheme.clone();
+ }
+
return changed;
}
@@ -685,6 +721,10 @@ public final class Configuration implements Parcelable, Comparable<Configuration
&& smallestScreenWidthDp != delta.smallestScreenWidthDp) {
changed |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
}
+ if (delta.customTheme != null &&
+ (customTheme == null || !customTheme.equals(delta.customTheme))) {
+ changed |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ }
return changed;
}
@@ -701,7 +741,9 @@ public final class Configuration implements Parcelable, Comparable<Configuration
* @return Return true if the resource needs to be loaded, else false.
*/
public static boolean needNewResources(int configChanges, int interestingChanges) {
- return (configChanges & (interestingChanges|ActivityInfo.CONFIG_FONT_SCALE)) != 0;
+ return (configChanges & (interestingChanges |
+ ActivityInfo.CONFIG_FONT_SCALE |
+ ActivityInfo.CONFIG_THEME_RESOURCE)) != 0;
}
/**
@@ -774,6 +816,14 @@ public final class Configuration implements Parcelable, Comparable<Configuration
dest.writeInt(compatSmallestScreenWidthDp);
dest.writeInt(textLayoutDirection);
dest.writeInt(seq);
+
+ if (customTheme == null) {
+ dest.writeInt(0);
+ } else {
+ dest.writeInt(1);
+ dest.writeString(customTheme.getThemeId());
+ dest.writeString(customTheme.getThemePackageName());
+ }
}
public void readFromParcel(Parcel source) {
@@ -802,6 +852,12 @@ public final class Configuration implements Parcelable, Comparable<Configuration
compatSmallestScreenWidthDp = source.readInt();
textLayoutDirection = source.readInt();
seq = source.readInt();
+
+ if (source.readInt() != 0) {
+ String themeId = source.readString();
+ String themePackage = source.readString();
+ customTheme = new CustomTheme(themeId, themePackage);
+ }
}
public static final Parcelable.Creator<Configuration> CREATOR
@@ -868,6 +924,17 @@ public final class Configuration implements Parcelable, Comparable<Configuration
if (n != 0) return n;
n = this.smallestScreenWidthDp - that.smallestScreenWidthDp;
//if (n != 0) return n;
+ if (this.customTheme == null) {
+ if (that.customTheme != null) return 1;
+ } else if (that.customTheme == null) {
+ return -1;
+ } else {
+ n = this.customTheme.getThemeId().compareTo(that.customTheme.getThemeId());
+ if (n != 0) return n;
+ n = this.customTheme.getThemePackageName().compareTo(that.customTheme.getThemePackageName());
+ if (n != 0) return n;
+ }
+
return n;
}
@@ -903,6 +970,8 @@ public final class Configuration implements Parcelable, Comparable<Configuration
result = 31 * result + screenWidthDp;
result = 31 * result + screenHeightDp;
result = 31 * result + smallestScreenWidthDp;
+ result = 31 * result + (this.customTheme != null ?
+ this.customTheme.hashCode() : 0);
return result;
}
}
diff --git a/core/java/android/content/res/CustomTheme.java b/core/java/android/content/res/CustomTheme.java
new file mode 100644
index 0000000..364fb11
--- /dev/null
+++ b/core/java/android/content/res/CustomTheme.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+import android.os.SystemProperties;
+import android.text.TextUtils;
+
+/**
+ * @hide
+ */
+public final class CustomTheme implements Cloneable {
+ private final String mThemeId;
+ private final String mThemePackageName;
+
+ private static final CustomTheme sBootTheme = new CustomTheme();
+ private static final CustomTheme sSystemTheme = new CustomTheme("", "");
+
+ private CustomTheme() {
+ mThemeId = SystemProperties.get("persist.sys.themeId");
+ mThemePackageName = SystemProperties.get("persist.sys.themePackageName");
+ }
+
+ public CustomTheme(String themeId, String packageName) {
+ mThemeId = themeId;
+ mThemePackageName = packageName;
+ }
+
+ @Override
+ public Object clone() {
+ try {
+ return super.clone();
+ } catch (CloneNotSupportedException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (object == this) {
+ return true;
+ }
+ if (object instanceof CustomTheme) {
+ CustomTheme o = (CustomTheme) object;
+ if (!mThemeId.equals(o.mThemeId)) {
+ return false;
+ }
+ String currentPackageName = (mThemePackageName == null)? "" : mThemePackageName;
+ String newPackageName = (o.mThemePackageName == null)? "" : o.mThemePackageName;
+ String currentThemeId = (mThemeId == null)? "" : mThemeId;
+ String newThemeId = (o.mThemeId == null)? "" : o.mThemeId;
+
+ /* uhh, why are we trimming here instead of when the object is
+ * constructed? actually, why are we trimming at all? */
+ return (currentPackageName.trim().equalsIgnoreCase(newPackageName.trim())) &&
+ (currentThemeId.trim().equalsIgnoreCase(newThemeId.trim()));
+ }
+ return false;
+ }
+
+ @Override
+ public final String toString() {
+ StringBuilder result = new StringBuilder();
+ if (!TextUtils.isEmpty(mThemePackageName) && !TextUtils.isEmpty(mThemeId)) {
+ result.append(mThemePackageName);
+ result.append('(');
+ result.append(mThemeId);
+ result.append(')');
+ } else {
+ result.append("system");
+ }
+ return result.toString();
+ }
+
+ @Override
+ public synchronized int hashCode() {
+ return mThemeId.hashCode() + mThemePackageName.hashCode();
+ }
+
+ public String getThemeId() {
+ return mThemeId;
+ }
+
+ public String getThemePackageName() {
+ return mThemePackageName;
+ }
+
+ /**
+ * Represents the theme that the device booted into. This is used to
+ * simulate a "default" configuration based on the user's last known
+ * preference until the theme is switched at runtime.
+ */
+ public static CustomTheme getBootTheme() {
+ return sBootTheme;
+ }
+
+ /**
+ * Represents the system framework theme, perceived by the system as there
+ * being no theme applied.
+ */
+ public static CustomTheme getSystemTheme() {
+ return sSystemTheme;
+ }
+}
diff --git a/core/java/android/content/res/PackageRedirectionMap.aidl b/core/java/android/content/res/PackageRedirectionMap.aidl
new file mode 100644
index 0000000..4f47525
--- /dev/null
+++ b/core/java/android/content/res/PackageRedirectionMap.aidl
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2011, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res;
+
+/**
+ * @hide
+ */
+parcelable PackageRedirectionMap;
diff --git a/core/java/android/content/res/PackageRedirectionMap.java b/core/java/android/content/res/PackageRedirectionMap.java
new file mode 100644
index 0000000..55c4282
--- /dev/null
+++ b/core/java/android/content/res/PackageRedirectionMap.java
@@ -0,0 +1,90 @@
+package android.content.res;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Native transport for package asset redirection information coming from the
+ * AssetRedirectionManagerService.
+ *
+ * @hide
+ */
+public class PackageRedirectionMap implements Parcelable {
+ private final int mNativePointer;
+
+ public static final Parcelable.Creator<PackageRedirectionMap> CREATOR
+ = new Parcelable.Creator<PackageRedirectionMap>() {
+ public PackageRedirectionMap createFromParcel(Parcel in) {
+ return new PackageRedirectionMap(in);
+ }
+
+ public PackageRedirectionMap[] newArray(int size) {
+ return new PackageRedirectionMap[size];
+ }
+ };
+
+ public PackageRedirectionMap() {
+ this(nativeConstructor());
+ }
+
+ private PackageRedirectionMap(Parcel in) {
+ this(nativeCreateFromParcel(in));
+ }
+
+ private PackageRedirectionMap(int nativePointer) {
+ if (nativePointer == 0) {
+ throw new RuntimeException();
+ }
+ mNativePointer = nativePointer;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ nativeDestructor(mNativePointer);
+ }
+
+ public int getNativePointer() {
+ return mNativePointer;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (!nativeWriteToParcel(mNativePointer, dest)) {
+ throw new RuntimeException();
+ }
+ }
+
+ public int getPackageId() {
+ return nativeGetPackageId(mNativePointer);
+ }
+
+ public void addRedirection(int fromIdent, int toIdent) {
+ nativeAddRedirection(mNativePointer, fromIdent, toIdent);
+ }
+
+ // Used for debugging purposes only.
+ public int[] getRedirectionKeys() {
+ return nativeGetRedirectionKeys(mNativePointer);
+ }
+
+ // Used for debugging purposes only.
+ public int lookupRedirection(int fromIdent) {
+ return nativeLookupRedirection(mNativePointer, fromIdent);
+ }
+
+ private static native int nativeConstructor();
+ private static native void nativeDestructor(int nativePointer);
+
+ private static native int nativeCreateFromParcel(Parcel p);
+ private static native boolean nativeWriteToParcel(int nativePointer, Parcel p);
+
+ private native void nativeAddRedirection(int nativePointer, int fromIdent, int toIdent);
+ private native int nativeGetPackageId(int nativePointer);
+ private native int[] nativeGetRedirectionKeys(int nativePointer);
+ private native int nativeLookupRedirection(int nativePointer, int fromIdent);
+}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 990c42c..51c81f5 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1477,7 +1477,15 @@ public class Resources {
mTmpConfig.locale = Locale.getDefault();
}
configChanges = mConfiguration.updateFrom(mTmpConfig);
- configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+
+ /* This is ugly, but modifying the activityInfoConfigToNative
+ * adapter would be messier */
+ if ((configChanges & ActivityInfo.CONFIG_THEME_RESOURCE) != 0) {
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ configChanges |= ActivityInfo.CONFIG_THEME_RESOURCE;
+ } else {
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ }
}
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
@@ -1539,6 +1547,18 @@ public class Resources {
private void clearDrawableCache(
LongSparseArray<WeakReference<ConstantState>> cache,
int configChanges) {
+ /*
+ * Quick test to find out if the config change that occurred should
+ * trigger a full cache wipe.
+ */
+ if (Configuration.needNewResources(configChanges, 0)) {
+ if (DEBUG_CONFIG) {
+ Log.d(TAG, "Clear drawable cache from config changes: 0x"
+ + Integer.toHexString(configChanges));
+ }
+ cache.clear();
+ return;
+ }
int N = cache.size();
if (DEBUG_CONFIG) {
Log.d(TAG, "Cleaning up drawables config changes: 0x"
@@ -1890,7 +1910,13 @@ public class Resources {
flushLayoutCache();
}
}
-
+
+ public final void updateStringCache() {
+ synchronized (mTmpValue) {
+ mAssets.recreateStringBlocks();
+ }
+ }
+
/*package*/ Drawable loadDrawable(TypedValue value, int id)
throws NotFoundException {
diff --git a/core/java/android/os/SystemProperties.java b/core/java/android/os/SystemProperties.java
index 7cb32c7..ea5b016 100644
--- a/core/java/android/os/SystemProperties.java
+++ b/core/java/android/os/SystemProperties.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -128,4 +129,49 @@ public class SystemProperties
}
native_set(key, val);
}
+
+ /**
+ * Get the value for the given key.
+ * @return def string if the key isn't found
+ */
+ public static String getLongString(String key, String def) {
+ if (key.length() + 1 > PROP_NAME_MAX) {
+ throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+ }
+ int chunks = getInt(key + '0', 0);
+ if (chunks == 0) {
+ return def;
+ }
+ StringBuffer sb = new StringBuffer();
+ for (int i = 1; i <= chunks; i++) {
+ sb.append(native_get(key + Integer.toString(i)));
+ }
+ return sb.toString();
+ }
+
+ /**
+ * Set the value for the given key.
+ * @throws IllegalArgumentException if the key exceeds 32 characters
+ */
+ public static void setLongString(String key, String val) {
+ if (key.length() + 1 > PROP_NAME_MAX) {
+ throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
+ }
+ int chunks = 0;
+ if (val != null && val.length() > 0) {
+ chunks = 1 + val.length() / (PROP_VALUE_MAX + 1);
+ }
+ native_set(key + '0', Integer.toString(chunks));
+ if (chunks > 0) {
+ for (int i = 1, start = 0; i <= chunks; i++) {
+ int end = start + PROP_VALUE_MAX;
+ if (end > val.length()) {
+ end = val.length();
+ }
+ native_set(key + Integer.toString(i), val.substring(start, end));
+ start = end;
+ }
+ }
+ }
+
}
diff --git a/core/java/com/android/internal/app/IAssetRedirectionManager.aidl b/core/java/com/android/internal/app/IAssetRedirectionManager.aidl
new file mode 100644
index 0000000..8b47f0b
--- /dev/null
+++ b/core/java/com/android/internal/app/IAssetRedirectionManager.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2011, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.app;
+
+import android.content.res.PackageRedirectionMap;
+
+/**
+ * Interface used to interact with the AssetRedirectionManagerService.
+ */
+interface IAssetRedirectionManager {
+ /**
+ * Access the package redirection map for the supplied package name given a
+ * particular theme.
+ */
+ PackageRedirectionMap getPackageRedirectionMap(in String themePackageName,
+ String themeId, in String targetPackageName);
+
+ /**
+ * Clear all redirection maps for the given theme.
+ */
+ void clearRedirectionMapsByTheme(in String themePackageName,
+ in String themeId);
+
+ /**
+ * Clear all redirection maps for the given target package.
+ */
+ void clearPackageRedirectionMap(in String targetPackageName);
+}
diff --git a/core/java/com/android/internal/os/ZygoteInit.java b/core/java/com/android/internal/os/ZygoteInit.java
index 51eb7ea..7e80095 100644
--- a/core/java/com/android/internal/os/ZygoteInit.java
+++ b/core/java/com/android/internal/os/ZygoteInit.java
@@ -100,7 +100,7 @@ public class ZygoteInit {
private static final String PRELOADED_CLASSES = "preloaded-classes";
/** Controls whether we should preload resources during zygote init. */
- private static final boolean PRELOAD_RESOURCES = true;
+ private static final boolean PRELOAD_RESOURCES = false;
/**
* Invokes a static "main(argv[]) method on class "className".
@@ -360,6 +360,8 @@ public class ZygoteInit {
N = preloadColorStateLists(runtime, ar);
Log.i(TAG, "...preloaded " + N + " resources in "
+ (SystemClock.uptimeMillis()-startTime) + "ms.");
+ } else {
+ Log.i(TAG, "Preload resources disabled, skipped.");
}
mResources.finishPreloading();
} catch (RuntimeException e) {
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 8b2f0c4..3853973 100755
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -86,6 +86,7 @@ LOCAL_SRC_FILES:= \
android_util_Process.cpp \
android_util_StringBlock.cpp \
android_util_XmlBlock.cpp \
+ android_util_PackageRedirectionMap.cpp \
android/graphics/AutoDecodeCancel.cpp \
android/graphics/Bitmap.cpp \
android/graphics/BitmapFactory.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index b5a6b37..5fa3172 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -177,6 +177,7 @@ extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
+extern int register_android_content_res_PackageRedirectionMap(JNIEnv* env);
#ifdef QCOM_HARDWARE
extern int register_org_codeaurora_Performance(JNIEnv *env);
#endif
@@ -1207,6 +1208,7 @@ static const RegJNIRec gRegJNI[] = {
REG_JNI(register_android_animation_PropertyValuesHolder),
REG_JNI(register_com_android_internal_content_NativeLibraryHelper),
+ REG_JNI(register_android_content_res_PackageRedirectionMap),
#ifdef QCOM_HARDWARE
REG_JNI(register_org_codeaurora_Performance),
#endif
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 4f8f1af..1a8f3e7 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -1,6 +1,7 @@
/* //device/libs/android_runtime/android_util_AssetManager.cpp
**
** Copyright 2006, The Android Open Source Project
+** This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
@@ -34,9 +35,13 @@
#include <utils/Asset.h>
#include <utils/AssetManager.h>
#include <utils/ResourceTypes.h>
+#include <utils/PackageRedirectionMap.h>
+#include <utils/ZipFile.h>
#include <stdio.h>
+#define REDIRECT_NOISY(x) //x
+
namespace android {
// ----------------------------------------------------------------------------
@@ -691,17 +696,23 @@ static jint android_content_AssetManager_loadResourceValue(JNIEnv* env, jobject
}
const ResTable& res(am->getResources());
+ uint32_t ref = res.lookupRedirectionMap(ident);
+ if (ref == 0) {
+ ref = ident;
+ } else {
+ REDIRECT_NOISY(LOGW("PERFORMED REDIRECT OF ident=0x%08x FOR ref=0x%08x\n", ident, ref));
+ }
+
Res_value value;
ResTable_config config;
uint32_t typeSpecFlags;
- ssize_t block = res.getResource(ident, &value, false, density, &typeSpecFlags, &config);
+ ssize_t block = res.getResource(ref, &value, false, density, &typeSpecFlags, &config);
#if THROW_ON_BAD_ID
if (block == BAD_INDEX) {
jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
return 0;
}
#endif
- uint32_t ref = ident;
if (resolve) {
block = res.resolveReference(&value, block, &ref);
#if THROW_ON_BAD_ID
@@ -952,6 +963,20 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
// Now lock down the resource object and start pulling stuff from it.
res.lock();
+ // Apply theme redirections to the referenced styles.
+ if (defStyleRes != 0) {
+ uint32_t ref = res.lookupRedirectionMap(defStyleRes);
+ if (ref != 0) {
+ defStyleRes = ref;
+ }
+ }
+ if (style != 0) {
+ uint32_t ref = res.lookupRedirectionMap(style);
+ if (ref != 0) {
+ style = ref;
+ }
+ }
+
// Retrieve the default style bag, if requested.
const ResTable::bag_entry* defStyleEnt = NULL;
uint32_t defStyleTypeSetFlags = 0;
@@ -1076,6 +1101,31 @@ static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject cla
block = kXmlBlock;
}
+ // One final test for a resource redirection from the applied theme.
+ if (resid != 0) {
+ uint32_t redirect = res.lookupRedirectionMap(resid);
+ if (redirect != 0) {
+ REDIRECT_NOISY(LOGW("deep REDIRECT 0x%08x => 0x%08x\n", resid, redirect));
+ ssize_t newBlock = res.getResource(redirect, &value, true, config.density, &typeSetFlags, &config);
+ if (newBlock >= 0) {
+ newBlock = res.resolveReference(&value, newBlock, &redirect, &typeSetFlags, &config);
+#if THROW_ON_BAD_ID
+ if (newBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return JNI_FALSE;
+ }
+#endif
+ if (newBlock >= 0) {
+ block = newBlock;
+ resid = redirect;
+ }
+ }
+ if (resid != redirect) {
+ LOGW("deep redirect failure from 0x%08x => 0x%08x, defStyleAttr=0x%08x, defStyleRes=0x%08x, style=0x%08x\n", resid, redirect, defStyleAttr, defStyleRes, style);
+ }
+ }
+ }
+
DEBUG_STYLES(LOGI("Attribute 0x%08x: type=0x%x, data=0x%08x",
curIdent, value.dataType, value.data));
@@ -1333,6 +1383,31 @@ static jint android_content_AssetManager_retrieveArray(JNIEnv* env, jobject claz
value.dataType = Res_value::TYPE_NULL;
}
+ // One final test for a resource redirection from the applied theme.
+ if (resid != 0) {
+ uint32_t redirect = res.lookupRedirectionMap(resid);
+ if (redirect != 0) {
+ REDIRECT_NOISY(LOGW("array REDIRECT 0x%08x => 0x%08x\n", resid, redirect));
+ ssize_t newBlock = res.getResource(redirect, &value, true, config.density, &typeSetFlags, &config);
+ if (newBlock >= 0) {
+ newBlock = res.resolveReference(&value, newBlock, &redirect, &typeSetFlags, &config);
+#if THROW_ON_BAD_ID
+ if (newBlock == BAD_INDEX) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!");
+ return JNI_FALSE;
+ }
+#endif
+ if (newBlock >= 0) {
+ block = newBlock;
+ resid = redirect;
+ }
+ }
+ if (resid != redirect) {
+ LOGW("array redirect failure from 0x%08x => 0x%08x, array id=0x%08x", resid, redirect, id);
+ }
+ }
+ }
+
//printf("Attribute 0x%08x: final type=0x%x, data=0x%08x\n", curIdent, value.dataType, value.data);
// Write the final value back to Java.
@@ -1561,6 +1636,84 @@ static jintArray android_content_AssetManager_getArrayIntResource(JNIEnv* env, j
return array;
}
+static jint android_content_AssetManager_splitThemePackage(JNIEnv* env, jobject clazz,
+ jstring srcFileName, jstring dstFileName, jobjectArray drmProtectedAssetNames)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return -1;
+ }
+
+ LOGV("splitThemePackage in %p (Java object %p)\n", am, clazz);
+
+ if (srcFileName == NULL || dstFileName == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", srcFileName == NULL ? "srcFileName" : "dstFileName");
+ return -2;
+ }
+
+ jsize size = env->GetArrayLength(drmProtectedAssetNames);
+ if (size == 0) {
+ jniThrowException(env, "java/lang/IllegalArgumentException", "drmProtectedAssetNames");
+ return -3;
+ }
+
+ const char* srcFileName8 = env->GetStringUTFChars(srcFileName, NULL);
+ ZipFile* srcZip = new ZipFile;
+ status_t err = srcZip->open(srcFileName8, ZipFile::kOpenReadWrite);
+ if (err != NO_ERROR) {
+ LOGV("error opening zip file %s\n", srcFileName8);
+ delete srcZip;
+ env->ReleaseStringUTFChars(srcFileName, srcFileName8);
+ return -4;
+ }
+
+ const char* dstFileName8 = env->GetStringUTFChars(dstFileName, NULL);
+ ZipFile* dstZip = new ZipFile;
+ err = dstZip->open(dstFileName8, ZipFile::kOpenReadWrite | ZipFile::kOpenTruncate | ZipFile::kOpenCreate);
+
+ if (err != NO_ERROR) {
+ LOGV("error opening zip file %s\n", dstFileName8);
+ delete srcZip;
+ delete dstZip;
+ env->ReleaseStringUTFChars(srcFileName, srcFileName8);
+ env->ReleaseStringUTFChars(dstFileName, dstFileName8);
+ return -5;
+ }
+
+ int result = 0;
+ for (int i = 0; i < size; i++) {
+ jstring javaString = (jstring)env->GetObjectArrayElement(drmProtectedAssetNames, i);
+ const char* drmProtectedAssetFileName8 = env->GetStringUTFChars(javaString, NULL);
+ ZipEntry *assetEntry = srcZip->getEntryByName(drmProtectedAssetFileName8);
+ if (assetEntry == NULL) {
+ result = 1;
+ LOGV("Invalid asset entry %s\n", drmProtectedAssetFileName8);
+ } else {
+ status_t loc_result = dstZip->add(srcZip, assetEntry, 0, NULL);
+ if (loc_result != NO_ERROR) {
+ LOGV("error copying zip entry %s\n", drmProtectedAssetFileName8);
+ result = result | 2;
+ } else {
+ loc_result = srcZip->remove(assetEntry);
+ if (loc_result != NO_ERROR) {
+ LOGV("error removing zip entry %s\n", drmProtectedAssetFileName8);
+ result = result | 4;
+ }
+ }
+ }
+ env->ReleaseStringUTFChars(javaString, drmProtectedAssetFileName8);
+ }
+ srcZip->flush();
+ dstZip->flush();
+
+ delete srcZip;
+ delete dstZip;
+ env->ReleaseStringUTFChars(srcFileName, srcFileName8);
+ env->ReleaseStringUTFChars(dstFileName, dstFileName8);
+
+ return (jint)result;
+}
+
static void android_content_AssetManager_init(JNIEnv* env, jobject clazz)
{
AssetManager* am = new AssetManager();
@@ -1607,6 +1760,173 @@ static jint android_content_AssetManager_getGlobalAssetManagerCount(JNIEnv* env,
return AssetManager::getGlobalCount();
}
+static jint android_content_AssetManager_getBasePackageCount(JNIEnv* env, jobject clazz)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ return am->getResources().getBasePackageCount();
+}
+
+static jstring android_content_AssetManager_getBasePackageName(JNIEnv* env, jobject clazz, jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ String16 packageName(am->getResources().getBasePackageName(index));
+ return env->NewString((const jchar*)packageName.string(), packageName.size());
+}
+
+static jint android_content_AssetManager_getBasePackageId(JNIEnv* env, jobject clazz, jint index)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ return am->getResources().getBasePackageId(index);
+}
+
+static void android_content_AssetManager_addRedirectionsNative(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return;
+ }
+
+ am->addRedirections(resMap);
+}
+
+static void android_content_AssetManager_clearRedirectionsNative(JNIEnv* env, jobject clazz)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return;
+ }
+
+ am->clearRedirections();
+}
+
+static jboolean android_content_AssetManager_generateStyleRedirections(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap, jint sourceStyle, jint destStyle)
+{
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ const ResTable& res(am->getResources());
+
+ res.lock();
+
+ // Load up a bag for the user-supplied theme.
+ const ResTable::bag_entry* themeEnt = NULL;
+ ssize_t N = res.getBagLocked(destStyle, &themeEnt);
+ const ResTable::bag_entry* endThemeEnt = themeEnt + (N >= 0 ? N : 0);
+
+ // ...and a bag for the framework default.
+ const ResTable::bag_entry* frameworkEnt = NULL;
+ N = res.getBagLocked(sourceStyle, &frameworkEnt);
+ const ResTable::bag_entry* endFrameworkEnt = frameworkEnt + (N >= 0 ? N : 0);
+
+ // Add the source => dest style redirection first.
+ jboolean ret = JNI_FALSE;
+ if (themeEnt < endThemeEnt && frameworkEnt < endFrameworkEnt) {
+ resMap->addRedirection(sourceStyle, destStyle);
+ ret = JNI_TRUE;
+ }
+
+ // Now compare them and infer resource redirections for attributes that
+ // remap to different styles. This works by essentially lining up all the
+ // sorted attributes from each theme and detected TYPE_REFERENCE entries
+ // that point to different resources. When we find such a mismatch, we'll
+ // create a resource redirection from the original framework resource ID to
+ // the one in the theme. This lets us do things like automatically find
+ // redirections for @android:style/Widget.Button by looking at how the
+ // theme overrides the android:attr/buttonStyle attribute.
+ REDIRECT_NOISY(LOGW("delta between 0x%08x and 0x%08x:\n", sourceStyle, destStyle));
+ for (; frameworkEnt < endFrameworkEnt; frameworkEnt++) {
+ if (frameworkEnt->map.value.dataType != Res_value::TYPE_REFERENCE) {
+ continue;
+ }
+
+ uint32_t curIdent = frameworkEnt->map.name.ident;
+
+ // Walk along the theme entry looking for a match.
+ while (themeEnt < endThemeEnt && curIdent > themeEnt->map.name.ident) {
+ themeEnt++;
+ }
+ // Match found, compare the references.
+ if (themeEnt < endThemeEnt && curIdent == themeEnt->map.name.ident) {
+ if (themeEnt->map.value.data != frameworkEnt->map.value.data) {
+ uint32_t fromIdent = frameworkEnt->map.value.data;
+ uint32_t toIdent = themeEnt->map.value.data;
+ REDIRECT_NOISY(LOGW(" generated mapping from 0x%08x => 0x%08x (by attr 0x%08x)\n",
+ fromIdent, toIdent, curIdent));
+ resMap->addRedirection(fromIdent, toIdent);
+ }
+ themeEnt++;
+ }
+
+ // Exhausted the theme, bail early.
+ if (themeEnt >= endThemeEnt) {
+ break;
+ }
+ }
+
+ res.unlock();
+
+ return ret;
+}
+
+static jboolean android_content_AssetManager_detachThemePath(JNIEnv* env, jobject clazz,
+ jstring packageName, jint cookie)
+{
+ if (packageName == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", "packageName");
+ return JNI_FALSE;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ const char* name8 = env->GetStringUTFChars(packageName, NULL);
+ bool res = am->detachThemePath(String8(name8), (void *)cookie);
+ env->ReleaseStringUTFChars(packageName, name8);
+
+ return res;
+}
+
+static jint android_content_AssetManager_attachThemePath(
+ JNIEnv* env, jobject clazz, jstring path)
+{
+ if (path == NULL) {
+ jniThrowException(env, "java/lang/NullPointerException", "path");
+ return JNI_FALSE;
+ }
+
+ AssetManager* am = assetManagerForJavaObject(env, clazz);
+ if (am == NULL) {
+ return JNI_FALSE;
+ }
+
+ const char* path8 = env->GetStringUTFChars(path, NULL);
+
+ void* cookie;
+ bool res = am->attachThemePath(String8(path8), &cookie);
+
+ env->ReleaseStringUTFChars(path, path8);
+
+ return (res) ? (jint)cookie : 0;
+}
+
// ----------------------------------------------------------------------------
/*
@@ -1716,6 +2036,28 @@ static JNINativeMethod gAssetManagerMethods[] = {
(void*) android_content_AssetManager_getAssetAllocations },
{ "getGlobalAssetManagerCount", "()I",
(void*) android_content_AssetManager_getGlobalAssetCount },
+
+ // Split theme package apk into two.
+ { "splitThemePackage","(Ljava/lang/String;Ljava/lang/String;[Ljava/lang/String;)I",
+ (void*) android_content_AssetManager_splitThemePackage },
+
+ // Dynamic theme package support.
+ { "detachThemePath", "(Ljava/lang/String;I)Z",
+ (void*) android_content_AssetManager_detachThemePath },
+ { "attachThemePath", "(Ljava/lang/String;)I",
+ (void*) android_content_AssetManager_attachThemePath },
+ { "getBasePackageCount", "()I",
+ (void*) android_content_AssetManager_getBasePackageCount },
+ { "getBasePackageName", "(I)Ljava/lang/String;",
+ (void*) android_content_AssetManager_getBasePackageName },
+ { "getBasePackageId", "(I)I",
+ (void*) android_content_AssetManager_getBasePackageId },
+ { "addRedirectionsNative", "(I)V",
+ (void*) android_content_AssetManager_addRedirectionsNative },
+ { "clearRedirectionsNative", "()V",
+ (void*) android_content_AssetManager_clearRedirectionsNative },
+ { "generateStyleRedirections", "(III)Z",
+ (void*) android_content_AssetManager_generateStyleRedirections },
};
int register_android_content_AssetManager(JNIEnv* env)
diff --git a/core/jni/android_util_PackageRedirectionMap.cpp b/core/jni/android_util_PackageRedirectionMap.cpp
new file mode 100644
index 0000000..2391edb
--- /dev/null
+++ b/core/jni/android_util_PackageRedirectionMap.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2011, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <utils/PackageRedirectionMap.h>
+
+#include "jni.h"
+#include "JNIHelp.h"
+#include <utils/misc.h>
+#include <android_runtime/AndroidRuntime.h>
+
+#include "android_util_Binder.h"
+#include <binder/Parcel.h>
+
+#include <utils/ResourceTypes.h>
+
+#include <stdio.h>
+
+namespace android {
+
+// ----------------------------------------------------------------------------
+
+static PackageRedirectionMap* PackageRedirectionMap_constructor(JNIEnv* env, jobject clazz)
+{
+ return new PackageRedirectionMap;
+}
+
+static void PackageRedirectionMap_destructor(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap)
+{
+ delete resMap;
+}
+
+static PackageRedirectionMap* PackageRedirectionMap_createFromParcel(JNIEnv* env, jobject clazz,
+ jobject parcel)
+{
+ if (parcel == NULL) {
+ return NULL;
+ }
+
+ Parcel* p = parcelForJavaObject(env, parcel);
+ PackageRedirectionMap* resMap = new PackageRedirectionMap;
+
+ int32_t entryCount = p->readInt32();
+ while (entryCount-- > 0) {
+ uint32_t fromIdent = (uint32_t)p->readInt32();
+ uint32_t toIdent = (uint32_t)p->readInt32();
+ resMap->addRedirection(fromIdent, toIdent);
+ }
+
+ return resMap;
+}
+
+static jboolean PackageRedirectionMap_writeToParcel(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap, jobject parcel)
+{
+ if (parcel == NULL) {
+ return JNI_FALSE;
+ }
+
+ Parcel* p = parcelForJavaObject(env, parcel);
+
+ int package = resMap->getPackage();
+ size_t nTypes = resMap->getNumberOfTypes();
+ size_t entryCount = 0;
+ for (size_t type=0; type<nTypes; type++) {
+ entryCount += resMap->getNumberOfUsedEntries(type);
+ }
+ p->writeInt32(entryCount);
+ for (size_t type=0; type<nTypes; type++) {
+ size_t nEntries = resMap->getNumberOfEntries(type);
+ for (size_t entry=0; entry<nEntries; entry++) {
+ uint32_t toIdent = resMap->getEntry(type, entry);
+ if (toIdent != 0) {
+ uint32_t fromIdent = Res_MAKEID(package-1, type, entry);
+ p->writeInt32(fromIdent);
+ p->writeInt32(toIdent);
+ }
+ }
+ }
+
+ return JNI_TRUE;
+}
+
+static void PackageRedirectionMap_addRedirection(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap, jint fromIdent, jint toIdent)
+{
+ resMap->addRedirection(fromIdent, toIdent);
+}
+
+static jint PackageRedirectionMap_getPackageId(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap)
+{
+ return resMap->getPackage();
+}
+
+static jint PackageRedirectionMap_lookupRedirection(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap, jint fromIdent)
+{
+ return resMap->lookupRedirection(fromIdent);
+}
+
+static jintArray PackageRedirectionMap_getRedirectionKeys(JNIEnv* env, jobject clazz,
+ PackageRedirectionMap* resMap)
+{
+ int package = resMap->getPackage();
+ size_t nTypes = resMap->getNumberOfTypes();
+ size_t entryCount = 0;
+ for (size_t type=0; type<nTypes; type++) {
+ size_t usedEntries = resMap->getNumberOfUsedEntries(type);
+ entryCount += usedEntries;
+ }
+ jintArray array = env->NewIntArray(entryCount);
+ if (array == NULL) {
+ jniThrowException(env, "java/lang/OutOfMemoryError", "");
+ return NULL;
+ }
+ jsize index = 0;
+ for (size_t type=0; type<nTypes; type++) {
+ size_t nEntries = resMap->getNumberOfEntries(type);
+ for (size_t entry=0; entry<nEntries; entry++) {
+ uint32_t toIdent = resMap->getEntry(type, entry);
+ if (toIdent != 0) {
+ jint fromIdent = (jint)Res_MAKEID(package-1, type, entry);
+ env->SetIntArrayRegion(array, index++, 1, &fromIdent);
+ }
+ }
+ }
+ return array;
+}
+
+// ----------------------------------------------------------------------------
+
+/*
+ * JNI registration.
+ */
+static JNINativeMethod gPackageRedirectionMapMethods[] = {
+ { "nativeConstructor", "()I",
+ (void*) PackageRedirectionMap_constructor },
+ { "nativeDestructor", "(I)V",
+ (void*) PackageRedirectionMap_destructor },
+ { "nativeCreateFromParcel", "(Landroid/os/Parcel;)I",
+ (void*) PackageRedirectionMap_createFromParcel },
+ { "nativeWriteToParcel", "(ILandroid/os/Parcel;)Z",
+ (void*) PackageRedirectionMap_writeToParcel },
+ { "nativeAddRedirection", "(III)V",
+ (void*) PackageRedirectionMap_addRedirection },
+ { "nativeGetPackageId", "(I)I",
+ (void*) PackageRedirectionMap_getPackageId },
+ { "nativeLookupRedirection", "(II)I",
+ (void*) PackageRedirectionMap_lookupRedirection },
+ { "nativeGetRedirectionKeys", "(I)[I",
+ (void*) PackageRedirectionMap_getRedirectionKeys },
+};
+
+int register_android_content_res_PackageRedirectionMap(JNIEnv* env)
+{
+ return AndroidRuntime::registerNativeMethods(env,
+ "android/content/res/PackageRedirectionMap",
+ gPackageRedirectionMapMethods,
+ NELEM(gPackageRedirectionMapMethods));
+}
+
+}; // namespace android
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 2e5dd50..26cc3ca 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -18,6 +18,7 @@
*/
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:pluto="http://www.w3.org/2001/pluto.html"
package="android" coreApp="true" android:sharedUserId="android.uid.system"
android:sharedUserLabel="@string/android_system_label">
@@ -1638,6 +1639,18 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.AppsLaunchFailureReceiver" >
+ <intent-filter>
+ <action android:name="com.tmobile.intent.action.APP_LAUNCH_FAILURE" />
+ <action android:name="com.tmobile.intent.action.APP_LAUNCH_FAILURE_RESET" />
+ <action android:name="android.intent.action.PACKAGE_ADDED" />
+ <action android:name="android.intent.action.PACKAGE_REMOVED" />
+ <action android:name="com.tmobile.intent.action.THEME_PACKAGE_UPDATED" />
+ <category android:name="com.tmobile.intent.category.THEME_PACKAGE_INSTALL_STATE_CHANGE" />
+ <data android:scheme="package" />
+ </intent-filter>
+ </receiver>
+
<service android:name="com.android.internal.os.storage.ExternalStorageFormatter"
android:permission="android.permission.MASTER_CLEAR"
android:exported="true" />
diff --git a/data/etc/com.tmobile.software.themes.xml b/data/etc/com.tmobile.software.themes.xml
new file mode 100644
index 0000000..c145e3e
--- /dev/null
+++ b/data/etc/com.tmobile.software.themes.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010, T-Mobile USA, Inc.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- T-Mobile global theme engine is present. -->
+<permissions>
+ <feature name="com.tmobile.software.themes" />
+</permissions>
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index 6cd07a3..27c562c 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -94,6 +94,11 @@
<group gid="net_bw_acct" />
</permission>
+ <!-- Permissions to read DRM-protected theme resources. -->
+ <permission name="com.tmobile.permission.ACCESS_DRM_THEME" >
+ <group gid="theme_manager" />
+ </permission>
+
<!-- ================================================================== -->
<!-- ================================================================== -->
<!-- ================================================================== -->
diff --git a/include/utils/AssetManager.h b/include/utils/AssetManager.h
index a8c7ddb..062a6fb 100644
--- a/include/utils/AssetManager.h
+++ b/include/utils/AssetManager.h
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +23,7 @@
#include <utils/Asset.h>
#include <utils/AssetDir.h>
+#include <utils/PackageRedirectionMap.h>
#include <utils/KeyedVector.h>
#include <utils/String8.h>
#include <utils/Vector.h>
@@ -91,7 +93,7 @@ public:
* then on success, *cookie is set to the value corresponding to the
* newly-added asset source.
*/
- bool addAssetPath(const String8& path, void** cookie);
+ bool addAssetPath(const String8& path, void** cookie, bool asSkin=false);
/*
* Convenience for adding the standard system assets. Uses the
@@ -217,14 +219,28 @@ public:
*/
void getLocales(Vector<String8>* locales) const;
+ /*
+ * Remove existing source for assets.
+ *
+ * Also updates the ResTable object to reflect the change.
+ *
+ * Returns "true" on success, "false" on failure.
+ */
+ bool detachThemePath(const String8& packageName, void *cookie);
+ bool attachThemePath(const String8& path, void** cookie);
+ void addRedirections(PackageRedirectionMap* resMap);
+ void clearRedirections();
+
private:
struct asset_path
{
String8 path;
FileType type;
String8 idmap;
+ bool asSkin;
};
+ void updateResTableFromAssetPath(ResTable* rt, const asset_path& ap, void* cookie) const;
Asset* openInPathLocked(const char* fileName, AccessMode mode,
const asset_path& path);
Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
diff --git a/include/utils/PackageRedirectionMap.h b/include/utils/PackageRedirectionMap.h
new file mode 100644
index 0000000..9e6435b
--- /dev/null
+++ b/include/utils/PackageRedirectionMap.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2005 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef ANDROID_PACKAGEREDIRECTIONMAP_H
+#define ANDROID_PACKAGEREDIRECTIONMAP_H
+
+#include <binder/Parcel.h>
+
+// ---------------------------------------------------------------------------
+
+namespace android {
+
+class PackageRedirectionMap
+{
+public:
+ PackageRedirectionMap();
+ ~PackageRedirectionMap();
+
+ bool addRedirection(uint32_t fromIdent, uint32_t toIdent);
+ uint32_t lookupRedirection(uint32_t fromIdent);
+
+ // If there are no redirections present in this map, this method will
+ // return -1.
+ int getPackage();
+
+ // Usage of the following methods is intended to be used only by the JNI
+ // methods for the purpose of parceling.
+ size_t getNumberOfTypes();
+ size_t getNumberOfUsedTypes();
+
+ size_t getNumberOfEntries(int type);
+ size_t getNumberOfUsedEntries(int type);
+
+ // Similar to lookupRedirection, but with no sanity checking.
+ uint32_t getEntry(int type, int entry);
+
+private:
+ int mPackage;
+
+ /*
+ * Sparse array organized into two layers: first by type, then by entry.
+ * The result of each lookup will be a qualified resource ID in the theme
+ * package scope.
+ *
+ * Underneath each layer is a SharedBuffer which
+ * indicates the array size.
+ */
+ uint32_t** mEntriesByType;
+};
+
+} // namespace android
+
+// ---------------------------------------------------------------------------
+
+#endif // ANDROID_PACKAGEREDIRECTIONMAP_H
diff --git a/include/utils/ResourceTypes.h b/include/utils/ResourceTypes.h
index 612ff93..d0cd257 100644
--- a/include/utils/ResourceTypes.h
+++ b/include/utils/ResourceTypes.h
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2005 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,6 +24,7 @@
#include <utils/Asset.h>
#include <utils/ByteOrder.h>
#include <utils/Errors.h>
+#include <utils/PackageRedirectionMap.h>
#include <utils/String16.h>
#include <utils/Vector.h>
@@ -1818,6 +1820,9 @@ public:
bool copyData=false, const void* idmap = NULL);
status_t add(ResTable* src);
+ void addRedirections(PackageRedirectionMap* resMap);
+ void clearRedirections();
+
status_t getError() const;
void uninit();
@@ -1865,6 +1870,8 @@ public:
uint32_t* inoutTypeSpecFlags = NULL,
ResTable_config* outConfig = NULL) const;
+ uint32_t lookupRedirectionMap(uint32_t resID) const;
+
enum {
TMP_BUFFER_SIZE = 16
};
@@ -2079,6 +2086,7 @@ public:
// IDMAP_HEADER_SIZE_BYTES) bytes of an idmap file.
static bool getIdmapInfo(const void* idmap, size_t size,
uint32_t* pOriginalCrc, uint32_t* pOverlayCrc);
+ void removeAssetsByCookie(const String8 &packageName, void* cookie);
#ifndef HAVE_ANDROID_OS
void print(bool inclValues) const;
@@ -2121,6 +2129,11 @@ private:
// Mapping from resource package IDs to indices into the internal
// package array.
uint8_t mPackageMap[256];
+
+ // Resource redirection mapping provided by the applied theme (if there is
+ // one). Resources requested which are found in this map will be
+ // automatically redirected to the appropriate themed value.
+ Vector<PackageRedirectionMap*> mRedirectionMap;
};
} // namespace android
diff --git a/include/utils/ZipEntry.h b/include/utils/ZipEntry.h
new file mode 100644
index 0000000..7f721b4
--- /dev/null
+++ b/include/utils/ZipEntry.h
@@ -0,0 +1,345 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Zip archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <utils/Errors.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+
+namespace android {
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * You can use one of these to get or set information about an entry, but
+ * there are no functions here for accessing the data itself. (We could
+ * tuck a pointer to the ZipFile in here for convenience, but that raises
+ * the likelihood of using ZipEntry objects after discarding the ZipFile.)
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void)
+ : mDeleted(false), mMarked(false)
+ {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Returns "true" if the data is compressed.
+ */
+ bool isCompressed(void) const {
+ return mCDE.mCompressionMethod != kCompressStored;
+ }
+ int getCompressionMethod(void) const { return mCDE.mCompressionMethod; }
+
+ /*
+ * Return the uncompressed length.
+ */
+ off_t getUncompressedLen(void) const { return mCDE.mUncompressedSize; }
+
+ /*
+ * Return the compressed length. For uncompressed data, this returns
+ * the same thing as getUncompresesdLen().
+ */
+ off_t getCompressedLen(void) const { return mCDE.mCompressedSize; }
+
+ /*
+ * Return the absolute file offset of the start of the compressed or
+ * uncompressed data.
+ */
+ off_t getFileOffset(void) const {
+ return mCDE.mLocalHeaderRelOffset +
+ LocalFileHeader::kLFHLen +
+ mLFH.mFileNameLength +
+ mLFH.mExtraFieldLength;
+ }
+
+ /*
+ * Return the data CRC.
+ */
+ unsigned long getCRC32(void) const { return mCDE.mCRC32; }
+
+ /*
+ * Return file modification time in UNIX seconds-since-epoch.
+ */
+ time_t getModWhen(void) const;
+
+ /*
+ * Return the archived file name.
+ */
+ const char* getFileName(void) const { return (const char*) mCDE.mFileName; }
+
+ /*
+ * Application-defined "mark". Can be useful when synchronizing the
+ * contents of an archive with contents on disk.
+ */
+ bool getMarked(void) const { return mMarked; }
+ void setMarked(bool val) { mMarked = val; }
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+ static inline void putLongLE(unsigned char* buf, long val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ buf[2] = (unsigned char) (val >> 16);
+ buf[3] = (unsigned char) (val >> 24);
+ }
+
+ /* defined for Zip archives */
+ enum {
+ kCompressStored = 0, // no compression
+ // shrunk = 1,
+ // reduced 1 = 2,
+ // reduced 2 = 3,
+ // reduced 3 = 4,
+ // reduced 4 = 5,
+ // imploded = 6,
+ // tokenized = 7,
+ kCompressDeflated = 8, // standard deflate
+ // Deflate64 = 9,
+ // lib imploded = 10,
+ // reserved = 11,
+ // bzip2 = 12,
+ };
+
+ /*
+ * Deletion flag. If set, the entry will be removed on the next
+ * call to "flush".
+ */
+ bool getDeleted(void) const { return mDeleted; }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry.
+ */
+ status_t initFromCDE(FILE* fp);
+
+ /*
+ * Initialize the structure for a new file. We need the filename
+ * and comment so that we can properly size the LFH area. The
+ * filename is mandatory, the comment is optional.
+ */
+ void initNew(const char* fileName, const char* comment);
+
+ /*
+ * Initialize the structure with the contents of a ZipEntry from
+ * another file.
+ */
+ status_t initFromExternal(const ZipFile* pZipFile, const ZipEntry* pEntry);
+
+ /*
+ * Add some pad bytes to the LFH. We do this by adding or resizing
+ * the "extra" field.
+ */
+ status_t addPadding(int padding);
+
+ /*
+ * Set information about the data for this entry.
+ */
+ void setDataInfo(long uncompLen, long compLen, unsigned long crc32,
+ int compressionMethod);
+
+ /*
+ * Set the modification date.
+ */
+ void setModWhen(time_t when);
+
+ /*
+ * Return the offset of the local file header.
+ */
+ off_t getLFHOffset(void) const { return mCDE.mLocalHeaderRelOffset; }
+
+ /*
+ * Set the offset of the local file header, relative to the start of
+ * the current file.
+ */
+ void setLFHOffset(off_t offset) {
+ mCDE.mLocalHeaderRelOffset = (long) offset;
+ }
+
+ /* mark for deletion; used by ZipFile::remove() */
+ void setDeleted(void) { mDeleted = true; }
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /* returns "true" if the CDE and the LFH agree */
+ bool compareHeaders(void) const;
+ void copyCDEtoLFH(void);
+
+ bool mDeleted; // set if entry is pending deletion
+ bool mMarked; // app-defined marker
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) :
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileName(NULL),
+ mExtraField(NULL)
+ {}
+ virtual ~LocalFileHeader(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+
+ void dump(void) const;
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mVersionMadeBy(0),
+ mVersionToExtract(0),
+ mGPBitFlag(0),
+ mCompressionMethod(0),
+ mLastModFileTime(0),
+ mLastModFileDate(0),
+ mCRC32(0),
+ mCompressedSize(0),
+ mUncompressedSize(0),
+ mFileNameLength(0),
+ mExtraFieldLength(0),
+ mFileCommentLength(0),
+ mDiskNumberStart(0),
+ mInternalAttrs(0),
+ mExternalAttrs(0),
+ mLocalHeaderRelOffset(0),
+ mFileName(NULL),
+ mExtraField(NULL),
+ mFileComment(NULL)
+ {}
+ virtual ~CentralDirEntry(void) {
+ delete[] mFileName;
+ delete[] mExtraField;
+ delete[] mFileComment;
+ }
+
+ status_t read(FILE* fp);
+ status_t write(FILE* fp);
+
+ // unsigned long mSignature;
+ unsigned short mVersionMadeBy;
+ unsigned short mVersionToExtract;
+ unsigned short mGPBitFlag;
+ unsigned short mCompressionMethod;
+ unsigned short mLastModFileTime;
+ unsigned short mLastModFileDate;
+ unsigned long mCRC32;
+ unsigned long mCompressedSize;
+ unsigned long mUncompressedSize;
+ unsigned short mFileNameLength;
+ unsigned short mExtraFieldLength;
+ unsigned short mFileCommentLength;
+ unsigned short mDiskNumberStart;
+ unsigned short mInternalAttrs;
+ unsigned long mExternalAttrs;
+ unsigned long mLocalHeaderRelOffset;
+ unsigned char* mFileName;
+ unsigned char* mExtraField;
+ unsigned char* mFileComment;
+
+ void dump(void) const;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ enum {
+ //kDataDescriptorSignature = 0x08074b50, // currently unused
+ kDataDescriptorLen = 16, // four 32-bit fields
+
+ kDefaultVersion = 20, // need deflate, nothing much else
+ kDefaultMadeBy = 0x0317, // 03=UNIX, 17=spec v2.3
+ kUsesDataDescr = 0x0008, // GPBitFlag bit 3
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/include/utils/ZipFile.h b/include/utils/ZipFile.h
new file mode 100644
index 0000000..dbbd072
--- /dev/null
+++ b/include/utils/ZipFile.h
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// General-purpose Zip archive access. This class allows both reading and
+// writing to Zip archives, including deletion of existing entries.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include <utils/Vector.h>
+#include <utils/Errors.h>
+#include <stdio.h>
+
+#include "ZipEntry.h"
+
+namespace android {
+
+/*
+ * Manipulate a Zip archive.
+ *
+ * Some changes will not be visible in the until until "flush" is called.
+ *
+ * The correct way to update a file archive is to make all changes to a
+ * copy of the archive in a temporary file, and then unlink/rename over
+ * the original after everything completes. Because we're only interested
+ * in using this for packaging, we don't worry about such things. Crashing
+ * after making changes and before flush() completes could leave us with
+ * an unusable Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void)
+ : mZipFp(NULL), mReadOnly(false), mNeedCDRewrite(false)
+ {}
+ ~ZipFile(void) {
+ if (!mReadOnly)
+ flush();
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ discardEntries();
+ }
+
+ /*
+ * Open a new or existing archive.
+ */
+ typedef enum {
+ kOpenReadOnly = 0x01,
+ kOpenReadWrite = 0x02,
+ kOpenCreate = 0x04, // create if it doesn't exist
+ kOpenTruncate = 0x08, // if it exists, empty it
+ };
+ status_t open(const char* zipFileName, int flags);
+
+ /*
+ * Add a file to the end of the archive. Specify whether you want the
+ * library to try to store it compressed.
+ *
+ * If "storageName" is specified, the archive will use that instead
+ * of "fileName".
+ *
+ * If there is already an entry with the same name, the call fails.
+ * Existing entries with the same name must be removed first.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const char* fileName, int compressionMethod,
+ ZipEntry** ppEntry)
+ {
+ return add(fileName, fileName, compressionMethod, ppEntry);
+ }
+ status_t add(const char* fileName, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add a file that is already compressed with gzip.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t addGzip(const char* fileName, const char* storageName,
+ ZipEntry** ppEntry)
+ {
+ return addCommon(fileName, NULL, 0, storageName,
+ ZipEntry::kCompressDeflated,
+ ZipEntry::kCompressDeflated, ppEntry);
+ }
+
+ /*
+ * Add a file from an in-memory data buffer.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const void* data, size_t size, const char* storageName,
+ int compressionMethod, ZipEntry** ppEntry)
+ {
+ return addCommon(NULL, data, size, storageName,
+ ZipEntry::kCompressStored,
+ compressionMethod, ppEntry);
+ }
+
+ /*
+ * Add an entry by copying it from another zip file. If "padding" is
+ * nonzero, the specified number of bytes will be added to the "extra"
+ * field in the header.
+ *
+ * If "ppEntry" is non-NULL, a pointer to the new entry will be returned.
+ */
+ status_t add(const ZipFile* pSourceZip, const ZipEntry* pSourceEntry,
+ int padding, ZipEntry** ppEntry);
+
+ /*
+ * Mark an entry as having been removed. It is not actually deleted
+ * from the archive or our internal data structures until flush() is
+ * called.
+ */
+ status_t remove(ZipEntry* pEntry);
+
+ /*
+ * Flush changes. If mNeedCDRewrite is set, this writes the central dir.
+ */
+ status_t flush(void);
+
+ /*
+ * Expand the data into the buffer provided. The buffer must hold
+ * at least <uncompressed len> bytes. Variation expands directly
+ * to a file.
+ *
+ * Returns "false" if an error was encountered in the compressed data.
+ */
+ //bool uncompress(const ZipEntry* pEntry, void* buf) const;
+ //bool uncompress(const ZipEntry* pEntry, FILE* fp) const;
+ void* uncompress(const ZipEntry* pEntry);
+
+ /*
+ * Get an entry, by name. Returns NULL if not found.
+ *
+ * Does not return entries pending deletion.
+ */
+ ZipEntry* getEntryByName(const char* fileName) const;
+
+ /*
+ * Get the Nth entry in the archive.
+ *
+ * This will return an entry that is pending deletion.
+ */
+ int getNumEntries(void) const { return mEntries.size(); }
+ ZipEntry* getEntryByIndex(int idx) const;
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) :
+ mDiskNumber(0),
+ mDiskWithCentralDir(0),
+ mNumEntries(0),
+ mTotalNumEntries(0),
+ mCentralDirSize(0),
+ mCentralDirOffset(0),
+ mCommentLen(0),
+ mComment(NULL)
+ {}
+ virtual ~EndOfCentralDir(void) {
+ delete[] mComment;
+ }
+
+ status_t readBuf(const unsigned char* buf, int len);
+ status_t write(FILE* fp);
+
+ //unsigned long mSignature;
+ unsigned short mDiskNumber;
+ unsigned short mDiskWithCentralDir;
+ unsigned short mNumEntries;
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirSize;
+ unsigned long mCentralDirOffset; // offset from first disk
+ unsigned short mCommentLen;
+ unsigned char* mComment;
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+
+ void dump(void) const;
+ };
+
+
+ /* read all entries in the central dir */
+ status_t readCentralDir(void);
+
+ /* crunch deleted entries out */
+ status_t crunchArchive(void);
+
+ /* clean up mEntries */
+ void discardEntries(void);
+
+ /* common handler for all "add" functions */
+ status_t addCommon(const char* fileName, const void* data, size_t size,
+ const char* storageName, int sourceType, int compressionMethod,
+ ZipEntry** ppEntry);
+
+ /* copy all of "srcFp" into "dstFp" */
+ status_t copyFpToFp(FILE* dstFp, FILE* srcFp, unsigned long* pCRC32);
+ /* copy all of "data" into "dstFp" */
+ status_t copyDataToFp(FILE* dstFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+ /* copy some of "srcFp" into "dstFp" */
+ status_t copyPartialFpToFp(FILE* dstFp, FILE* srcFp, long length,
+ unsigned long* pCRC32);
+ /* like memmove(), but on parts of a single file */
+ status_t filemove(FILE* fp, off_t dest, off_t src, size_t n);
+ /* compress all of "srcFp" into "dstFp", using Deflate */
+ status_t compressFpToFp(FILE* dstFp, FILE* srcFp,
+ const void* data, size_t size, unsigned long* pCRC32);
+
+ /* get modification date from a file descriptor */
+ time_t getModTime(int fd);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+
+ /* did we open this read-only? */
+ bool mReadOnly;
+
+ /* set this when we trash the central dir */
+ bool mNeedCDRewrite;
+
+ /*
+ * One ZipEntry per entry in the zip file. I'm using pointers instead
+ * of objects because it's easier than making operator= work for the
+ * classes and sub-classes.
+ */
+ Vector<ZipEntry*> mEntries;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/libs/utils/Android.mk b/libs/utils/Android.mk
index 831d9e3..58276ba 100644
--- a/libs/utils/Android.mk
+++ b/libs/utils/Android.mk
@@ -30,6 +30,7 @@ commonSources:= \
LinearTransform.cpp \
ObbFile.cpp \
PropertyMap.cpp \
+ PackageRedirectionMap.cpp \
RefBase.cpp \
ResourceTypes.cpp \
SharedBuffer.cpp \
@@ -49,6 +50,8 @@ commonSources:= \
ZipFileCRO.cpp \
ZipFileRO.cpp \
ZipUtils.cpp \
+ ../../tools/aapt/ZipFile.cpp \
+ ../../tools/aapt/ZipEntry.cpp \
misc.cpp
diff --git a/libs/utils/AssetManager.cpp b/libs/utils/AssetManager.cpp
index 22034c5..4733e78 100644
--- a/libs/utils/AssetManager.cpp
+++ b/libs/utils/AssetManager.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -134,7 +135,7 @@ AssetManager::~AssetManager(void)
delete[] mVendor;
}
-bool AssetManager::addAssetPath(const String8& path, void** cookie)
+bool AssetManager::addAssetPath(const String8& path, void** cookie, bool asSkin)
{
AutoMutex _l(mLock);
@@ -144,6 +145,7 @@ bool AssetManager::addAssetPath(const String8& path, void** cookie)
if (kAppZipName) {
realPath.appendPath(kAppZipName);
}
+ ap.asSkin = asSkin;
ap.type = ::getFileType(realPath.string());
if (ap.type == kFileTypeRegular) {
ap.path = realPath;
@@ -498,9 +500,13 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode)
size_t i = mAssetPaths.size();
while (i > 0) {
i--;
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ if (ap.asSkin) {
+ continue;
+ }
LOGV("Looking for asset '%s' in '%s'\n",
- assetName.string(), mAssetPaths.itemAt(i).path.string());
- Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+ assetName.string(), ap.path.string());
+ Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, ap);
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
@@ -532,9 +538,13 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode)
size_t i = mAssetPaths.size();
while (i > 0) {
i--;
- LOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ if (ap.asSkin) {
+ continue;
+ }
+ LOGV("Looking for non-asset '%s' in '%s'\n", fileName, ap.path.string());
Asset* pAsset = openNonAssetInPathLocked(
- fileName, mode, mAssetPaths.itemAt(i));
+ fileName, mode, ap);
if (pAsset != NULL) {
return pAsset != kExcludedAsset ? pAsset : NULL;
}
@@ -614,83 +624,87 @@ const ResTable* AssetManager::getResTable(bool required) const
if (mCacheMode != CACHE_OFF && !mCacheValid)
const_cast<AssetManager*>(this)->loadFileNameCacheLocked();
- const size_t N = mAssetPaths.size();
- for (size_t i=0; i<N; i++) {
- Asset* ass = NULL;
- ResTable* sharedRes = NULL;
- bool shared = true;
- const asset_path& ap = mAssetPaths.itemAt(i);
- Asset* idmap = openIdmapLocked(ap);
- LOGV("Looking for resource asset in '%s'\n", ap.path.string());
- if (ap.type != kFileTypeDirectory) {
- if (i == 0) {
- // The first item is typically the framework resources,
- // which we want to avoid parsing every time.
- sharedRes = const_cast<AssetManager*>(this)->
- mZipSet.getZipResourceTable(ap.path);
- }
- if (sharedRes == NULL) {
+ mResources = rt = new ResTable();
+
+ if (rt) {
+ const size_t N = mAssetPaths.size();
+ for (size_t i=0; i<N; i++) {
+ const asset_path& ap = mAssetPaths.itemAt(i);
+ updateResTableFromAssetPath(rt, ap, (void*)(i+1));
+ }
+ }
+
+ if (required && !rt) LOGW("Unable to find resources file resources.arsc");
+ if (!rt) {
+ mResources = rt = new ResTable();
+ }
+
+ return rt;
+}
+
+void AssetManager::updateResTableFromAssetPath(ResTable *rt, const asset_path& ap, void *cookie) const
+{
+ Asset* ass = NULL;
+ ResTable* sharedRes = NULL;
+ bool shared = true;
+ size_t cookiePos = (size_t)cookie;
+ LOGV("Looking for resource asset in '%s'\n", ap.path.string());
+ if (ap.type != kFileTypeDirectory) {
+ if (cookiePos == 1) {
+ // The first item is typically the framework resources,
+ // which we want to avoid parsing every time.
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTable(ap.path);
+ }
+ if (sharedRes == NULL) {
+ ass = const_cast<AssetManager*>(this)->
+ mZipSet.getZipResourceTableAsset(ap.path);
+ if (ass == NULL) {
+ LOGV("loading resource table %s\n", ap.path.string());
ass = const_cast<AssetManager*>(this)->
- mZipSet.getZipResourceTableAsset(ap.path);
- if (ass == NULL) {
- LOGV("loading resource table %s\n", ap.path.string());
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ if (ass != NULL && ass != kExcludedAsset) {
ass = const_cast<AssetManager*>(this)->
- openNonAssetInPathLocked("resources.arsc",
- Asset::ACCESS_BUFFER,
- ap);
- if (ass != NULL && ass != kExcludedAsset) {
- ass = const_cast<AssetManager*>(this)->
- mZipSet.setZipResourceTableAsset(ap.path, ass);
- }
- }
-
- if (i == 0 && ass != NULL) {
- // If this is the first resource table in the asset
- // manager, then we are going to cache it so that we
- // can quickly copy it out for others.
- LOGV("Creating shared resources for %s", ap.path.string());
- sharedRes = new ResTable();
- sharedRes->add(ass, (void*)(i+1), false, idmap);
- sharedRes = const_cast<AssetManager*>(this)->
- mZipSet.setZipResourceTable(ap.path, sharedRes);
+ mZipSet.setZipResourceTableAsset(ap.path, ass);
}
}
- } else {
- LOGV("loading resource table %s\n", ap.path.string());
- Asset* ass = const_cast<AssetManager*>(this)->
- openNonAssetInPathLocked("resources.arsc",
- Asset::ACCESS_BUFFER,
- ap);
- shared = false;
- }
- if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
- if (rt == NULL) {
- mResources = rt = new ResTable();
- updateResourceParamsLocked();
- }
- LOGV("Installing resource asset %p in to table %p\n", ass, mResources);
- if (sharedRes != NULL) {
- LOGV("Copying existing resources for %s", ap.path.string());
- rt->add(sharedRes);
- } else {
- LOGV("Parsing resources for %s", ap.path.string());
- rt->add(ass, (void*)(i+1), !shared, idmap);
- }
- if (!shared) {
- delete ass;
+ if (cookiePos == 0 && ass != NULL) {
+ // If this is the first resource table in the asset
+ // manager, then we are going to cache it so that we
+ // can quickly copy it out for others.
+ LOGV("Creating shared resources for %s", ap.path.string());
+ sharedRes = new ResTable();
+ sharedRes->add(ass, cookie, false);
+ sharedRes = const_cast<AssetManager*>(this)->
+ mZipSet.setZipResourceTable(ap.path, sharedRes);
}
}
- if (idmap != NULL) {
- delete idmap;
+ } else {
+ LOGV("loading resource table %s\n", ap.path.string());
+ Asset* ass = const_cast<AssetManager*>(this)->
+ openNonAssetInPathLocked("resources.arsc",
+ Asset::ACCESS_BUFFER,
+ ap);
+ shared = false;
+ }
+ if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) {
+ updateResourceParamsLocked();
+ LOGV("Installing resource asset %p in to table %p\n", ass, mResources);
+ if (sharedRes != NULL) {
+ LOGV("Copying existing resources for %s", ap.path.string());
+ rt->add(sharedRes);
+ } else {
+ LOGV("Parsing resources for %s", ap.path.string());
+ rt->add(ass, cookie, !shared);
}
- }
- if (required && !rt) LOGW("Unable to find resources file resources.arsc");
- if (!rt) {
- mResources = rt = new ResTable();
+ if (!shared) {
+ delete ass;
+ }
}
- return rt;
}
void AssetManager::updateResourceParamsLocked() const
@@ -1145,6 +1159,9 @@ AssetDir* AssetManager::openDir(const char* dirName)
while (i > 0) {
i--;
const asset_path& ap = mAssetPaths.itemAt(i);
+ if (ap.asSkin) {
+ continue;
+ }
if (ap.type == kFileTypeRegular) {
LOGV("Adding directory %s from zip %s", dirName, ap.path.string());
scanAndMergeZipLocked(pMergedInfo, ap, kAssetsRoot, dirName);
@@ -1999,3 +2016,52 @@ int AssetManager::ZipSet::getIndex(const String8& zip) const
return mZipPath.size()-1;
}
+
+bool AssetManager::attachThemePath(const String8& path, void** cookie)
+{
+ bool res = addAssetPath(path, cookie, true);
+ ResTable* rt = mResources;
+ if (res && rt != NULL && ((size_t)*cookie == mAssetPaths.size())) {
+ AutoMutex _l(mLock);
+ const asset_path& ap = mAssetPaths.itemAt((size_t)*cookie - 1);
+ updateResTableFromAssetPath(rt, ap, *cookie);
+ }
+ return res;
+}
+
+bool AssetManager::detachThemePath(const String8 &packageName, void* cookie)
+{
+ AutoMutex _l(mLock);
+
+ const size_t which = ((size_t)cookie)-1;
+ if (which >= mAssetPaths.size()) {
+ return false;
+ }
+
+ /* TODO: Ensure that this cookie is added with asSkin == true. */
+ mAssetPaths.removeAt(which);
+
+ ResTable* rt = mResources;
+ if (rt == NULL) {
+ LOGV("ResTable must not be NULL");
+ return false;
+ }
+
+ rt->removeAssetsByCookie(packageName, (void *)cookie);
+
+ return true;
+}
+
+void AssetManager::addRedirections(PackageRedirectionMap* resMap)
+{
+ getResources();
+ ResTable* rt = mResources;
+ rt->addRedirections(resMap);
+}
+
+void AssetManager::clearRedirections()
+{
+ getResources();
+ ResTable* rt = mResources;
+ rt->clearRedirections();
+}
diff --git a/libs/utils/PackageRedirectionMap.cpp b/libs/utils/PackageRedirectionMap.cpp
new file mode 100644
index 0000000..bf1062a
--- /dev/null
+++ b/libs/utils/PackageRedirectionMap.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//
+// Provide access to read-only assets.
+//
+
+#define LOG_TAG "packageresmap"
+
+#include <utils/PackageRedirectionMap.h>
+#include <utils/ResourceTypes.h>
+#include <utils/misc.h>
+
+using namespace android;
+
+PackageRedirectionMap::PackageRedirectionMap()
+ : mPackage(-1), mEntriesByType(NULL)
+{
+}
+
+static void clearEntriesByType(uint32_t** entriesByType)
+{
+ SharedBuffer* buf = SharedBuffer::bufferFromData(entriesByType);
+ const size_t N = buf->size() / sizeof(entriesByType[0]);
+ for (size_t i = 0; i < N; i++) {
+ uint32_t* entries = entriesByType[i];
+ if (entries != NULL) {
+ SharedBuffer::bufferFromData(entries)->release();
+ }
+ }
+ buf->release();
+}
+
+PackageRedirectionMap::~PackageRedirectionMap()
+{
+ if (mEntriesByType != NULL) {
+ clearEntriesByType(mEntriesByType);
+ }
+}
+
+static void* ensureCapacity(void* data, size_t nmemb, size_t size)
+{
+ SharedBuffer* buf;
+ size_t currentSize;
+
+ if (data != NULL) {
+ buf = SharedBuffer::bufferFromData(data);
+ currentSize = buf->size();
+ } else {
+ buf = NULL;
+ currentSize = 0;
+ }
+
+ size_t minSize = nmemb * size;
+ if (minSize > currentSize) {
+ unsigned int requestSize = roundUpPower2(minSize);
+ if (buf == NULL) {
+ buf = SharedBuffer::alloc(requestSize);
+ } else {
+ buf = buf->editResize(requestSize);
+ }
+ memset((unsigned char*)buf->data()+currentSize, 0, requestSize - currentSize);
+ }
+
+ return buf->data();
+}
+
+bool PackageRedirectionMap::addRedirection(uint32_t fromIdent, uint32_t toIdent)
+{
+ const int package = Res_GETPACKAGE(fromIdent);
+ const int type = Res_GETTYPE(fromIdent);
+ const int entry = Res_GETENTRY(fromIdent);
+
+ // The first time we add a redirection we can infer the package for all
+ // future redirections.
+ if (mPackage == -1) {
+ mPackage = package+1;
+ } else if (mPackage != (package+1)) {
+ LOGW("cannot add redirection for conflicting package 0x%02x (expecting package 0x%02x)\n", package+1, mPackage);
+ return false;
+ }
+
+ mEntriesByType = (uint32_t**)ensureCapacity(mEntriesByType, type + 1, sizeof(uint32_t*));
+ uint32_t* entries = mEntriesByType[type];
+ entries = (uint32_t*)ensureCapacity(entries, entry + 1, sizeof(uint32_t));
+ entries[entry] = toIdent;
+ mEntriesByType[type] = entries;
+
+ return true;
+}
+
+uint32_t PackageRedirectionMap::lookupRedirection(uint32_t fromIdent)
+{
+ if (mPackage == -1 || mEntriesByType == NULL || fromIdent == 0) {
+ return 0;
+ }
+
+ const int package = Res_GETPACKAGE(fromIdent);
+ const int type = Res_GETTYPE(fromIdent);
+ const int entry = Res_GETENTRY(fromIdent);
+
+ if (package+1 != mPackage) {
+ return 0;
+ }
+
+ size_t nTypes = getNumberOfTypes();
+ if (type < 0 || type >= nTypes) {
+ return 0;
+ }
+ uint32_t* entries = mEntriesByType[type];
+ if (entries == NULL) {
+ return 0;
+ }
+ size_t nEntries = getNumberOfEntries(type);
+ if (entry < 0 || entry >= nEntries) {
+ return 0;
+ }
+ return entries[entry];
+}
+
+int PackageRedirectionMap::getPackage()
+{
+ return mPackage;
+}
+
+size_t PackageRedirectionMap::getNumberOfTypes()
+{
+ if (mEntriesByType == NULL) {
+ return 0;
+ } else {
+ return SharedBuffer::bufferFromData(mEntriesByType)->size() /
+ sizeof(mEntriesByType[0]);
+ }
+}
+
+size_t PackageRedirectionMap::getNumberOfUsedTypes()
+{
+ uint32_t** entriesByType = mEntriesByType;
+ size_t N = getNumberOfTypes();
+ size_t count = 0;
+ for (size_t i=0; i<N; i++) {
+ if (entriesByType[i] != NULL) {
+ count++;
+ }
+ }
+ return count;
+}
+
+size_t PackageRedirectionMap::getNumberOfEntries(int type)
+{
+ uint32_t* entries = mEntriesByType[type];
+ if (entries == NULL) {
+ return 0;
+ } else {
+ return SharedBuffer::bufferFromData(entries)->size() /
+ sizeof(entries[0]);
+ }
+}
+
+size_t PackageRedirectionMap::getNumberOfUsedEntries(int type)
+{
+ size_t N = getNumberOfEntries(type);
+ uint32_t* entries = mEntriesByType[type];
+ size_t count = 0;
+ for (size_t i=0; i<N; i++) {
+ if (entries[i] != 0) {
+ count++;
+ }
+ }
+ return count;
+}
+
+uint32_t PackageRedirectionMap::getEntry(int type, int entry)
+{
+ uint32_t* entries = mEntriesByType[type];
+ return entries[entry];
+}
diff --git a/libs/utils/ResourceTypes.cpp b/libs/utils/ResourceTypes.cpp
index 6cf01c8..6040df1 100644
--- a/libs/utils/ResourceTypes.cpp
+++ b/libs/utils/ResourceTypes.cpp
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2008 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +26,7 @@
#include <utils/String8.h>
#include <utils/TextOutput.h>
#include <utils/Log.h>
+#include <utils/misc.h>
#include <stdlib.h>
#include <string.h>
@@ -43,6 +45,7 @@
#define TABLE_SUPER_NOISY(x) //x
#define LOAD_TABLE_NOISY(x) //x
#define TABLE_THEME(x) //x
+#define REDIRECT_NOISY(x) //x
namespace android {
@@ -1550,6 +1553,13 @@ status_t ResTable::Theme::applyStyle(uint32_t resID, bool force)
const bag_entry* bag;
uint32_t bagTypeSpecFlags = 0;
mTable.lock();
+ uint32_t redirect = mTable.lookupRedirectionMap(resID);
+ if (redirect != 0 || resID == 0x01030005) {
+ REDIRECT_NOISY(LOGW("applyStyle: PERFORMED REDIRECT OF ident=0x%08x FOR redirect=0x%08x\n", resID, redirect));
+ }
+ if (redirect != 0) {
+ resID = redirect;
+ }
const ssize_t N = mTable.getBagLocked(resID, &bag, &bagTypeSpecFlags);
TABLE_NOISY(LOGV("Applying style 0x%08x to theme %p, count=%d", resID, this, N));
if (N < 0) {
@@ -1997,6 +2007,8 @@ void ResTable::uninit()
mPackageGroups.clear();
mHeaders.clear();
+
+ clearRedirections();
}
bool ResTable::getResourceName(uint32_t resID, resource_name* outName) const
@@ -2254,6 +2266,24 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex,
return blockIndex;
}
+uint32_t ResTable::lookupRedirectionMap(uint32_t resID) const
+{
+ if (mError != NO_ERROR) {
+ return 0;
+ }
+
+ const int p = Res_GETPACKAGE(resID)+1;
+
+ const size_t N = mRedirectionMap.size();
+ for (size_t i=0; i<N; i++) {
+ PackageRedirectionMap* resMap = mRedirectionMap[i];
+ if (resMap->getPackage() == p) {
+ return resMap->lookupRedirection(resID);
+ }
+ }
+ return 0;
+}
+
const char16_t* ResTable::valueToString(
const Res_value* value, size_t stringBlock,
char16_t tmpBuffer[TMP_BUFFER_SIZE], size_t* outLen)
@@ -2461,7 +2491,19 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag,
if (parent) {
const bag_entry* parentBag;
uint32_t parentTypeSpecFlags = 0;
- const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags);
+ uint32_t parentRedirect = lookupRedirectionMap(parent);
+ uint32_t parentActual = parent;
+ if (parentRedirect != 0 || parent == 0x01030005) {
+ if (parentRedirect == resID) {
+ REDIRECT_NOISY(LOGW("applyStyle(parent): ignoring circular redirect from parent=0x%08x to parentRedirect=0x%08x\n", parent, parentRedirect));
+ } else {
+ REDIRECT_NOISY(LOGW("applyStyle(parent): PERFORMED REDIRECT OF parent=0x%08x FOR parentRedirect=0x%08x\n", parent, parentRedirect));
+ if (parentRedirect != 0) {
+ parentActual = parentRedirect;
+ }
+ }
+ }
+ const ssize_t NP = getBagLocked(parentActual, &parentBag, &parentTypeSpecFlags);
const size_t NT = ((NP >= 0) ? NP : 0) + N;
set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT);
if (set == NULL) {
@@ -4442,6 +4484,78 @@ bool ResTable::getIdmapInfo(const void* idmap, size_t sizeBytes,
return true;
}
+void ResTable::removeAssetsByCookie(const String8 &packageName, void* cookie)
+{
+ mError = NO_ERROR;
+
+ size_t N = mHeaders.size();
+ for (size_t i = 0; i < N; i++) {
+ Header* header = mHeaders[i];
+ if ((size_t)header->cookie == (size_t)cookie) {
+ if (header->ownedData != NULL) {
+ free(header->ownedData);
+ }
+ mHeaders.removeAt(i);
+ break;
+ }
+ }
+ size_t pgCount = mPackageGroups.size();
+ for (size_t pgIndex = 0; pgIndex < pgCount; pgIndex++) {
+ PackageGroup* pg = mPackageGroups[pgIndex];
+
+ size_t pkgCount = pg->packages.size();
+ size_t index = pkgCount;
+ for (size_t pkgIndex = 0; pkgIndex < pkgCount; pkgIndex++) {
+ const Package* pkg = pg->packages[pkgIndex];
+ if (String8(String16(pkg->package->name)).compare(packageName) == 0) {
+ index = pkgIndex;
+ LOGV("Delete Package %d id=%d name=%s\n",
+ (int)pkgIndex, pkg->package->id,
+ String8(String16(pkg->package->name)).string());
+ break;
+ }
+ }
+ if (index < pkgCount) {
+ const Package* pkg = pg->packages[index];
+ uint32_t id = dtohl(pkg->package->id);
+ if (id != 0 && id < 256) {
+ mPackageMap[id] = 0;
+ }
+ if (pkgCount == 1) {
+ LOGV("Delete Package Group %d id=%d packageCount=%d name=%s\n",
+ (int)pgIndex, pg->id, (int)pg->packages.size(),
+ String8(pg->name).string());
+ mPackageGroups.removeAt(pgIndex);
+ delete pg;
+ } else {
+ pg->packages.removeAt(index);
+ delete pkg;
+ }
+ return;
+ }
+ }
+}
+
+/*
+ * Load the redirection map from the supplied map path.
+ *
+ * The path is expected to be a directory containing individual map cache files
+ * for each package that is to have resources redirected. Only those packages
+ * that are included in this ResTable will be loaded into the redirection map.
+ * For this reason, this method should be called only after all resource
+ * bundles have been added to the table.
+ */
+void ResTable::addRedirections(PackageRedirectionMap* resMap)
+{
+ // TODO: Replace an existing entry matching the same package.
+ mRedirectionMap.add(resMap);
+}
+
+void ResTable::clearRedirections()
+{
+ /* This memory is being managed by strong references at the Java layer. */
+ mRedirectionMap.clear();
+}
#ifndef HAVE_ANDROID_OS
#define CHAR16_TO_CSTR(c16, len) (String8(String16(c16,len)).string())
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index f16ba36..8b6fd4a 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,8 +21,6 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
-import android.media.AudioManager;
-import android.media.MediaPlayer;
import android.net.Uri;
import android.provider.DrmStore;
import android.provider.MediaStore;
@@ -113,6 +112,19 @@ public class Ringtone {
return mTitle = getTitle(context, mUri, true);
}
+ private static String stringForQuery(Cursor cursor) {
+ if (cursor != null) {
+ try {
+ if (cursor.moveToFirst()) {
+ return cursor.getString(0);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+ return null;
+ }
+
private static String getTitle(Context context, Uri uri, boolean followSettingsUri) {
Cursor cursor = null;
ContentResolver res = context.getContentResolver();
@@ -131,8 +143,15 @@ public class Ringtone {
.getString(com.android.internal.R.string.ringtone_default_with_actual,
actualTitle);
}
+ } else if (RingtoneManager.THEME_AUTHORITY.equals(authority)) {
+ Uri themes = Uri.parse("content://com.tmobile.thememanager.themes/themes");
+ title = stringForQuery(res.query(themes, new String[] { "ringtone_name" },
+ "ringtone_uri = ?", new String[] { uri.toString() }, null));
+ if (title == null) {
+ title = stringForQuery(res.query(themes, new String[] { "notif_ringtone_name" },
+ "notif_ringtone_uri = ?", new String[] { uri.toString() }, null));
+ }
} else {
-
if (DrmStore.AUTHORITY.equals(authority)) {
cursor = res.query(uri, DRM_COLUMNS, null, null, null);
} else if (MediaStore.AUTHORITY.equals(authority)) {
diff --git a/media/java/android/media/RingtoneManager.java b/media/java/android/media/RingtoneManager.java
index 7aa4109..7d253f1 100644
--- a/media/java/android/media/RingtoneManager.java
+++ b/media/java/android/media/RingtoneManager.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,7 +26,6 @@ import android.content.ContentUris;
import android.app.ProfileGroup;
import android.app.ProfileManager;
import android.content.Context;
-import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.net.Uri;
import android.os.Environment;
@@ -177,23 +177,28 @@ public class RingtoneManager {
public static final String EXTRA_RINGTONE_PICKED_URI =
"android.intent.extra.ringtone.PICKED_URI";
+ /**
+ * @hide
+ */
+ public static final String THEME_AUTHORITY = "com.tmobile.thememanager.packageresources";
+
// Make sure the column ordering and then ..._COLUMN_INDEX are in sync
private static final String[] INTERNAL_COLUMNS = new String[] {
MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
- "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\"",
+ "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "/\" || " + MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE_KEY
};
private static final String[] DRM_COLUMNS = new String[] {
DrmStore.Audio._ID, DrmStore.Audio.TITLE,
- "\"" + DrmStore.Audio.CONTENT_URI + "\"",
+ "\"" + DrmStore.Audio.CONTENT_URI + "/\" || " + DrmStore.Audio._ID,
DrmStore.Audio.TITLE + " AS " + MediaStore.Audio.Media.TITLE_KEY
};
private static final String[] MEDIA_COLUMNS = new String[] {
MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
- "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"",
+ "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "/\" || " + MediaStore.Audio.Media._ID,
MediaStore.Audio.Media.TITLE_KEY
};
@@ -367,8 +372,11 @@ public class RingtoneManager {
final Cursor internalCursor = getInternalRingtones();
final Cursor drmCursor = mIncludeDrm ? getDrmRingtones() : null;
final Cursor mediaCursor = getMediaRingtones();
-
- return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor },
+ final Cursor themeRegularCursor = getThemeRegularRingtones();
+ final Cursor themeNotifCursor = getThemeNotificationRingtones();
+
+ return mCursor = new SortCursor(new Cursor[] { internalCursor, drmCursor, mediaCursor,
+ themeRegularCursor, themeNotifCursor },
MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
}
@@ -405,8 +413,7 @@ public class RingtoneManager {
}
private static Uri getUriFromCursor(Cursor cursor) {
- return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor
- .getLong(ID_COLUMN_INDEX));
+ return Uri.parse(cursor.getString(URI_COLUMN_INDEX));
}
/**
@@ -426,23 +433,12 @@ public class RingtoneManager {
return -1;
}
- // Only create Uri objects when the actual URI changes
- Uri currentUri = null;
- String previousUriString = null;
for (int i = 0; i < cursorCount; i++) {
- String uriString = cursor.getString(URI_COLUMN_INDEX);
- if (currentUri == null || !uriString.equals(previousUriString)) {
- currentUri = Uri.parse(uriString);
- }
-
- if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor
- .getLong(ID_COLUMN_INDEX)))) {
+ if (ringtoneUri.equals(getUriFromCursor(cursor))) {
return i;
}
cursor.move(1);
-
- previousUriString = uriString;
}
return -1;
@@ -468,6 +464,14 @@ public class RingtoneManager {
uri = getValidRingtoneUriFromCursorAndClose(context, rm.getDrmRingtones());
}
+ if (uri == null) {
+ uri = getValidRingtoneUriFromCursorAndClose(context, rm.getThemeRegularRingtones());
+ }
+
+ if (uri == null) {
+ uri = getValidRingtoneUriFromCursorAndClose(context, rm.getThemeNotificationRingtones());
+ }
+
return uri;
}
@@ -513,6 +517,38 @@ public class RingtoneManager {
: null;
}
+ private String getThemeWhereClause(String uriColumn) {
+ /* Filter out themes with no ringtone and the default theme (which has no package). */
+ String clause = uriColumn + " IS NOT NULL AND LENGTH(theme_package) > 0";
+ if (mIncludeDrm) {
+ return clause;
+ } else {
+ return clause + " AND " + uriColumn + " NOT LIKE '%/assets/%locked%'";
+ }
+ }
+
+ private Cursor getThemeRegularRingtones() {
+ if ((mType & TYPE_RINGTONE) != 0) {
+ return query(Uri.parse("content://com.tmobile.thememanager.themes/themes"),
+ new String[] { "_id", "ringtone_name AS " + MEDIA_COLUMNS[1], "ringtone_uri",
+ "ringtone_name_key AS " + MEDIA_COLUMNS[3] },
+ getThemeWhereClause("ringtone_uri"), null, MEDIA_COLUMNS[3]);
+ } else {
+ return null;
+ }
+ }
+
+ private Cursor getThemeNotificationRingtones() {
+ if ((mType & TYPE_NOTIFICATION) != 0) {
+ return query(Uri.parse("content://com.tmobile.thememanager.themes/themes"),
+ new String[] { "_id", "notif_ringtone_name AS " + MEDIA_COLUMNS[1], "notif_ringtone_uri",
+ "notif_ringtone_name_key AS " + MEDIA_COLUMNS[3] },
+ getThemeWhereClause("notif_ringtone_uri"), null, MEDIA_COLUMNS[3]);
+ } else {
+ return null;
+ }
+ }
+
private void setFilterColumnsList(int type) {
List<String> columns = mFilterColumns;
columns.clear();
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 95fd62d..e0f31b7 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2007 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -636,7 +637,8 @@ public class SettingsProvider extends ContentProvider {
// Only proxy the openFile call to drm or media providers
String authority = soundUri.getAuthority();
boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY);
- if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) {
+ if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY) ||
+ authority.equals(RingtoneManager.THEME_AUTHORITY)) {
if (isDrmAuthority) {
try {
@@ -677,7 +679,8 @@ public class SettingsProvider extends ContentProvider {
// Only proxy the openFile call to drm or media providers
String authority = soundUri.getAuthority();
boolean isDrmAuthority = authority.equals(DrmStore.AUTHORITY);
- if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY)) {
+ if (isDrmAuthority || authority.equals(MediaStore.AUTHORITY) ||
+ authority.equals(RingtoneManager.THEME_AUTHORITY)) {
if (isDrmAuthority) {
try {
@@ -690,10 +693,8 @@ public class SettingsProvider extends ContentProvider {
}
}
- ParcelFileDescriptor pfd = null;
try {
- pfd = context.getContentResolver().openFileDescriptor(soundUri, mode);
- return new AssetFileDescriptor(pfd, 0, -1);
+ return context.getContentResolver().openAssetFileDescriptor(soundUri, mode);
} catch (FileNotFoundException ex) {
// fall through and open the fallback ringtone below
}
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index cfc48ee..1325142 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -23,7 +23,8 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res/com.android.systemui"
android:background="@drawable/status_bar_background"
- android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:focusable="true"
android:descendantFocusability="afterDescendants"
>
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUI.java b/packages/SystemUI/src/com/android/systemui/SystemUI.java
index 2110483..6862068 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUI.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUI.java
@@ -19,6 +19,8 @@ package com.android.systemui;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import android.widget.FrameLayout;
+
import android.content.Context;
import android.content.res.Configuration;
@@ -26,6 +28,8 @@ public abstract class SystemUI {
public Context mContext;
public abstract void start();
+
+ public FrameLayout mStatusBarContainer;
protected void onConfigurationChanged(Configuration newConfig) {
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index d7a5056..7fdafff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -32,6 +32,8 @@ import android.os.ServiceManager;
import android.util.Slog;
import android.view.IWindowManager;
+import android.widget.FrameLayout;
+
public class SystemUIService extends Service {
static final String TAG = "SystemUIService";
@@ -89,7 +91,9 @@ public class SystemUIService extends Service {
throw new RuntimeException(ex);
}
mServices[i].mContext = this;
+ mServices[i].mStatusBarContainer = new FrameLayout(this);
Slog.d(TAG, "running: " + mServices[i]);
+
mServices[i].start();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 3d904ee..90ae038 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -107,6 +107,10 @@ public class NotificationData {
return e;
}
+ public void clear() {
+ mEntries.clear();
+ }
+
/**
* Return whether there are any visible items (i.e. items without an error).
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
index 2be35b7..371af53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBar.java
@@ -63,6 +63,8 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac
// First set up our views and stuff.
View sb = makeStatusBarView();
+ mStatusBarContainer.addView(sb);
+
// Connect in to the status bar manager service
StatusBarIconList iconList = new StatusBarIconList();
ArrayList<IBinder> notificationKeys = new ArrayList<IBinder>();
@@ -137,7 +139,7 @@ public abstract class StatusBar extends SystemUI implements CommandQueue.Callbac
lp.setTitle("StatusBar");
lp.packageName = mContext.getPackageName();
lp.windowAnimations = R.style.Animation_StatusBar;
- WindowManagerImpl.getDefault().addView(sb, lp);
+ WindowManagerImpl.getDefault().addView(mStatusBarContainer, lp);
if (SPEW) {
Slog.d(TAG, "Added status bar view: gravity=0x" + Integer.toHexString(lp.gravity)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
index 8228df5..1f57bf7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarIconView.java
@@ -201,6 +201,10 @@ public class StatusBarIconView extends AnimatedImageView {
}
}
+ public String getStatusBarSlot() {
+ return mSlot;
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index d551ef5..ecad4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -33,6 +33,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.content.res.Configuration;
+import android.content.res.CustomTheme;
import android.database.ContentObserver;
import android.graphics.PixelFormat;
import android.graphics.Rect;
@@ -48,6 +49,7 @@ import android.os.SystemClock;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.DisplayMetrics;
+import android.util.Pair;
import android.util.Slog;
import android.util.Log;
import android.view.Display;
@@ -66,6 +68,7 @@ import android.view.WindowManagerImpl;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.ScrollView;
@@ -235,6 +238,10 @@ public class PhoneStatusBar extends StatusBar {
int mLinger = 0;
Runnable mPostCollapseCleanup = null;
+ // last theme that was applied in order to detect theme change (as opposed
+ // to some other configuration change).
+ CustomTheme mCurrentTheme;
+ private boolean mRecreating = false;
// for disabling the status bar
int mDisabled = 0;
@@ -300,6 +307,11 @@ public class PhoneStatusBar extends StatusBar {
mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ CustomTheme currentTheme = mContext.getResources().getConfiguration().customTheme;
+ if (currentTheme != null) {
+ mCurrentTheme = (CustomTheme)currentTheme.clone();
+ }
+
super.start(); // calls makeStatusBarView()
addNavigationBar();
@@ -365,6 +377,11 @@ public class PhoneStatusBar extends StatusBar {
mIcons = (LinearLayout)sb.findViewById(R.id.icons);
mTickerView = sb.findViewById(R.id.ticker);
+ /* Destroy the old widget before recreating the expanded dialog
+ to make sure there are no context issues */
+ if (mRecreating)
+ mPowerWidget.destroyWidget();
+
mExpandedDialog = new ExpandedDialog(context);
mExpandedView = expanded;
mPile = (NotificationRowLayout)expanded.findViewById(R.id.latestItems);
@@ -533,6 +550,12 @@ public class PhoneStatusBar extends StatusBar {
private void repositionNavigationBar() {
if (mNavigationBarView == null) return;
+ CustomTheme newTheme = mContext.getResources().getConfiguration().customTheme;
+ if (newTheme != null &&
+ (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) {
+ // Nevermind, this will be re-created
+ return;
+ }
prepareNavigationBarView();
WindowManagerImpl.getDefault().updateViewLayout(
@@ -650,7 +673,7 @@ public class PhoneStatusBar extends StatusBar {
notification.notification.fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
- } else {
+ } else if (!mRecreating) {
// usual case: status bar visible & not immersive
// show the ticker
@@ -1966,6 +1989,10 @@ public class PhoneStatusBar extends StatusBar {
WindowManagerImpl.getDefault().addView(mTrackingView, lp);
}
+ void onBarViewDetached() {
+ WindowManagerImpl.getDefault().removeView(mTrackingView);
+ }
+
void onTrackingViewAttached() {
WindowManager.LayoutParams lp;
int pixelFormat;
@@ -2001,6 +2028,9 @@ public class PhoneStatusBar extends StatusBar {
mExpandedDialog.show();
}
+ void onTrackingViewDetached() {
+ }
+
void setNotificationIconVisibility(boolean visible, int anim) {
int old = mNotificationIcons.getVisibility();
int v = visible ? View.VISIBLE : View.INVISIBLE;
@@ -2135,7 +2165,8 @@ public class PhoneStatusBar extends StatusBar {
if (DEBUG) {
Slog.d(TAG, "updateDisplaySize: " + mDisplayMetrics);
}
- updateExpandedSize();
+ if (!mRecreating)
+ updateExpandedSize();
}
void updateExpandedSize() {
@@ -2337,6 +2368,59 @@ public class PhoneStatusBar extends StatusBar {
mIntruderAlertView.setVisibility(vis ? View.VISIBLE : View.GONE);
}
+ private static void copyNotifications(ArrayList<Pair<IBinder, StatusBarNotification>> dest,
+ NotificationData source) {
+ int N = source.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry entry = source.get(i);
+ dest.add(Pair.create(entry.key, entry.notification));
+ }
+ }
+
+ private void recreateStatusBar() {
+ mRecreating = true;
+ mStatusBarContainer.removeAllViews();
+
+ // extract icons from the soon-to-be recreated viewgroup.
+ int nIcons = mStatusIcons.getChildCount();
+ ArrayList<StatusBarIcon> icons = new ArrayList<StatusBarIcon>(nIcons);
+ ArrayList<String> iconSlots = new ArrayList<String>(nIcons);
+ for (int i = 0; i < nIcons; i++) {
+ StatusBarIconView iconView = (StatusBarIconView)mStatusIcons.getChildAt(i);
+ icons.add(iconView.getStatusBarIcon());
+ iconSlots.add(iconView.getStatusBarSlot());
+ }
+
+ // extract notifications.
+ int nNotifs = mNotificationData.size();
+ ArrayList<Pair<IBinder, StatusBarNotification>> notifications =
+ new ArrayList<Pair<IBinder, StatusBarNotification>>(nNotifs);
+ copyNotifications(notifications, mNotificationData);
+ mNotificationData.clear();
+
+ View newStatusBarView = makeStatusBarView();
+
+ // recreate StatusBarIconViews.
+ for (int i = 0; i < nIcons; i++) {
+ StatusBarIcon icon = icons.get(i);
+ String slot = iconSlots.get(i);
+ addIcon(slot, i, i, icon);
+ }
+
+ // recreate notifications.
+ for (int i = 0; i < nNotifs; i++) {
+ Pair<IBinder, StatusBarNotification> notifData = notifications.get(i);
+ addNotificationViews(notifData.first, notifData.second);
+ }
+
+ setAreThereNotifications();
+
+ mStatusBarContainer.addView(newStatusBarView);
+
+ updateExpandedViewPos(EXPANDED_LEAVE_ALONE);
+ mRecreating = false;
+ }
+
/**
* Reload some of our resources when the configuration changes.
*
@@ -2348,12 +2432,21 @@ public class PhoneStatusBar extends StatusBar {
final Context context = mContext;
final Resources res = context.getResources();
- if (mClearButton instanceof TextView) {
- ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button));
- }
- mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title));
+ // detect theme change.
+ CustomTheme newTheme = res.getConfiguration().customTheme;
+ if (newTheme != null &&
+ (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) {
+ mCurrentTheme = (CustomTheme)newTheme.clone();
+ recreateStatusBar();
+ } else {
- loadDimens();
+ if (mClearButton instanceof TextView) {
+ ((TextView)mClearButton).setText(context.getText(R.string.status_bar_clear_all_button));
+ }
+ mNoNotificationsTitle.setText(context.getText(R.string.status_bar_no_notifications_title));
+
+ loadDimens();
+ }
}
protected void loadDimens() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index 809b742..0818370 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -90,7 +90,13 @@ public class PhoneStatusBarView extends FrameLayout {
return mEndAlpha
- (int)(((mEndAlpha-mStartAlpha) * (mEndTime-time) / DIM_ANIM_TIME));
}
-
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mService.onBarViewDetached();
+ }
+
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java
index cc23afc..459c376 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TrackingView.java
@@ -70,4 +70,10 @@ public class TrackingView extends LinearLayout {
});
}
}
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mService.onTrackingViewDetached();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java b/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java
index 17ee782..251d62a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/powerwidget/PowerWidget.java
@@ -84,7 +84,7 @@ public class PowerWidget extends FrameLayout {
updateVisibility();
}
- public void setupWidget() {
+ public void destroyWidget() {
Log.i(TAG, "Clearing any old widget stuffs");
// remove all views from the layout
removeAllViews();
@@ -100,6 +100,10 @@ public class PowerWidget extends FrameLayout {
// clear the button instances
PowerButton.unloadAllButtons();
+ }
+
+ public void setupWidget() {
+ destroyWidget();
Log.i(TAG, "Setting up widget");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
index 5c1d363..4eadbcc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/TabletStatusBar.java
@@ -36,6 +36,7 @@ import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
+import android.content.res.CustomTheme;
import android.content.res.Resources;
import android.inputmethodservice.InputMethodService;
import android.graphics.PixelFormat;
@@ -52,6 +53,7 @@ import android.os.ServiceManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.text.TextUtils;
+import android.util.Pair;
import android.util.Slog;
import android.view.accessibility.AccessibilityEvent;
import android.view.Display;
@@ -68,6 +70,7 @@ import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.WindowManagerImpl;
import android.widget.ImageView;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.ScrollView;
@@ -206,6 +209,12 @@ public class TabletStatusBar extends StatusBar implements
private StorageManager mStorageManager;
+ // last theme that was applied in order to detect theme change (as opposed
+ // to some other configuration change).
+ CustomTheme mCurrentTheme;
+ private boolean mRecreating = false;
+
+
protected void addPanelWindows() {
final Context context = mContext;
final Resources res = mContext.getResources();
@@ -409,14 +418,54 @@ public class TabletStatusBar extends StatusBar implements
super.start(); // will add the main bar view
}
+ private static void copyNotifications(ArrayList<Pair<IBinder, StatusBarNotification>> dest,
+ NotificationData source) {
+ int N = source.size();
+ for (int i = 0; i < N; i++) {
+ NotificationData.Entry entry = source.get(i);
+ dest.add(Pair.create(entry.key, entry.notification));
+ }
+ }
+
+ private void recreateStatusBar() {
+ mRecreating = true;
+ mStatusBarContainer.removeAllViews();
+
+ // extract notifications.
+ int nNotifs = mNotificationData.size();
+ ArrayList<Pair<IBinder, StatusBarNotification>> notifications =
+ new ArrayList<Pair<IBinder, StatusBarNotification>>(nNotifs);
+ copyNotifications(notifications, mNotificationData);
+ mNotificationData.clear();
+
+ mStatusBarContainer.addView(makeStatusBarView());
+
+ // recreate notifications.
+ for (int i = 0; i < nNotifs; i++) {
+ Pair<IBinder, StatusBarNotification> notifData = notifications.get(i);
+ addNotificationViews(notifData.first, notifData.second);
+ }
+
+ setAreThereNotifications();
+
+ mRecreating = false;
+ }
+
@Override
protected void onConfigurationChanged(Configuration newConfig) {
- mHeightReceiver.updateHeight(); // display size may have changed
- loadDimens();
- mNotificationPanelParams.height = getNotificationPanelHeight();
- WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
- mNotificationPanelParams);
- mRecentsPanel.updateValuesFromResources();
+ // detect theme change.
+ CustomTheme newTheme = mContext.getResources().getConfiguration().customTheme;
+ if (newTheme != null &&
+ (mCurrentTheme == null || !mCurrentTheme.equals(newTheme))) {
+ mCurrentTheme = (CustomTheme)newTheme.clone();
+ recreateStatusBar();
+ }
+ mHeightReceiver.updateHeight(); // display size may have changed
+ loadDimens();
+ mNotificationPanelParams.height = getNotificationPanelHeight();
+ WindowManagerImpl.getDefault().updateViewLayout(mNotificationPanel,
+ mNotificationPanelParams);
+ mRecentsPanel.updateValuesFromResources();
}
protected void loadDimens() {
@@ -451,6 +500,11 @@ public class TabletStatusBar extends StatusBar implements
mWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
+ CustomTheme currentTheme = mContext.getResources().getConfiguration().customTheme;
+ if (currentTheme != null) {
+ mCurrentTheme = (CustomTheme)currentTheme.clone();
+ }
+
// This guy will listen for HDMI plugged broadcasts so we can resize the
// status bar as appropriate.
mHeightReceiver = new HeightReceiver(mContext);
@@ -653,7 +707,7 @@ public class TabletStatusBar extends StatusBar implements
public void onBarHeightChanged(int height) {
final WindowManager.LayoutParams lp
- = (WindowManager.LayoutParams)mStatusBarView.getLayoutParams();
+ = (WindowManager.LayoutParams)mStatusBarContainer.getLayoutParams();
if (lp == null) {
// haven't been added yet
return;
@@ -661,7 +715,7 @@ public class TabletStatusBar extends StatusBar implements
if (lp.height != height) {
lp.height = height;
final WindowManager wm = WindowManagerImpl.getDefault();
- wm.updateViewLayout(mStatusBarView, lp);
+ wm.updateViewLayout(mStatusBarContainer, lp);
}
}
@@ -840,7 +894,7 @@ public class TabletStatusBar extends StatusBar implements
notification.notification.fullScreenIntent.send();
} catch (PendingIntent.CanceledException e) {
}
- } else {
+ } else if (!mRecreating) {
tick(key, notification, true);
}
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 231606e..9d627ad 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -1365,13 +1365,15 @@ public class PhoneWindowManager implements WindowManagerPolicy {
Context context = mContext;
//Log.i(TAG, "addStartingWindow " + packageName + ": nonLocalizedLabel="
// + nonLocalizedLabel + " theme=" + Integer.toHexString(theme));
- if (theme != context.getThemeResId() || labelRes != 0) {
- try {
- context = context.createPackageContext(packageName, 0);
+
+
+ try {
+ context = context.createPackageContext(packageName, 0);
+ if (theme != context.getThemeResId()) {
context.setTheme(theme);
- } catch (PackageManager.NameNotFoundException e) {
- // Ignore
}
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
}
Window win = PolicyManager.makeNewWindow(context);
diff --git a/services/java/com/android/server/AppsLaunchFailureReceiver.java b/services/java/com/android/server/AppsLaunchFailureReceiver.java
new file mode 100644
index 0000000..6ef07aa
--- /dev/null
+++ b/services/java/com/android/server/AppsLaunchFailureReceiver.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2010, T-Mobile USA, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.content.res.CustomTheme;
+import android.util.Log;
+import android.app.ActivityManager;
+import android.os.SystemClock;
+
+public class AppsLaunchFailureReceiver extends BroadcastReceiver {
+
+ private static final int FAILURES_THRESHOLD = 5;
+ private static final int EXPIRATION_TIME_IN_MILLISECONDS = 30000; // 30 seconds
+
+ private int mFailuresCount = 0;
+ private long mStartTime = 0;
+
+ // This function implements the following logic.
+ // If after a theme was applied the number of application launch failures
+ // at any moment was equal to FAILURES_THRESHOLD
+ // in less than EXPIRATION_TIME_IN_MILLISECONDS
+ // the default theme is applied unconditionally.
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE)) {
+ long currentTime = SystemClock.uptimeMillis();
+ if (currentTime - mStartTime > EXPIRATION_TIME_IN_MILLISECONDS) {
+ // reset both the count and the timer
+ mStartTime = currentTime;
+ mFailuresCount = 0;
+ }
+ if (mFailuresCount <= FAILURES_THRESHOLD) {
+ mFailuresCount++;
+ if (mFailuresCount == FAILURES_THRESHOLD) {
+ CustomTheme defaultTheme = CustomTheme.getSystemTheme();
+ ActivityManager am = (ActivityManager)context.getSystemService(Context.ACTIVITY_SERVICE);
+ Configuration currentConfig = am.getConfiguration();
+ currentConfig.customTheme = new CustomTheme(
+ defaultTheme.getThemeId(),
+ defaultTheme.getThemePackageName());
+ am.updateConfiguration(currentConfig);
+ }
+ }
+ } else if (action.equals(Intent.ACTION_APP_LAUNCH_FAILURE_RESET)) {
+ mFailuresCount = 0;
+ mStartTime = SystemClock.uptimeMillis();
+ } else if (action.equals(Intent.ACTION_PACKAGE_ADDED) ||
+ action.equals(Intent.ACTION_PACKAGE_REMOVED)) {
+ mFailuresCount = 0;
+ mStartTime = SystemClock.uptimeMillis();
+ }
+ }
+
+}
diff --git a/services/java/com/android/server/AssetRedirectionManagerService.java b/services/java/com/android/server/AssetRedirectionManagerService.java
new file mode 100644
index 0000000..1e124b9
--- /dev/null
+++ b/services/java/com/android/server/AssetRedirectionManagerService.java
@@ -0,0 +1,397 @@
+package com.android.server;
+
+import com.android.internal.app.IAssetRedirectionManager;
+import com.android.internal.util.XmlUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Context;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ThemeInfo;
+import android.content.res.AssetManager;
+import android.content.res.PackageRedirectionMap;
+import android.content.res.Resources;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+public class AssetRedirectionManagerService extends IAssetRedirectionManager.Stub {
+ private static final String TAG = "AssetRedirectionManager";
+
+ private final Context mContext;
+
+ /*
+ * TODO: This data structure should have some way to expire very old cache
+ * entries. Would be nice to optimize for the removal path as well.
+ */
+ private final HashMap<RedirectionKey, PackageRedirectionMap> mRedirections =
+ new HashMap<RedirectionKey, PackageRedirectionMap>();
+
+ public AssetRedirectionManagerService(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void clearRedirectionMapsByTheme(String themePackageName, String themeId)
+ throws RemoteException {
+ synchronized (mRedirections) {
+ Set<RedirectionKey> keys = mRedirections.keySet();
+ Iterator<RedirectionKey> iter = keys.iterator();
+ while (iter.hasNext()) {
+ RedirectionKey key = iter.next();
+ if (themePackageName.equals(key.themePackageName) &&
+ (themeId == null || themeId.equals(key.themeId))) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public void clearPackageRedirectionMap(String targetPackageName) throws RemoteException {
+ synchronized (mRedirections) {
+ Set<RedirectionKey> keys = mRedirections.keySet();
+ Iterator<RedirectionKey> iter = keys.iterator();
+ while (iter.hasNext()) {
+ RedirectionKey key = iter.next();
+ if (targetPackageName.equals(key.targetPackageName)) {
+ iter.remove();
+ }
+ }
+ }
+ }
+
+ @Override
+ public PackageRedirectionMap getPackageRedirectionMap(String themePackageName,
+ String themeId, String targetPackageName) throws RemoteException {
+ synchronized (mRedirections) {
+ RedirectionKey key = new RedirectionKey();
+ key.themePackageName = themePackageName;
+ key.themeId = themeId;
+ key.targetPackageName = targetPackageName;
+
+ PackageRedirectionMap map = mRedirections.get(key);
+ if (map != null) {
+ return map;
+ } else {
+ map = generatePackageRedirectionMap(key);
+ if (map != null) {
+ mRedirections.put(key, map);
+ }
+ return map;
+ }
+ }
+ }
+
+ private PackageRedirectionMap generatePackageRedirectionMap(RedirectionKey key) {
+ AssetManager assets = new AssetManager();
+
+ boolean frameworkAssets = key.targetPackageName.equals("android");
+
+ if (!frameworkAssets) {
+ PackageInfo pi = getPackageInfo(mContext, key.targetPackageName);
+ if (pi == null || pi.applicationInfo == null ||
+ assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) {
+ Log.w(TAG, "Unable to attach target package assets for " + key.targetPackageName);
+ return null;
+ }
+ }
+
+ PackageInfo pi = getPackageInfo(mContext, key.themePackageName);
+ if (pi == null || pi.applicationInfo == null || pi.themeInfos == null ||
+ assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) {
+ Log.w(TAG, "Unable to attach theme package assets from " + key.themePackageName);
+ return null;
+ }
+
+ PackageRedirectionMap resMap = new PackageRedirectionMap();
+
+ /*
+ * Apply a special redirection hack for the highest level <style>
+ * replacing @android:style/Theme.
+ */
+ if (frameworkAssets) {
+ int themeResourceId = findThemeResourceId(pi.themeInfos, key.themeId);
+ assets.generateStyleRedirections(resMap.getNativePointer(), android.R.style.Theme,
+ themeResourceId);
+ }
+
+ Resources res = new Resources(assets, null, null);
+ generateExplicitRedirections(resMap, res, key.themePackageName, key.targetPackageName);
+
+ return resMap;
+ }
+
+ private void generateExplicitRedirections(PackageRedirectionMap resMap, Resources res,
+ String themePackageName, String targetPackageName) {
+ /*
+ * XXX: We should be parsing the <theme> tag's <meta-data>! Instead,
+ * we're just assuming that res/xml/<package>.xml exists and describes
+ * the redirects we want!
+ */
+ String redirectXmlName = targetPackageName.replace('.', '_');
+ int redirectXmlResId = res.getIdentifier(redirectXmlName, "xml", themePackageName);
+ if (redirectXmlResId == 0) {
+ return;
+ }
+
+ ResourceRedirectionsProcessor processor = new ResourceRedirectionsProcessor(res,
+ redirectXmlResId, themePackageName, targetPackageName, resMap);
+ processor.process();
+ }
+
+ private static PackageInfo getPackageInfo(Context context, String packageName) {
+ try {
+ return context.getPackageManager().getPackageInfo(packageName, 0);
+ } catch (NameNotFoundException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Searches for the high-level theme resource id for the specific
+ * &lt;theme&gt; tag being applied.
+ * <p>
+ * An individual theme package can contain multiple &lt;theme&gt; tags, each
+ * representing a separate theme choice from the user's perspective, even
+ * though the most common case is for there to be only 1.
+ *
+ * @return The style resource id or 0 if no match was found.
+ */
+ private static int findThemeResourceId(ThemeInfo[] themeInfos, String needle) {
+ if (themeInfos != null && !TextUtils.isEmpty(needle)) {
+ int n = themeInfos.length;
+ for (int i = 0; i < n; i++) {
+ ThemeInfo info = themeInfos[i];
+ if (needle.equals(info.themeId)) {
+ return info.styleResourceId;
+ }
+ }
+ }
+ return 0;
+ }
+
+ private static Resources getUnredirectedResourcesForPackage(Context context, String packageName) {
+ AssetManager assets = new AssetManager();
+
+ if (!packageName.equals("android")) {
+ PackageInfo pi = getPackageInfo(context, packageName);
+ if (pi == null || pi.applicationInfo == null ||
+ assets.addAssetPath(pi.applicationInfo.publicSourceDir) == 0) {
+ Log.w(TAG, "Unable to get resources for package " + packageName);
+ return null;
+ }
+ }
+
+ return new Resources(assets, null, null);
+ }
+
+ @Override
+ protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+ synchronized (mRedirections) {
+ final ArrayList<RedirectionKey> filteredKeySet = new ArrayList<RedirectionKey>();
+ for (Map.Entry<RedirectionKey, PackageRedirectionMap> entry: mRedirections.entrySet()) {
+ PackageRedirectionMap map = entry.getValue();
+ if (map != null && map.getPackageId() != -1) {
+ filteredKeySet.add(entry.getKey());
+ }
+ }
+ Collections.sort(filteredKeySet, new Comparator<RedirectionKey>() {
+ @Override
+ public int compare(RedirectionKey a, RedirectionKey b) {
+ int comp = a.themePackageName.compareTo(b.themePackageName);
+ if (comp != 0) {
+ return comp;
+ }
+ comp = a.themeId.compareTo(b.themeId);
+ if (comp != 0) {
+ return comp;
+ }
+ return a.targetPackageName.compareTo(b.targetPackageName);
+ }
+ });
+
+ pw.println("Theme asset redirections:");
+ String lastPackageName = null;
+ String lastId = null;
+ Resources themeRes = null;
+ for (RedirectionKey key: filteredKeySet) {
+ if (lastPackageName == null || !lastPackageName.equals(key.themePackageName)) {
+ pw.println("* Theme package " + key.themePackageName + ":");
+ lastPackageName = key.themePackageName;
+ themeRes = getUnredirectedResourcesForPackage(mContext, key.themePackageName);
+ }
+ if (lastId == null || !lastId.equals(key.themeId)) {
+ pw.println(" theme id #" + key.themeId + ":");
+ lastId = key.themeId;
+ }
+ pw.println(" " + key.targetPackageName + ":");
+ Resources targetRes = getUnredirectedResourcesForPackage(mContext, key.targetPackageName);
+ PackageRedirectionMap resMap = mRedirections.get(key);
+ int[] fromIdents = resMap.getRedirectionKeys();
+ int N = fromIdents.length;
+ for (int i = 0; i < N; i++) {
+ int fromIdent = fromIdents[i];
+ int toIdent = resMap.lookupRedirection(fromIdent);
+ String fromName = targetRes != null ? targetRes.getResourceName(fromIdent) : null;
+ String toName = themeRes != null ? themeRes.getResourceName(toIdent) : null;
+ pw.println(String.format(" %s (0x%08x) => %s (0x%08x)", fromName, fromIdent,
+ toName, toIdent));
+ }
+ }
+ }
+ }
+
+ private static class RedirectionKey {
+ public String themePackageName;
+ public String themeId;
+ public String targetPackageName;
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if (!(o instanceof RedirectionKey)) return false;
+ final RedirectionKey oo = (RedirectionKey)o;
+ if (!nullSafeEquals(themePackageName, oo.themePackageName)) {
+ return false;
+ }
+ if (!nullSafeEquals(themeId, oo.themeId)) {
+ return false;
+ }
+ if (!nullSafeEquals(targetPackageName, oo.targetPackageName)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return themePackageName.hashCode() +
+ themeId.hashCode() +
+ targetPackageName.hashCode();
+ }
+
+ private static boolean nullSafeEquals(Object a, Object b) {
+ if (a == null) {
+ return b == a;
+ } else if (b == null) {
+ return false;
+ } else {
+ return a.equals(b);
+ }
+ }
+ }
+
+ /**
+ * Parses and processes explicit redirection XML files.
+ */
+ private static class ResourceRedirectionsProcessor {
+ private final Resources mResources;
+ private final XmlPullParser mParser;
+ private final int mResourceId;
+ private final String mThemePackageName;
+ private final String mTargetPackageName;
+ private final PackageRedirectionMap mResMap;
+
+ public ResourceRedirectionsProcessor(Resources res, int resourceId,
+ String themePackageName, String targetPackageName,
+ PackageRedirectionMap outMap) {
+ mResources = res;
+ mParser = res.getXml(resourceId);
+ mResourceId = resourceId;
+ mThemePackageName = themePackageName;
+ mTargetPackageName = targetPackageName;
+ mResMap = outMap;
+ }
+
+ public void process() {
+ XmlPullParser parser = mParser;
+ int type;
+ try {
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // just loop...
+ }
+
+ String tagName = parser.getName();
+ if (parser.getName().equals("resource-redirections")) {
+ processResourceRedirectionsTag();
+ } else {
+ Log.w(TAG, "Unknown root element: " + tagName + " at " + getResourceLabel() + " " +
+ parser.getPositionDescription());
+ }
+ } catch (XmlPullParserException e) {
+ Log.w(TAG, "Malformed theme redirection meta at " + getResourceLabel());
+ } catch (IOException e) {
+ Log.w(TAG, "Unknown error reading redirection meta at " + getResourceLabel());
+ }
+ }
+
+ private void processResourceRedirectionsTag() throws XmlPullParserException, IOException {
+ XmlPullParser parser = mParser;
+ int type;
+ final int innerDepth = parser.getDepth();
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT &&
+ (type != XmlPullParser.END_TAG || parser.getDepth() > innerDepth)) {
+ if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+ continue;
+ }
+
+ String tagName = parser.getName();
+ if (tagName.equals("item")) {
+ processItemTag();
+ } else {
+ Log.w(TAG, "Unknown element under <resource-redirections>: " + tagName
+ + " at " + getResourceLabel() + " "
+ + parser.getPositionDescription());
+ XmlUtils.skipCurrentTag(parser);
+ continue;
+ }
+ }
+ }
+
+ private void processItemTag() throws XmlPullParserException, IOException {
+ XmlPullParser parser = mParser;
+ String fromName = parser.getAttributeValue(null, "name");
+ if (TextUtils.isEmpty(fromName)) {
+ Log.w(TAG, "Missing android:name attribute on <item> tag at " + getResourceLabel() + " " +
+ parser.getPositionDescription());
+ return;
+ }
+ String toName = parser.nextText();
+ if (TextUtils.isEmpty(toName)) {
+ Log.w(TAG, "Missing <item> text at " + getResourceLabel() + " " +
+ parser.getPositionDescription());
+ return;
+ }
+ int fromIdent = mResources.getIdentifier(fromName, null, mTargetPackageName);
+ if (fromIdent == 0) {
+ Log.w(TAG, "No such resource found for " + mTargetPackageName + ":" + fromName);
+ return;
+ }
+ int toIdent = mResources.getIdentifier(toName, null, mThemePackageName);
+ if (toIdent == 0) {
+ Log.w(TAG, "No such resource found for " + mThemePackageName + ":" + toName);
+ return;
+ }
+ mResMap.addRedirection(fromIdent, toIdent);
+ }
+
+ private String getResourceLabel() {
+ return "resource #0x" + Integer.toHexString(mResourceId);
+ }
+ }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d8ea9bf..ad78a3c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2006 The Android Open Source Project
* Copyright (c) 2010-2011, Code Aurora Forum. All rights reserved.
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -25,6 +26,7 @@ import android.content.ContentResolver;
import android.content.ContentService;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.IPackageManager;
import android.content.res.Configuration;
import android.database.ContentObserver;
@@ -601,6 +603,13 @@ class ServerThread extends Thread {
} catch (Throwable e) {
reportWtf("starting NetworkTimeUpdate service", e);
}
+
+ try {
+ Slog.i(TAG, "AssetRedirectionManager Service");
+ ServiceManager.addService("assetredirection", new AssetRedirectionManagerService(context));
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failure starting AssetRedirectionManager Service", e);
+ }
}
// make sure the ADB_ENABLED setting value matches the secure property value
@@ -670,6 +679,15 @@ class ServerThread extends Thread {
reportWtf("making Package Manager Service ready", e);
}
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE);
+ filter.addAction(Intent.ACTION_APP_LAUNCH_FAILURE_RESET);
+ filter.addAction(Intent.ACTION_PACKAGE_ADDED);
+ filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+ filter.addCategory(Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE);
+ filter.addDataScheme("package");
+ context.registerReceiver(new AppsLaunchFailureReceiver(), filter);
+
// These are needed to propagate to the runnable below.
final Context contextF = context;
final BatteryService batteryF = battery;
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index cffb391..2d06e8c 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006-2008 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -78,6 +79,7 @@ import android.content.pm.ServiceInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
+import android.content.res.CustomTheme;
import android.graphics.Bitmap;
import android.net.Proxy;
import android.net.ProxyProperties;
@@ -147,6 +149,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
+import dalvik.system.Zygote;
public final class ActivityManagerService extends ActivityManagerNative
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
@@ -13462,6 +13465,11 @@ public final class ActivityManagerService extends ActivityManagerNative
values.userSetLocale);
}
+ if (values.customTheme != null) {
+ saveThemeResourceLocked(values.customTheme,
+ !values.customTheme.equals(mConfiguration.customTheme));
+ }
+
mConfigurationSeq++;
if (mConfigurationSeq <= 0) {
mConfigurationSeq = 1;
@@ -13554,6 +13562,13 @@ public final class ActivityManagerService extends ActivityManagerNative
}
}
+ private void saveThemeResourceLocked(CustomTheme t, boolean isDiff){
+ if(isDiff){
+ SystemProperties.set(Configuration.THEME_ID_PERSISTENCE_PROPERTY, t.getThemeId());
+ SystemProperties.set(Configuration.THEME_PACKAGE_NAME_PERSISTENCE_PROPERTY, t.getThemePackageName());
+ }
+ }
+
// =========================================================
// LIFETIME MANAGEMENT
// =========================================================
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 0eeb377..8719e8e 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2006 The Android Open Source Project
+ * This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -22,6 +23,7 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
import static libcore.io.OsConstants.S_ISLNK;
+import com.android.internal.app.IAssetRedirectionManager;
import com.android.internal.app.IMediaContainerService;
import com.android.internal.app.ResolverActivity;
import com.android.internal.content.NativeLibraryHelper;
@@ -183,6 +185,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// package apks to install directory.
private static final String INSTALL_PACKAGE_SUFFIX = "-";
+ private static final int THEME_MAMANER_GUID = 1300;
+
static final int SCAN_MONITOR = 1<<0;
static final int SCAN_NO_DEX = 1<<1;
static final int SCAN_FORCE_DEX = 1<<2;
@@ -374,6 +378,8 @@ public class PackageManagerService extends IPackageManager.Stub {
ComponentName mResolveComponentName;
PackageParser.Package mPlatformPackage;
+ IAssetRedirectionManager mAssetRedirectionManager;
+
// Set of pending broadcasts for aggregating enable/disable of components.
final HashMap<String, ArrayList<String>> mPendingBroadcasts
= new HashMap<String, ArrayList<String>>();
@@ -663,22 +669,26 @@ public class PackageManagerService extends IPackageManager.Stub {
PackageInstalledInfo res = data.res;
if (res.returnCode == PackageManager.INSTALL_SUCCEEDED) {
- res.removedInfo.sendBroadcast(false, true);
+ res.removedInfo.sendBroadcast(false, true, false);
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, res.uid);
final boolean update = res.removedInfo.removedPackage != null;
if (update) {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
+ String category = null;
+ if(res.pkg.mIsThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED,
- res.pkg.applicationInfo.packageName,
+ res.pkg.applicationInfo.packageName, category,
extras, null, null);
if (update) {
sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED,
- res.pkg.applicationInfo.packageName,
+ res.pkg.applicationInfo.packageName, category,
extras, null, null);
sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED,
- null, null,
+ null, null, null,
res.pkg.applicationInfo.packageName, null);
}
if (res.removedInfo.args != null) {
@@ -881,6 +891,9 @@ public class PackageManagerService extends IPackageManager.Stub {
MULTIPLE_APPLICATION_UIDS
? LOG_UID : FIRST_APPLICATION_UID,
ApplicationInfo.FLAG_SYSTEM);
+ mSettings.addSharedUserLPw("com.tmobile.thememanager",
+ THEME_MAMANER_GUID,
+ ApplicationInfo.FLAG_SYSTEM);
mSettings.addSharedUserLPw("android.uid.nfc",
MULTIPLE_APPLICATION_UIDS
? NFC_UID : FIRST_APPLICATION_UID,
@@ -2586,6 +2599,20 @@ public class PackageManagerService extends IPackageManager.Stub {
return list;
}
+ public List<PackageInfo> getInstalledThemePackages() {
+ // Returns a list of theme APKs.
+ ArrayList<PackageInfo> finalList = new ArrayList<PackageInfo>();
+ List<PackageInfo> installedPackagesList = mContext.getPackageManager().getInstalledPackages(0);
+ Iterator<PackageInfo> i = installedPackagesList.iterator();
+ while (i.hasNext()) {
+ final PackageInfo pi = i.next();
+ if (pi != null && pi.isThemeApk) {
+ finalList.add(pi);
+ }
+ }
+ return finalList;
+ }
+
public ParceledListSlice<ApplicationInfo> getInstalledApplications(int flags,
String lastRead) {
final ParceledListSlice<ApplicationInfo> list = new ParceledListSlice<ApplicationInfo>();
@@ -3952,6 +3979,32 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ // NOTE: this method can return null if the SystemServer is still
+ // initializing
+ public IAssetRedirectionManager getAssetRedirectionManager() {
+ if (mAssetRedirectionManager != null) {
+ return mAssetRedirectionManager;
+ }
+ IBinder b = ServiceManager.getService("assetredirection");
+ mAssetRedirectionManager = IAssetRedirectionManager.Stub.asInterface(b);
+ return mAssetRedirectionManager;
+ }
+
+ private void cleanAssetRedirections(PackageParser.Package pkg) {
+ IAssetRedirectionManager rm = getAssetRedirectionManager();
+ if (rm == null) {
+ return;
+ }
+ try {
+ if (pkg.mIsThemeApk) {
+ rm.clearRedirectionMapsByTheme(pkg.packageName, null);
+ } else {
+ rm.clearPackageRedirectionMap(pkg.packageName);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
void removePackageLI(PackageParser.Package pkg, boolean chatty) {
if (DEBUG_INSTALL) {
if (chatty)
@@ -3960,6 +4013,8 @@ public class PackageManagerService extends IPackageManager.Stub {
// writer
synchronized (mPackages) {
+ cleanAssetRedirections(pkg);
+
clearPackagePreferredActivitiesLPw(pkg.packageName);
mPackages.remove(pkg.applicationInfo.packageName);
@@ -4742,7 +4797,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
};
- static final void sendPackageBroadcast(String action, String pkg,
+ static final void sendPackageBroadcast(String action, String pkg, String intentCategory,
Bundle extras, String targetPkg, IIntentReceiver finishedReceiver) {
IActivityManager am = ActivityManagerNative.getDefault();
if (am != null) {
@@ -4756,6 +4811,9 @@ public class PackageManagerService extends IPackageManager.Stub {
intent.setPackage(targetPkg);
}
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+ if (intentCategory != null) {
+ intent.addCategory(intentCategory);
+ }
am.broadcastIntent(null, intent, null, finishedReceiver,
0, null, null, null, finishedReceiver != null, false);
} catch (RemoteException ex) {
@@ -4826,6 +4884,7 @@ public class PackageManagerService extends IPackageManager.Stub {
int removedUid = -1;
String addedPackage = null;
int addedUid = -1;
+ String category = null;
// TODO post a message to the handler to obtain serial ordering
synchronized (mInstallLock) {
@@ -4857,6 +4916,9 @@ public class PackageManagerService extends IPackageManager.Stub {
}
if ((event&REMOVE_EVENTS) != 0) {
if (p != null) {
+ if (p.mIsThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
removePackageLI(p, true);
removedPackage = p.applicationInfo.packageName;
removedUid = p.applicationInfo.uid;
@@ -4887,6 +4949,9 @@ public class PackageManagerService extends IPackageManager.Stub {
addedUid = p.applicationInfo.uid;
}
}
+ if (p != null && p.mIsThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
}
// reader
@@ -4899,13 +4964,13 @@ public class PackageManagerService extends IPackageManager.Stub {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, removedUid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category,
extras, null, null);
}
if (addedPackage != null) {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, addedUid);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage, category,
extras, null, null);
}
}
@@ -6591,6 +6656,23 @@ public class PackageManagerService extends IPackageManager.Stub {
} else {
Log.d(TAG, "New package installed in " + newPackage.mPath);
}
+ cleanAssetRedirections(newPackage);
+
+ if (newPackage.mIsThemeApk) {
+ /* DBS-TODO
+ boolean isThemePackageDrmProtected = false;
+ int N = newPackage.mThemeInfos.size();
+ for (int i = 0; i < N; i++) {
+ if (newPackage.mThemeInfos.get(i).isDrmProtected) {
+ isThemePackageDrmProtected = true;
+ break;
+ }
+ }
+ if (isThemePackageDrmProtected) {
+ splitThemePackage(newPackage.mPath);
+ }
+ */
+ }
synchronized (mPackages) {
updatePermissionsLPw(newPackage.packageName, newPackage,
newPackage.permissions.size() > 0, true, false);
@@ -6605,6 +6687,66 @@ public class PackageManagerService extends IPackageManager.Stub {
}
}
+ private void deleteLockedZipFileIfExists(String originalPackagePath) {
+ String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath);
+ File zipFile = new File(lockedZipFilePath);
+ if (zipFile.exists() && zipFile.isFile()) {
+ if (!zipFile.delete()) {
+ Log.w(TAG, "Couldn't delete locked zip file: " + originalPackagePath);
+ }
+ }
+ }
+
+ private void splitThemePackage(File originalFile) {
+ final String originalPackagePath = originalFile.getPath();
+ final String lockedZipFilePath = PackageParser.getLockedZipFilePath(originalPackagePath);
+
+ try {
+ final List<String> drmProtectedEntries = new ArrayList<String>();
+ final ZipFile privateZip = new ZipFile(originalFile.getPath());
+
+ final Enumeration<? extends ZipEntry> privateZipEntries = privateZip.entries();
+ while (privateZipEntries.hasMoreElements()) {
+ final ZipEntry zipEntry = privateZipEntries.nextElement();
+ final String zipEntryName = zipEntry.getName();
+ if (zipEntryName.startsWith("assets/") && zipEntryName.contains("/locked/")) {
+ drmProtectedEntries.add(zipEntryName);
+ }
+ }
+ privateZip.close();
+
+ String [] args = new String[0];
+ args = drmProtectedEntries.toArray(args);
+ int code = mContext.getAssets().splitDrmProtectedThemePackage(
+ originalPackagePath,
+ lockedZipFilePath,
+ args);
+ if (code != 0) {
+ Log.e("PackageManagerService",
+ "splitDrmProtectedThemePackage returned = " + code);
+ }
+ code = FileUtils.setPermissions(
+ lockedZipFilePath,
+ 0640,
+ -1,
+ THEME_MAMANER_GUID);
+ if (code != 0) {
+ Log.e("PackageManagerService",
+ "Set permissions for " + lockedZipFilePath + " returned = " + code);
+ }
+ code = FileUtils.setPermissions(
+ originalPackagePath,
+ 0644,
+ -1, -1);
+ if (code != 0) {
+ Log.e("PackageManagerService",
+ "Set permissions for " + originalPackagePath + " returned = " + code);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failure to generate new zip files for theme");
+ }
+ }
+
private void installPackageLI(InstallArgs args,
boolean newInstall, PackageInstalledInfo res) {
int pFlags = args.flags;
@@ -6915,7 +7057,18 @@ public class PackageManagerService extends IPackageManager.Stub {
}
} catch (RemoteException e) {
}
-
+
+ synchronized (mPackages) {
+ PackageParser.Package p = mPackages.get(packageName);
+ if (p != null) {
+ info.isThemeApk = p.mIsThemeApk;
+ if (info.isThemeApk && deleteCodeAndResources &&
+ !info.isRemovedPackageSystemUpdate && sendBroadCast) {
+ deleteLockedZipFileIfExists(p.mPath);
+ }
+ }
+ }
+
synchronized (mInstallLock) {
res = deletePackageLI(packageName, deleteCodeAndResources,
flags | REMOVE_CHATTY, info, true);
@@ -6923,7 +7076,7 @@ public class PackageManagerService extends IPackageManager.Stub {
if (res && sendBroadCast) {
boolean systemUpdate = info.isRemovedPackageSystemUpdate;
- info.sendBroadcast(deleteCodeAndResources, systemUpdate);
+ info.sendBroadcast(deleteCodeAndResources, systemUpdate, true);
// If the removed package was a system update, the old system packaged
// was re-enabled; we need to broadcast this information
@@ -6932,11 +7085,16 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putInt(Intent.EXTRA_UID, info.removedUid >= 0 ? info.removedUid : info.uid);
extras.putBoolean(Intent.EXTRA_REPLACING, true);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName,
+ String category = null;
+ if (info.isThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
+
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, category,
extras, null, null);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REPLACED, packageName, category,
extras, null, null);
- sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null,
+ sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null,
null, packageName, null);
}
}
@@ -6960,8 +7118,10 @@ public class PackageManagerService extends IPackageManager.Stub {
boolean isRemovedPackageSystemUpdate = false;
// Clean up resources deleted packages.
InstallArgs args = null;
+ boolean isThemeApk = false;
- void sendBroadcast(boolean fullRemove, boolean replacing) {
+ void sendBroadcast(boolean fullRemove, boolean replacing,
+ boolean deleteLockedZipFileIfExists) {
Bundle extras = new Bundle(1);
extras.putInt(Intent.EXTRA_UID, removedUid >= 0 ? removedUid : uid);
extras.putBoolean(Intent.EXTRA_DATA_REMOVED, fullRemove);
@@ -6969,15 +7129,19 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putBoolean(Intent.EXTRA_REPLACING, true);
}
if (removedPackage != null) {
- sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
+ String category = null;
+ if (isThemeApk) {
+ category = Intent.CATEGORY_THEME_PACKAGE_INSTALLED_STATE_CHANGE;
+ }
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage, category,
extras, null, null);
if (fullRemove && !replacing) {
- sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage,
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_FULLY_REMOVED, removedPackage, category,
extras, null, null);
}
}
if (removedUid >= 0) {
- sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, extras, null, null);
+ sendPackageBroadcast(Intent.ACTION_UID_REMOVED, null, null, extras, null, null);
}
}
}
@@ -7684,7 +7848,7 @@ public class PackageManagerService extends IPackageManager.Stub {
extras.putStringArray(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST, nameList);
extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, killFlag);
extras.putInt(Intent.EXTRA_UID, packageUid);
- sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, null, null);
+ sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, null, extras, null, null);
}
public void setPackageStoppedState(String packageName, boolean stopped) {
@@ -8240,7 +8404,7 @@ public class PackageManagerService extends IPackageManager.Stub {
}
String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE
: Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE;
- sendPackageBroadcast(action, null, extras, null, finishedReceiver);
+ sendPackageBroadcast(action, null, null, extras, null, finishedReceiver);
}
}
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 36442a0..46f10d2 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -2010,7 +2010,7 @@ final class Settings {
if (pkgSetting.notLaunched) {
if (pkgSetting.installerPackageName != null) {
PackageManagerService.sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH,
- pkgSetting.name, null,
+ pkgSetting.name, null, null,
pkgSetting.installerPackageName, null);
}
pkgSetting.notLaunched = false;
@@ -2261,4 +2261,4 @@ final class Settings {
pw.println("Settings parse messages:");
pw.print(mReadMessages.toString());
}
-} \ No newline at end of file
+}
diff --git a/test-runner/src/android/test/mock/MockPackageManager.java b/test-runner/src/android/test/mock/MockPackageManager.java
index 58680ea..567aa21 100644
--- a/test-runner/src/android/test/mock/MockPackageManager.java
+++ b/test-runner/src/android/test/mock/MockPackageManager.java
@@ -554,6 +554,14 @@ public class MockPackageManager extends PackageManager {
}
/**
+ * @hide - to match hiding in superclass
+ */
+ @Override
+ public List<PackageInfo> getInstalledThemePackages() {
+ throw new UnsupportedOperationException();
+ }
+
+ /**
* @hide
*/
@Override
diff --git a/tools/aapt/Bundle.h b/tools/aapt/Bundle.h
index 2d1060b..7ea277a 100644
--- a/tools/aapt/Bundle.h
+++ b/tools/aapt/Bundle.h
@@ -1,5 +1,6 @@
//
// Copyright 2006 The Android Open Source Project
+// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
//
// State bundle. Used to pass around stuff like command-line args.
//
@@ -36,7 +37,7 @@ public:
Bundle(void)
: mCmd(kCommandUnknown), mVerbose(false), mAndroidList(false),
mForce(false), mGrayscaleTolerance(0), mMakePackageDirs(false),
- mUpdate(false), mExtending(false),
+ mUpdate(false), mExtending(false), mExtendedPackageId(0),
mRequireLocalization(false), mPseudolocalize(false),
mWantUTF16(false), mValues(false),
mCompressionMethod(0), mOutputAPKFile(NULL),
@@ -78,6 +79,8 @@ public:
void setUpdate(bool val) { mUpdate = val; }
bool getExtending(void) const { return mExtending; }
void setExtending(bool val) { mExtending = val; }
+ int getExtendedPackageId(void) const { return mExtendedPackageId; }
+ void setExtendedPackageId(int val) { mExtendedPackageId = val; }
bool getRequireLocalization(void) const { return mRequireLocalization; }
void setRequireLocalization(bool val) { mRequireLocalization = val; }
bool getPseudolocalize(void) const { return mPseudolocalize; }
@@ -226,6 +229,7 @@ private:
bool mMakePackageDirs;
bool mUpdate;
bool mExtending;
+ int mExtendedPackageId;
bool mRequireLocalization;
bool mPseudolocalize;
bool mWantUTF16;
diff --git a/tools/aapt/Main.cpp b/tools/aapt/Main.cpp
index 50c828d..77d5dd6 100644
--- a/tools/aapt/Main.cpp
+++ b/tools/aapt/Main.cpp
@@ -1,5 +1,6 @@
//
// Copyright 2006 The Android Open Source Project
+// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
//
// Android Asset Packaging Tool main entry point.
//
@@ -14,6 +15,7 @@
#include <stdlib.h>
#include <getopt.h>
#include <assert.h>
+#include <ctype.h>
using namespace android;
@@ -55,7 +57,7 @@ void usage(void)
" xmltree Print the compiled xmls in the given assets.\n"
" xmlstrings Print the strings of the given compiled xml assets.\n\n", gProgName);
fprintf(stderr,
- " %s p[ackage] [-d][-f][-m][-u][-v][-x][-z][-M AndroidManifest.xml] \\\n"
+ " %s p[ackage] [-d][-f][-m][-u][-v][-x[ extending-resource-id]][-z][-M AndroidManifest.xml] \\\n"
" [-0 extension [-0 extension ...]] [-g tolerance] [-j jarfile] \\\n"
" [--debug-mode] [--min-sdk-version VAL] [--target-sdk-version VAL] \\\n"
" [--app-version VAL] [--app-version-name TEXT] [--custom-package VAL] \\\n"
@@ -116,7 +118,7 @@ void usage(void)
#endif
" -u update existing packages (add new, replace older, remove deleted files)\n"
" -v verbose output\n"
- " -x create extending (non-application) resource IDs\n"
+ " -x either create or assign (if specified) extending (non-application) resource IDs\n"
" -z require localization of resource attributes marked with\n"
" localization=\"suggested\"\n"
" -A additional directory in which to find raw asset files\n"
@@ -305,6 +307,14 @@ int main(int argc, char* const argv[])
break;
case 'x':
bundle.setExtending(true);
+ argc--;
+ argv++;
+ if (!argc || !isdigit(argv[0][0])) {
+ argc++;
+ argv--;
+ } else {
+ bundle.setExtendedPackageId(atoi(argv[0]));
+ }
break;
case 'z':
bundle.setRequireLocalization(true);
diff --git a/tools/aapt/ResourceTable.cpp b/tools/aapt/ResourceTable.cpp
index fdb39ca..df117db 100644
--- a/tools/aapt/ResourceTable.cpp
+++ b/tools/aapt/ResourceTable.cpp
@@ -1,5 +1,6 @@
//
// Copyright 2006 The Android Open Source Project
+// This code has been modified. Portions copyright (C) 2010, T-Mobile USA, Inc.
//
// Build resource files from raw assets.
//
@@ -3620,7 +3621,16 @@ sp<ResourceTable::Package> ResourceTable::getPackage(const String16& package)
mHaveAppPackage = true;
p = new Package(package, 127);
} else {
- p = new Package(package, mNextPackageId);
+ int extendedPackageId = mBundle->getExtendedPackageId();
+ if (extendedPackageId != 0) {
+ if ((uint32_t)extendedPackageId < mNextPackageId) {
+ fprintf(stderr, "Package ID %d already in use!\n", mNextPackageId);
+ return NULL;
+ }
+ p = new Package(package, extendedPackageId);
+ } else {
+ p = new Package(package, mNextPackageId);
+ }
}
//printf("*** NEW PACKAGE: \"%s\" id=%d\n",
// String8(package).string(), p->getAssignedId());