diff options
138 files changed, 2930 insertions, 933 deletions
diff --git a/api/current.xml b/api/current.xml index 2128e5b4acaa..962e8e6336da 100644 --- a/api/current.xml +++ b/api/current.xml @@ -27473,6 +27473,17 @@ visibility="public" > </method> +<method name="getApplicationInfo" + return="android.content.pm.ApplicationInfo" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getAssets" return="android.content.res.AssetManager" abstract="true" @@ -28825,6 +28836,17 @@ visibility="public" > </method> +<method name="getApplicationInfo" + return="android.content.pm.ApplicationInfo" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getAssets" return="android.content.res.AssetManager" abstract="false" @@ -30186,7 +30208,7 @@ synchronized="false" static="true" final="false" - deprecated="not deprecated" + deprecated="deprecated" visibility="public" > <parameter name="uri" type="java.lang.String"> @@ -30237,6 +30259,17 @@ <parameter name="defaultValue" type="long"> </parameter> </method> +<method name="getPackage" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getParcelableArrayExtra" return="android.os.Parcelable[]" abstract="false" @@ -30436,6 +30469,23 @@ <exception name="XmlPullParserException" type="org.xmlpull.v1.XmlPullParserException"> </exception> </method> +<method name="parseUri" + return="android.content.Intent" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="uri" type="java.lang.String"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +<exception name="URISyntaxException" type="java.net.URISyntaxException"> +</exception> +</method> <method name="putExtra" return="android.content.Intent" abstract="false" @@ -31109,6 +31159,19 @@ <parameter name="flags" type="int"> </parameter> </method> +<method name="setPackage" + return="android.content.Intent" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="packageName" type="java.lang.String"> +</parameter> +</method> <method name="setType" return="android.content.Intent" abstract="false" @@ -31129,9 +31192,22 @@ synchronized="false" static="false" final="false" + deprecated="deprecated" + visibility="public" +> +</method> +<method name="toUri" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" deprecated="not deprecated" visibility="public" > +<parameter name="flags" type="int"> +</parameter> </method> <method name="writeToParcel" return="void" @@ -31775,6 +31851,17 @@ visibility="public" > </field> +<field name="ACTION_POWER_USAGE_SUMMARY" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.POWER_USAGE_SUMMARY"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_PROVIDER_CHANGED" type="java.lang.String" transient="false" @@ -31874,6 +31961,17 @@ visibility="public" > </field> +<field name="ACTION_SEND_MULTIPLE" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.intent.action.SEND_MULTIPLE"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="ACTION_SET_WALLPAPER" type="java.lang.String" transient="false" @@ -32500,6 +32598,17 @@ visibility="public" > </field> +<field name="FILL_IN_PACKAGE" + type="int" + transient="false" + volatile="false" + value="16" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_ACTIVITY_BROUGHT_TO_FRONT" type="int" transient="false" @@ -32709,6 +32818,17 @@ visibility="public" > </field> +<field name="URI_INTENT_SCHEME" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> </class> <class name="Intent.FilterComparison" extends="java.lang.Object" @@ -35109,6 +35229,17 @@ visibility="public" > </field> +<field name="FLAG_SUPPORTS_LARGE_SCREENS" + type="int" + transient="false" + volatile="false" + value="512" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_SYSTEM" type="int" transient="false" @@ -35182,16 +35313,6 @@ visibility="public" > </field> -<field name="expandable" - type="boolean" - transient="false" - volatile="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="flags" type="int" transient="false" @@ -36910,23 +37031,6 @@ <parameter name="flags" type="int"> </parameter> </method> -<method name="resolveActivity" - return="android.content.pm.ResolveInfo" - abstract="true" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="intent" type="android.content.Intent"> -</parameter> -<parameter name="flags" type="int"> -</parameter> -<parameter name="packageName" type="java.lang.String"> -</parameter> -</method> <method name="resolveContentProvider" return="android.content.pm.ProviderInfo" abstract="true" @@ -37068,17 +37172,6 @@ visibility="public" > </field> -<field name="GET_EXPANDABLE" - type="int" - transient="false" - volatile="false" - value="131072" - static="true" - final="true" - deprecated="not deprecated" - visibility="public" -> -</field> <field name="GET_GIDS" type="int" transient="false" @@ -114328,6 +114421,17 @@ visibility="public" > </method> +<method name="getApplicationInfo" + return="android.content.pm.ApplicationInfo" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> <method name="getAssets" return="android.content.res.AssetManager" abstract="false" @@ -115702,23 +115806,6 @@ </parameter> <parameter name="flags" type="int"> </parameter> -<parameter name="packageName" type="java.lang.String"> -</parameter> -</method> -<method name="resolveActivity" - return="android.content.pm.ResolveInfo" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="intent" type="android.content.Intent"> -</parameter> -<parameter name="flags" type="int"> -</parameter> </method> <method name="resolveContentProvider" return="android.content.pm.ProviderInfo" diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 3af80fa29c96..841e3df784be 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -80,6 +80,14 @@ public final class Bmgr { doRestore(); return; } + + if ("transport".equals(op)) { + doTransport(); + return; + } + + System.err.println("Unknown command"); + showUsage(); } private void doRun() { @@ -113,6 +121,19 @@ public final class Bmgr { } } + private void doTransport() { + try { + int which = Integer.parseInt(nextArg()); + int old = mBmgr.selectBackupTransport(which); + System.out.println("Selected transport " + which + " (formerly " + old + ")"); + } catch (NumberFormatException e) { + showUsage(); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + private void doList() { String arg = nextArg(); // sets, transports, packages set# if ("transports".equals(arg)) { diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 477badbdf2d4..6873379e8ae0 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -187,7 +187,7 @@ public final class ActivityThread { try { appInfo = getPackageManager().getApplicationInfo( pkgInfo.getPackageName(), - PackageManager.GET_SUPPORTS_DENSITIES | PackageManager.GET_EXPANDABLE); + PackageManager.GET_SUPPORTS_DENSITIES); } catch (RemoteException e) { throw new AssertionError(e); } @@ -287,6 +287,10 @@ public final class ActivityThread { return mPackageName; } + public ApplicationInfo getApplicationInfo() { + return mApplicationInfo; + } + public boolean isSecurityViolation() { return mSecurityViolation; } diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index fa3d5c2ad8a6..658baee55bbd 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -283,6 +283,14 @@ class ApplicationContext extends Context { } @Override + public ApplicationInfo getApplicationInfo() { + if (mPackageInfo != null) { + return mPackageInfo.getApplicationInfo(); + } + throw new RuntimeException("Not supported in system context"); + } + + @Override public String getPackageResourcePath() { if (mPackageInfo != null) { return mPackageInfo.getResDir(); @@ -1519,14 +1527,16 @@ class ApplicationContext extends Context { // overall package (such as if it has multiple launcher entries). Intent intentToResolve = new Intent(Intent.ACTION_MAIN); intentToResolve.addCategory(Intent.CATEGORY_INFO); - ResolveInfo resolveInfo = resolveActivity(intentToResolve, 0, packageName); + intentToResolve.setPackage(packageName); + ResolveInfo resolveInfo = resolveActivity(intentToResolve, 0); // Otherwise, try to find a main launcher activity. if (resolveInfo == null) { // reuse the intent instance intentToResolve.removeCategory(Intent.CATEGORY_INFO); intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER); - resolveInfo = resolveActivity(intentToResolve, 0, packageName); + intentToResolve.setPackage(packageName); + resolveInfo = resolveActivity(intentToResolve, 0); } if (resolveInfo == null) { return null; @@ -1774,19 +1784,6 @@ class ApplicationContext extends Context { } @Override - public ResolveInfo resolveActivity(Intent intent, int flags, String packageName) { - try { - return mPM.resolveIntentForPackage( - intent, - intent.resolveTypeIfNeeded(mContext.getContentResolver()), - flags, - packageName); - } catch (RemoteException e) { - throw new RuntimeException("Package manager has died", e); - } - } - - @Override public List<ResolveInfo> queryIntentActivities(Intent intent, int flags) { try { @@ -2361,22 +2358,13 @@ class ApplicationContext extends Context { } @Override - public void freeStorage(long freeStorageSize, PendingIntent pi) { + public void freeStorage(long freeStorageSize, IntentSender pi) { try { mPM.freeStorage(freeStorageSize, pi); } catch (RemoteException e) { // Should never happen! } } - - @Override - public void freeStorageWithIntent(long freeStorageSize, IntentSender pi) { - try { - mPM.freeStorageWithIntent(freeStorageSize, pi); - } catch (RemoteException e) { - // Should never happen! - } - } @Override public void getPackageSizeInfo(String packageName, diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index b6c8385c10d6..3a3a983cb4ba 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -134,9 +134,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private SuggestionsAdapter mSuggestionsAdapter; // Whether to rewrite queries when selecting suggestions - // TODO: This is disabled because of problems with persistent selections - // causing non-user-initiated rewrites. - private static final boolean REWRITE_QUERIES = false; + private static final boolean REWRITE_QUERIES = true; // The query entered by the user. This is not changed when selecting a suggestion // that modifies the contents of the text field. But if the user then edits diff --git a/core/java/android/backup/BackupDataOutput.java b/core/java/android/backup/BackupDataOutput.java index a6d5becd3e82..05e667ed4764 100644 --- a/core/java/android/backup/BackupDataOutput.java +++ b/core/java/android/backup/BackupDataOutput.java @@ -36,6 +36,7 @@ public class BackupDataOutput { } } + // A dataSize of -1 indicates that the record under this key should be deleted public int writeEntityHeader(String key, int dataSize) throws IOException { int result = writeEntityHeader_native(mBackupWriter, key, dataSize); if (result >= 0) { diff --git a/core/java/android/backup/RestoreHelper.java b/core/java/android/backup/RestoreHelper.java index ac7d8ee8a790..e47869ce4ca3 100644 --- a/core/java/android/backup/RestoreHelper.java +++ b/core/java/android/backup/RestoreHelper.java @@ -16,6 +16,8 @@ package android.backup; +import android.os.ParcelFileDescriptor; + import java.io.InputStream; /** @hide */ @@ -27,5 +29,6 @@ public interface RestoreHelper { * <code>dataSize</code> bytes from <code>data</code>. */ public void restoreEntity(BackupDataInputStream data); + public void writeSnapshot(ParcelFileDescriptor fd); } diff --git a/core/java/android/backup/RestoreHelperBase.java b/core/java/android/backup/RestoreHelperBase.java index 894c9afee277..93a8fefcdd16 100644 --- a/core/java/android/backup/RestoreHelperBase.java +++ b/core/java/android/backup/RestoreHelperBase.java @@ -17,69 +17,66 @@ package android.backup; import android.content.Context; +import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.InputStream; import java.io.File; +import java.io.FileDescriptor; import java.io.FileOutputStream; -import java.io.IOException; class RestoreHelperBase { private static final String TAG = "RestoreHelperBase"; - private static final int BUF_SIZE = 8 * 1024; + int mPtr; Context mContext; - byte[] mBuf = new byte[BUF_SIZE]; boolean mExceptionLogged; RestoreHelperBase(Context context) { + mPtr = ctor(); mContext = context; } - void writeFile(File f, InputStream in) { - boolean success = false; - FileOutputStream out = null; + protected void finalize() throws Throwable { try { - // Create the enclosing directory. - File parent = f.getParentFile(); - parent.mkdirs(); + dtor(mPtr); + } finally { + super.finalize(); + } + } - // Copy the file. - int sum = 0; - out = new FileOutputStream(f); - byte[] buf = mBuf; - int amt; - while ((amt = in.read(buf)) > 0) { - out.write(buf, 0, amt); - sum += amt; - } + void writeFile(File f, InputStream in) { + if (!(in instanceof BackupDataInputStream)) { + throw new IllegalStateException("input stream must be a BackupDataInputStream"); + } + int result = -1; - // TODO: Set the permissions of the file. + // Create the enclosing directory. + File parent = f.getParentFile(); + parent.mkdirs(); - // We're done - success = true; - out = null; - } catch (IOException ex) { - // Bail on this entity. Only log one exception per helper object. + result = writeFile_native(mPtr, f.getAbsolutePath(), + ((BackupDataInputStream)in).mData.mBackupReader); + if (result != 0) { + // Bail on this entity. Only log one failure per helper object. if (!mExceptionLogged) { Log.e(TAG, "Failed restoring file '" + f + "' for app '" - + mContext.getPackageName() + '\'', ex); + + mContext.getPackageName() + "\' result=0x" + + Integer.toHexString(result)); mExceptionLogged = true; } } - finally { - if (out != null) { - try { - out.close(); - } catch (IOException ex) { - } - } - if (!success) { - // Something didn't work out, delete the file - f.delete(); - } - } } + + public void writeSnapshot(ParcelFileDescriptor fd) { + int result = writeSnapshot_native(mPtr, fd.getFileDescriptor()); + // TODO: Do something with the error. + } + + private static native int ctor(); + private static native void dtor(int ptr); + private static native int writeFile_native(int ptr, String filename, int backupReader); + private static native int writeSnapshot_native(int ptr, FileDescriptor fd); } diff --git a/core/java/android/backup/RestoreHelperDispatcher.java b/core/java/android/backup/RestoreHelperDispatcher.java index 5928914ee217..48617757cb47 100644 --- a/core/java/android/backup/RestoreHelperDispatcher.java +++ b/core/java/android/backup/RestoreHelperDispatcher.java @@ -16,10 +16,12 @@ package android.backup; +import android.os.ParcelFileDescriptor; import android.util.Log; import java.io.IOException; import java.util.HashMap; +import java.util.Map; /** @hide */ public class RestoreHelperDispatcher { @@ -31,7 +33,7 @@ public class RestoreHelperDispatcher { mHelpers.put(keyPrefix, helper); } - public void dispatch(BackupDataInput input) throws IOException { + public void dispatch(BackupDataInput input, ParcelFileDescriptor newState) throws IOException { boolean alreadyComplained = false; BackupDataInputStream stream = new BackupDataInputStream(input); @@ -60,5 +62,17 @@ public class RestoreHelperDispatcher { } input.skipEntityData(); // In case they didn't consume the data. } + + if (mHelpers.size() > 1) { + throw new RuntimeException("RestoreHelperDispatcher won't get your your" + + " data in the right order yet."); + } + + // Write out the state files + for (RestoreHelper helper: mHelpers.values()) { + // TODO: Write a header for the state + helper.writeSnapshot(newState); + } } } + diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java index e1984353e88c..fe1e09af984a 100644 --- a/core/java/android/bluetooth/BluetoothHeadset.java +++ b/core/java/android/bluetooth/BluetoothHeadset.java @@ -332,6 +332,31 @@ public class BluetoothHeadset { } /** + * Get battery usage hint for Bluetooth Headset service. + * This is a monotonically increasing integer. Wraps to 0 at + * Integer.MAX_INT, and at boot. + * Current implementation returns the number of AT commands handled since + * boot. This is a good indicator for spammy headset/handsfree units that + * can keep the device awake by polling for cellular status updates. As a + * rule of thumb, each AT command prevents the CPU from sleeping for 500 ms + * @return monotonically increasing battery usage hint, or a negative error + * code on error + * @hide + */ + public int getBatteryUsageHint() { + if (DBG) log("getBatteryUsageHint()"); + if (mService != null) { + try { + return mService.getBatteryUsageHint(); + } catch (RemoteException e) {Log.e(TAG, e.toString());} + } else { + Log.w(TAG, "Proxy not attached to service"); + if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable())); + } + return -1; + } + + /** * Check class bits for possible HSP or HFP support. * This is a simple heuristic that tries to guess if a device with the * given class bits might support HSP or HFP. It is not accurate for all diff --git a/core/java/android/bluetooth/HeadsetBase.java b/core/java/android/bluetooth/HeadsetBase.java index f31e7a2c65ec..f987ffdd440a 100644 --- a/core/java/android/bluetooth/HeadsetBase.java +++ b/core/java/android/bluetooth/HeadsetBase.java @@ -40,6 +40,8 @@ public class HeadsetBase { public static final int DIRECTION_INCOMING = 1; public static final int DIRECTION_OUTGOING = 2; + private static int sAtInputCount = 0; /* TODO: Consider not using a static variable */ + private final BluetoothDevice mBluetooth; private final String mAddress; private final int mRfcommChannel; @@ -109,6 +111,14 @@ public class HeadsetBase { acquireWakeLock(); long timestamp; + synchronized(HeadsetBase.class) { + if (sAtInputCount == Integer.MAX_VALUE) { + sAtInputCount = 0; + } else { + sAtInputCount++; + } + } + if (DBG) timestamp = System.currentTimeMillis(); AtCommandResult result = mAtParser.process(input); if (DBG) Log.d(TAG, "Processing " + input + " took " + @@ -279,7 +289,11 @@ public class HeadsetBase { } } - private void log(String msg) { + public static int getAtInputCount() { + return sAtInputCount; + } + + private static void log(String msg) { Log.d(TAG, msg); } } diff --git a/core/java/android/bluetooth/IBluetoothHeadset.aidl b/core/java/android/bluetooth/IBluetoothHeadset.aidl index 582d4e340ee1..5f42fd6521b7 100644 --- a/core/java/android/bluetooth/IBluetoothHeadset.aidl +++ b/core/java/android/bluetooth/IBluetoothHeadset.aidl @@ -31,4 +31,5 @@ interface IBluetoothHeadset { boolean stopVoiceRecognition(); boolean setPriority(in String address, int priority); int getPriority(in String address); + int getBatteryUsageHint(); } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 925249eb9c1f..b0396f68a55f 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -16,6 +16,7 @@ package android.content; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; @@ -233,6 +234,9 @@ public abstract class Context { /** Return the name of this application's package. */ public abstract String getPackageName(); + /** Return the full application info for this context's package. */ + public abstract ApplicationInfo getApplicationInfo(); + /** * {@hide} * Return the full path to this context's resource files. This is the ZIP files diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 262204e2694b..7513b3bf6471 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -16,6 +16,7 @@ package android.content; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; @@ -120,6 +121,11 @@ public class ContextWrapper extends Context { } @Override + public ApplicationInfo getApplicationInfo() { + return mBase.getApplicationInfo(); + } + + @Override public String getPackageResourcePath() { return mBase.getPackageResourcePath(); } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index d4a78151908e..609fa748cdb3 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -915,6 +915,23 @@ public class Intent implements Parcelable { @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_SEND = "android.intent.action.SEND"; /** + * Activity Action: Deliver multiple data to someone else. + * <p> + * Like ACTION_SEND, except the data is multiple. + * <p> + * Input: {@link #getType} is the MIME type of the data being sent. + * get*ArrayListExtra can have either a {@link #EXTRA_TEXT} or {@link + * #EXTRA_STREAM} field, containing the data to be sent. + * <p> + * Optional standard extras, which may be interpreted by some recipients as + * appropriate, are: {@link #EXTRA_EMAIL}, {@link #EXTRA_CC}, + * {@link #EXTRA_BCC}, {@link #EXTRA_SUBJECT}. + * <p> + * Output: nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SEND_MULTIPLE = "android.intent.action.SEND_MULTIPLE"; + /** * Activity Action: Handle an incoming phone call. * <p>Input: nothing. * <p>Output: nothing. @@ -1059,6 +1076,15 @@ public class Intent implements Parcelable { */ @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_APP_ERROR = "android.intent.action.APP_ERROR"; + + /** + * Activity Action: Show power usage information to the user. + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_POWER_USAGE_SUMMARY = "android.intent.action.POWER_USAGE_SUMMARY"; + // --------------------------------------------------------------------- // --------------------------------------------------------------------- // Standard intent broadcast actions (see action variable). @@ -2040,10 +2066,25 @@ public class Intent implements Parcelable { public static final int FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT = 0x20000000; // --------------------------------------------------------------------- + // --------------------------------------------------------------------- + // toUri() and parseUri() options. + + /** + * Flag for use with {@link #toUri} and {@link #parseUri}: the URI string + * always has the "intent:" scheme. This syntax can be used when you want + * to later disambiguate between URIs that are intended to describe an + * Intent vs. all others that should be treated as raw URIs. When used + * with {@link #parseUri}, any other scheme will result in a generic + * VIEW action for that raw URI. + */ + public static final int URI_INTENT_SCHEME = 1<<0; + + // --------------------------------------------------------------------- private String mAction; private Uri mData; private String mType; + private String mPackage; private ComponentName mComponent; private int mFlags; private HashSet<String> mCategories; @@ -2064,6 +2105,7 @@ public class Intent implements Parcelable { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; + this.mPackage = o.mPackage; this.mComponent = o.mComponent; this.mFlags = o.mFlags; if (o.mCategories != null) { @@ -2083,6 +2125,7 @@ public class Intent implements Parcelable { this.mAction = o.mAction; this.mData = o.mData; this.mType = o.mType; + this.mPackage = o.mPackage; this.mComponent = o.mComponent; if (o.mCategories != null) { this.mCategories = new HashSet<String>(o.mCategories); @@ -2183,23 +2226,50 @@ public class Intent implements Parcelable { } /** + * Call {@link #parseUri} with 0 flags. + * @deprecated Use {@link #parseUri} instead. + */ + @Deprecated + public static Intent getIntent(String uri) throws URISyntaxException { + return parseUri(uri, 0); + } + + /** * Create an intent from a URI. This URI may encode the action, - * category, and other intent fields, if it was returned by toURI(). If - * the Intent was not generate by toURI(), its data will be the entire URI - * and its action will be ACTION_VIEW. + * category, and other intent fields, if it was returned by + * {@link #toUri}.. If the Intent was not generate by toUri(), its data + * will be the entire URI and its action will be ACTION_VIEW. * * <p>The URI given here must not be relative -- that is, it must include * the scheme and full path. * * @param uri The URI to turn into an Intent. + * @param flags Additional processing flags. Either 0 or * * @return Intent The newly created Intent object. * - * @see #toURI + * @throws URISyntaxException Throws URISyntaxError if the basic URI syntax + * it bad (as parsed by the Uri class) or the Intent data within the + * URI is invalid. + * + * @see #toUri */ - public static Intent getIntent(String uri) throws URISyntaxException { + public static Intent parseUri(String uri, int flags) throws URISyntaxException { int i = 0; try { + // Validate intent scheme for if requested. + if ((flags&URI_INTENT_SCHEME) != 0) { + if (!uri.startsWith("intent:")) { + Intent intent = new Intent(ACTION_VIEW); + try { + intent.setData(Uri.parse(uri)); + } catch (IllegalArgumentException e) { + throw new URISyntaxException(uri, e.getMessage()); + } + return intent; + } + } + // simple case i = uri.lastIndexOf("#"); if (i == -1) return new Intent(ACTION_VIEW, Uri.parse(uri)); @@ -2211,16 +2281,15 @@ public class Intent implements Parcelable { Intent intent = new Intent(ACTION_VIEW); // fetch data part, if present - if (i > 0) { - intent.mData = Uri.parse(uri.substring(0, i)); - } + String data = i >= 0 ? uri.substring(0, i) : null; + String scheme = null; i += "#Intent;".length(); // loop over contents of Intent, all name=value; while (!uri.startsWith("end", i)) { int eq = uri.indexOf('=', i); int semi = uri.indexOf(';', eq); - String value = uri.substring(eq + 1, semi); + String value = Uri.decode(uri.substring(eq + 1, semi)); // action if (uri.startsWith("action=", i)) { @@ -2242,15 +2311,24 @@ public class Intent implements Parcelable { intent.mFlags = Integer.decode(value).intValue(); } + // package + else if (uri.startsWith("package=", i)) { + intent.mPackage = value; + } + // component else if (uri.startsWith("component=", i)) { intent.mComponent = ComponentName.unflattenFromString(value); } + // scheme + else if (uri.startsWith("scheme=", i)) { + scheme = value; + } + // extra else { String key = Uri.decode(uri.substring(i + 2, eq)); - value = Uri.decode(value); // create Bundle if it doesn't already exist if (intent.mExtras == null) intent.mExtras = new Bundle(); Bundle b = intent.mExtras; @@ -2271,6 +2349,23 @@ public class Intent implements Parcelable { i = semi + 1; } + if (data != null) { + if (data.startsWith("intent:")) { + data = data.substring(7); + if (scheme != null) { + data = scheme + ':' + data; + } + } + + if (data.length() > 0) { + try { + intent.mData = Uri.parse(data); + } catch (IllegalArgumentException e) { + throw new URISyntaxException(uri, e.getMessage()); + } + } + } + return intent; } catch (IndexOutOfBoundsException e) { @@ -3084,6 +3179,20 @@ public class Intent implements Parcelable { } /** + * Retrieve the application package name this Intent is limited to. When + * resolving an Intent, if non-null this limits the resolution to only + * components in the given application package. + * + * @return The name of the application package for the Intent. + * + * @see #resolveActivity + * @see #setPackage + */ + public String getPackage() { + return mPackage; + } + + /** * Retrieve the concrete component associated with the intent. When receiving * an intent, this is the component that was found to best handle it (that is, * yourself) and will always be non-null; in all other cases it will be @@ -3118,6 +3227,9 @@ public class Intent implements Parcelable { * <p>If {@link #addCategory} has added any categories, the activity must * handle ALL of the categories specified. * + * <p>If {@link #getPackage} is non-NULL, only activity components in + * that application package will be considered. + * * <p>If there are no activities that satisfy all of these conditions, a * null string is returned. * @@ -4089,6 +4201,27 @@ public class Intent implements Parcelable { } /** + * (Usually optional) Set an explicit application package name that limits + * the components this Intent will resolve to. If left to the default + * value of null, all components in all applications will considered. + * If non-null, the Intent can only match the components in the given + * application package. + * + * @param packageName The name of the application package to handle the + * intent, or null to allow any application package. + * + * @return Returns the same Intent object, for chaining multiple calls + * into a single statement. + * + * @see #getPackage + * @see #resolveActivity + */ + public Intent setPackage(String packageName) { + mPackage = packageName; + return this; + } + + /** * (Usually optional) Explicitly set the component to handle the intent. * If left with the default value of null, the system will determine the * appropriate class to use based on the other fields (action, data, @@ -4200,6 +4333,12 @@ public class Intent implements Parcelable { public static final int FILL_IN_COMPONENT = 1<<3; /** + * Use with {@link #fillIn} to allow the current package value to be + * overwritten, even if it is already set. + */ + public static final int FILL_IN_PACKAGE = 1<<4; + + /** * Copy the contents of <var>other</var> in to this object, but only * where fields are not defined by this object. For purposes of a field * being defined, the following pieces of data in the Intent are @@ -4210,14 +4349,15 @@ public class Intent implements Parcelable { * <li> data URI and MIME type, as set by {@link #setData(Uri)}, * {@link #setType(String)}, or {@link #setDataAndType(Uri, String)}. * <li> categories, as set by {@link #addCategory}. + * <li> package, as set by {@link #setPackage}. * <li> component, as set by {@link #setComponent(ComponentName)} or * related methods. * <li> each top-level name in the associated extras. * </ul> * * <p>In addition, you can use the {@link #FILL_IN_ACTION}, - * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and - * {@link #FILL_IN_COMPONENT} to override the restriction where the + * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE}, + * and {@link #FILL_IN_COMPONENT} to override the restriction where the * corresponding field will not be replaced if it is already set. * * <p>For example, consider Intent A with {data="foo", categories="bar"} @@ -4233,32 +4373,39 @@ public class Intent implements Parcelable { * @param flags Options to control which fields can be filled in. * * @return Returns a bit mask of {@link #FILL_IN_ACTION}, - * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, and - * {@link #FILL_IN_COMPONENT} indicating which fields were changed. + * {@link #FILL_IN_DATA}, {@link #FILL_IN_CATEGORIES}, {@link #FILL_IN_PACKAGE}, + * and {@link #FILL_IN_COMPONENT} indicating which fields were changed. */ public int fillIn(Intent other, int flags) { int changes = 0; - if ((mAction == null && other.mAction == null) - || (flags&FILL_IN_ACTION) != 0) { + if (other.mAction != null + && (mAction == null || (flags&FILL_IN_ACTION) != 0)) { mAction = other.mAction; changes |= FILL_IN_ACTION; } - if ((mData == null && mType == null && - (other.mData != null || other.mType != null)) - || (flags&FILL_IN_DATA) != 0) { + if ((other.mData != null || other.mType != null) + && ((mData == null && mType == null) + || (flags&FILL_IN_DATA) != 0)) { mData = other.mData; mType = other.mType; changes |= FILL_IN_DATA; } - if ((mCategories == null && other.mCategories == null) - || (flags&FILL_IN_CATEGORIES) != 0) { + if (other.mCategories != null + && (mCategories == null || (flags&FILL_IN_CATEGORIES) != 0)) { if (other.mCategories != null) { mCategories = new HashSet<String>(other.mCategories); } changes |= FILL_IN_CATEGORIES; } - if ((mComponent == null && other.mComponent == null) - || (flags&FILL_IN_COMPONENT) != 0) { + if (other.mPackage != null + && (mPackage == null || (flags&FILL_IN_PACKAGE) != 0)) { + mPackage = other.mPackage; + changes |= FILL_IN_PACKAGE; + } + // Component is special: it can -only- be set if explicitly allowed, + // since otherwise the sender could force the intent somewhere the + // originator didn't intend. + if (other.mComponent != null && (flags&FILL_IN_COMPONENT) != 0) { mComponent = other.mComponent; changes |= FILL_IN_COMPONENT; } @@ -4373,6 +4520,17 @@ public class Intent implements Parcelable { } } } + if (mPackage != other.mPackage) { + if (mPackage != null) { + if (!mPackage.equals(other.mPackage)) { + return false; + } + } else { + if (!other.mPackage.equals(mPackage)) { + return false; + } + } + } if (mComponent != other.mComponent) { if (mComponent != null) { if (!mComponent.equals(other.mComponent)) { @@ -4418,6 +4576,9 @@ public class Intent implements Parcelable { if (mType != null) { code += mType.hashCode(); } + if (mPackage != null) { + code += mPackage.hashCode(); + } if (mComponent != null) { code += mComponent.hashCode(); } @@ -4488,6 +4649,13 @@ public class Intent implements Parcelable { first = false; b.append("flg=0x").append(Integer.toHexString(mFlags)); } + if (mPackage != null) { + if (!first) { + b.append(' '); + } + first = false; + b.append("pkg=").append(mPackage); + } if (comp && mComponent != null) { if (!first) { b.append(' '); @@ -4504,28 +4672,87 @@ public class Intent implements Parcelable { } } + /** + * Call {@link #toUri} with 0 flags. + * @deprecated Use {@link #toUri} instead. + */ + @Deprecated public String toURI() { + return toUri(0); + } + + /** + * Convert this Intent into a String holding a URI representation of it. + * The returned URI string has been properly URI encoded, so it can be + * used with {@link Uri#parse Uri.parse(String)}. The URI contains the + * Intent's data as the base URI, with an additional fragment describing + * the action, categories, type, flags, package, component, and extras. + * + * <p>You can convert the returned string back to an Intent with + * {@link #getIntent}. + * + * @param flags Additional operating flags. Either 0 or + * {@link #URI_INTENT_SCHEME}. + * + * @return Returns a URI encoding URI string describing the entire contents + * of the Intent. + */ + public String toUri(int flags) { StringBuilder uri = new StringBuilder(128); - if (mData != null) uri.append(mData.toString()); + String scheme = null; + if (mData != null) { + String data = mData.toString(); + if ((flags&URI_INTENT_SCHEME) != 0) { + final int N = data.length(); + for (int i=0; i<N; i++) { + char c = data.charAt(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') + || c == '.' || c == '-') { + continue; + } + if (c == ':' && i > 0) { + // Valid scheme. + scheme = data.substring(0, i); + uri.append("intent:"); + data = data.substring(i+1); + break; + } + + // No scheme. + break; + } + } + uri.append(data); + + } else if ((flags&URI_INTENT_SCHEME) != 0) { + uri.append("intent:"); + } uri.append("#Intent;"); + if (scheme != null) { + uri.append("scheme=").append(scheme).append(';'); + } if (mAction != null) { - uri.append("action=").append(mAction).append(';'); + uri.append("action=").append(Uri.encode(mAction)).append(';'); } if (mCategories != null) { for (String category : mCategories) { - uri.append("category=").append(category).append(';'); + uri.append("category=").append(Uri.encode(category)).append(';'); } } if (mType != null) { - uri.append("type=").append(mType).append(';'); + uri.append("type=").append(Uri.encode(mType, "/")).append(';'); } if (mFlags != 0) { uri.append("launchFlags=0x").append(Integer.toHexString(mFlags)).append(';'); } + if (mPackage != null) { + uri.append("package=").append(Uri.encode(mPackage)).append(';'); + } if (mComponent != null) { - uri.append("component=").append(mComponent.flattenToShortString()).append(';'); + uri.append("component=").append(Uri.encode( + mComponent.flattenToShortString(), "/")).append(';'); } if (mExtras != null) { for (String key : mExtras.keySet()) { @@ -4567,6 +4794,7 @@ public class Intent implements Parcelable { Uri.writeToParcel(out, mData); out.writeString(mType); out.writeInt(mFlags); + out.writeString(mPackage); ComponentName.writeToParcel(mComponent, out); if (mCategories != null) { @@ -4600,6 +4828,7 @@ public class Intent implements Parcelable { mData = Uri.CREATOR.createFromParcel(in); mType = in.readString(); mFlags = in.readInt(); + mPackage = in.readString(); mComponent = ComponentName.readFromParcel(in); int N = in.readInt(); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index f10dd5309f25..2a2cf93de299 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -137,6 +137,13 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_TEST_ONLY = 1<<8; /** + * Value for {@link #flags}: true when the application's window can be + * expanded over default window size in target density (320x480 for + * 1.0 density, 480x720 for 1.5 density etc) + */ + public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<9; + + /** * Value for {@link #flags}: this is false if the application has set * its android:allowBackup to false, true otherwise. * @@ -201,12 +208,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public int[] supportsDensities; /** - * True when the application's window can be expanded over default window - * size in target density (320x480 for 1.0 density, 480x720 for 1.5 density etc) - */ - public boolean expandable = false; - - /** * The minimum SDK version this application targets. It may run on earilier * versions, but it knows how to work with any new behavior added at this * version. Will be {@link android.os.Build.VERSION_CODES#CUR_DEVELOPMENT} @@ -240,7 +241,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { pw.println(prefix + "manageSpaceActivityName="+manageSpaceActivityName); pw.println(prefix + "description=0x"+Integer.toHexString(descriptionRes)); pw.println(prefix + "supportsDensities=" + supportsDensities); - pw.println(prefix + "expandable=" + expandable); super.dumpBack(pw, prefix); } @@ -288,7 +288,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { manageSpaceActivityName = orig.manageSpaceActivityName; descriptionRes = orig.descriptionRes; supportsDensities = orig.supportsDensities; - expandable = orig.expandable; } @@ -321,7 +320,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { dest.writeString(backupAgentName); dest.writeInt(descriptionRes); dest.writeIntArray(supportsDensities); - dest.writeInt(expandable ? 1 : 0); } public static final Parcelable.Creator<ApplicationInfo> CREATOR @@ -353,7 +351,6 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { backupAgentName = source.readString(); descriptionRes = source.readInt(); supportsDensities = source.createIntArray(); - expandable = source.readInt() != 0; } /** @@ -383,7 +380,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public void disableCompatibilityMode() { - expandable = true; + flags |= FLAG_SUPPORTS_LARGE_SCREENS; supportsDensities = ANY_DENSITIES_ARRAY; } } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index 1a0f31f2cafe..bf2a8959c7f4 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -34,7 +34,6 @@ import android.content.pm.PermissionInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.net.Uri; -import android.app.PendingIntent; import android.content.IntentSender; /** @@ -82,9 +81,6 @@ interface IPackageManager { ResolveInfo resolveIntent(in Intent intent, String resolvedType, int flags); - ResolveInfo resolveIntentForPackage(in Intent intent, String resolvedType, int flags, - String packageName); - List<ResolveInfo> queryIntentActivities(in Intent intent, String resolvedType, int flags); @@ -238,35 +234,11 @@ interface IPackageManager { * and the current free storage is YY, * if XX is less than YY, just return. if not free XX-YY number * of bytes if possible. - * @param opFinishedIntent PendingIntent call back used to - * notify when the operation is completed.May be null - * to indicate that no call back is desired. - */ - void freeStorage(in long freeStorageSize, - in PendingIntent opFinishedIntent); - - /** - * Free storage by deleting LRU sorted list of cache files across - * all applications. If the currently available free storage - * on the device is greater than or equal to the requested - * free storage, no cache files are cleared. If the currently - * available storage on the device is less than the requested - * free storage, some or all of the cache files across - * all applications are deleted (based on last accessed time) - * to increase the free storage space on the device to - * the requested value. There is no guarantee that clearing all - * the cache files from all applications will clear up - * enough storage to achieve the desired value. - * @param freeStorageSize The number of bytes of storage to be - * freed by the system. Say if freeStorageSize is XX, - * and the current free storage is YY, - * if XX is less than YY, just return. if not free XX-YY number - * of bytes if possible. * @param pi IntentSender call back used to * notify when the operation is completed.May be null * to indicate that no call back is desired. */ - void freeStorageWithIntent(in long freeStorageSize, + void freeStorage(in long freeStorageSize, in IntentSender pi); /** @@ -304,4 +276,11 @@ interface IPackageManager { boolean isSafeMode(); void systemReady(); boolean hasSystemUidErrors(); + + /** + * Ask the package manager to perform dex-opt (if needed) on the given + * package, if it already hasn't done mode. Only does this if running + * in the special development "no pre-dexopt" mode. + */ + boolean performDexOpt(String packageName); } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index f74f3c2a5a14..941ca9e5bb74 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -16,8 +16,6 @@ package android.content.pm; - -import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -181,12 +179,6 @@ public abstract class PackageManager { public static final int MATCH_DEFAULT_ONLY = 0x00010000; /** - * {@link ApplicationInfo} flag: return the - * {link ApplicationInfo#expandable} boolean flag of the package. - */ - public static final int GET_EXPANDABLE = 0x00020000; - - /** * Permission check result: this is returned by {@link #checkPermission} * if the permission has been granted to the given package. */ @@ -986,23 +978,6 @@ public abstract class PackageManager { public abstract ResolveInfo resolveActivity(Intent intent, int flags); /** - * Resolve the intent restricted to a package. - * {@see #resolveActivity} - * - * @param intent An intent containing all of the desired specification - * (action, data, type, category, and/or component). - * @param flags Additional option flags. The most important is - * MATCH_DEFAULT_ONLY, to limit the resolution to only - * those activities that support the CATEGORY_DEFAULT. - * @param packageName Restrict the intent resolution to this package. - * - * @return Returns a ResolveInfo containing the final activity intent that - * was determined to be the best action. Returns null if no - * matching activity was found. - */ - public abstract ResolveInfo resolveActivity(Intent intent, int flags, String packageName); - - /** * Retrieve all activities that can be performed for the given intent. * * @param intent The desired intent as per resolveActivity(). @@ -1541,40 +1516,13 @@ public abstract class PackageManager { * and the current free storage is YY, * if XX is less than YY, just return. if not free XX-YY number * of bytes if possible. - * @param opFinishedIntent PendingIntent call back used to - * notify when the operation is completed.May be null - * to indicate that no call back is desired. - * - * @deprecated - * @hide - */ - @Deprecated - public abstract void freeStorage(long freeStorageSize, PendingIntent opFinishedIntent); - - /** - * Free storage by deleting LRU sorted list of cache files across - * all applications. If the currently available free storage - * on the device is greater than or equal to the requested - * free storage, no cache files are cleared. If the currently - * available storage on the device is less than the requested - * free storage, some or all of the cache files across - * all applications are deleted (based on last accessed time) - * to increase the free storage space on the device to - * the requested value. There is no guarantee that clearing all - * the cache files from all applications will clear up - * enough storage to achieve the desired value. - * @param freeStorageSize The number of bytes of storage to be - * freed by the system. Say if freeStorageSize is XX, - * and the current free storage is YY, - * if XX is less than YY, just return. if not free XX-YY number - * of bytes if possible. * @param pi IntentSender call back used to * notify when the operation is completed.May be null * to indicate that no call back is desired. * * @hide */ - public abstract void freeStorageWithIntent(long freeStorageSize, IntentSender pi); + public abstract void freeStorage(long freeStorageSize, IntentSender pi); /** * Retrieve the size information for a package. diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index ab8559c0eb81..ab9518eec250 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -777,7 +777,7 @@ public class PackageParser { targetCode = minCode = val.string.toString(); } else { // If it's not a string, it's an integer. - minVers = val.data; + targetVers = minVers = val.data; } } @@ -798,6 +798,25 @@ public class PackageParser { sa.recycle(); + if (minCode != null) { + if (!minCode.equals(mSdkCodename)) { + if (mSdkCodename != null) { + outError[0] = "Requires development platform " + minCode + + " (current platform is " + mSdkCodename + ")"; + } else { + outError[0] = "Requires development platform " + minCode + + " but this is a release platform."; + } + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; + } + } else if (minVers > mSdkVersion) { + outError[0] = "Requires newer sdk version #" + minVers + + " (current version is #" + mSdkVersion + ")"; + mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; + return null; + } + if (targetCode != null) { if (!targetCode.equals(mSdkCodename)) { if (mSdkCodename != null) { @@ -817,13 +836,6 @@ public class PackageParser { pkg.applicationInfo.targetSdkVersion = targetVers; } - if (minVers > mSdkVersion) { - outError[0] = "Requires newer sdk version #" + minVers - + " (current version is #" + mSdkVersion + ")"; - mParseError = PackageManager.INSTALL_FAILED_OLDER_SDK; - return null; - } - if (maxVers < mSdkVersion) { outError[0] = "Requires older sdk version #" + maxVers + " (current version is #" + mSdkVersion + ")"; @@ -865,7 +877,7 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); } else if (tagName.equals("expandable")) { - pkg.expandable = true; + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; XmlUtils.skipCurrentTag(parser); } else { Log.w(TAG, "Bad element under <manifest>: " @@ -2262,9 +2274,6 @@ public class PackageParser { public final ArrayList<Integer> supportsDensityList = new ArrayList<Integer>(); public int[] supportsDensities = null; - // If the application's window is expandable. - public boolean expandable; - // If this is a 3rd party app, this is the path of the zip file. public String mPath; @@ -2287,6 +2296,17 @@ public class PackageParser { // preferred up order. public int mPreferredOrder = 0; + // For use by package manager service to keep track of which apps + // have been installed with forward locking. + public boolean mForwardLocked; + + // For use by the package manager to keep track of the path to the + // file an app came from. + public String mScanPath; + + // For use by package manager to keep track of where it has done dexopt. + public boolean mDidDexOpt; + // Additional data supplied by callers. public Object mExtras; @@ -2439,9 +2459,6 @@ public class PackageParser { && p.supportsDensities != null) { return true; } - if ((flags & PackageManager.GET_EXPANDABLE) != 0) { - return true; - } return false; } @@ -2462,9 +2479,6 @@ public class PackageParser { if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0) { ai.supportsDensities = p.supportsDensities; } - if ((flags & PackageManager.GET_EXPANDABLE) != 0) { - ai.expandable = p.expandable; - } return ai; } diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index 836de3960b9a..680fef847dba 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -69,7 +69,8 @@ public class CompatibilityInfo { public final boolean mScalingRequired; public CompatibilityInfo(ApplicationInfo appInfo) { - mExpandable = mConfiguredExpandable = appInfo.expandable; + mExpandable = mConfiguredExpandable = + (appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0; float packageDensityScale = -1.0f; if (appInfo.supportsDensities != null) { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 2f638201a42c..71dbd38cb65a 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -22,11 +22,9 @@ import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import android.app.ActivityThread.PackageInfo; import android.content.pm.ApplicationInfo; import android.graphics.Movie; import android.graphics.drawable.Drawable; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.SystemProperties; @@ -35,6 +33,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; +import android.util.LongSparseArray; import java.io.IOException; import java.io.InputStream; @@ -59,19 +58,19 @@ public class Resources { // Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all // single-threaded, and after that these are immutable. - private static final SparseArray<Drawable.ConstantState> sPreloadedDrawables - = new SparseArray<Drawable.ConstantState>(); + private static final LongSparseArray<Drawable.ConstantState> sPreloadedDrawables + = new LongSparseArray<Drawable.ConstantState>(); private static final SparseArray<ColorStateList> mPreloadedColorStateLists = new SparseArray<ColorStateList>(); private static boolean mPreloaded; - private final SparseArray<Drawable.ConstantState> mPreloadedDrawables; + private final LongSparseArray<Drawable.ConstantState> mPreloadedDrawables; /*package*/ final TypedValue mTmpValue = new TypedValue(); // These are protected by the mTmpValue lock. - private final SparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache - = new SparseArray<WeakReference<Drawable.ConstantState> >(); + private final LongSparseArray<WeakReference<Drawable.ConstantState> > mDrawableCache + = new LongSparseArray<WeakReference<Drawable.ConstantState> >(); private final SparseArray<WeakReference<ColorStateList> > mColorStateListCache = new SparseArray<WeakReference<ColorStateList> >(); private boolean mPreloading; @@ -89,20 +88,20 @@ public class Resources { private final CompatibilityInfo mCompatibilityInfo; - private static final SparseArray<Object> EMPTY_ARRAY = new SparseArray<Object>() { + private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>() { @Override - public void put(int k, Object o) { + public void put(long k, Object o) { throw new UnsupportedOperationException(); } @Override - public void append(int k, Object o) { + public void append(long k, Object o) { throw new UnsupportedOperationException(); } }; @SuppressWarnings("unchecked") - private static <T> SparseArray<T> emptySparseArray() { - return (SparseArray<T>) EMPTY_ARRAY; + private static <T> LongSparseArray<T> emptySparseArray() { + return (LongSparseArray<T>) EMPTY_ARRAY; } /** @@ -1315,14 +1314,14 @@ public class Resources { configChanges, cs.getChangingConfigurations())) { if (DEBUG_CONFIG) { Log.d(TAG, "FLUSHING #0x" - + Integer.toHexString(mDrawableCache.keyAt(i)) + + Long.toHexString(mDrawableCache.keyAt(i)) + " / " + cs + " with changes: 0x" + Integer.toHexString(cs.getChangingConfigurations())); } mDrawableCache.setValueAt(i, null); } else if (DEBUG_CONFIG) { Log.d(TAG, "(Keeping #0x" - + Integer.toHexString(mDrawableCache.keyAt(i)) + + Long.toHexString(mDrawableCache.keyAt(i)) + " / " + cs + " with changes: 0x" + Integer.toHexString(cs.getChangingConfigurations()) + ")"); @@ -1653,7 +1652,7 @@ public class Resources { } } - final int key = (value.assetCookie << 24) | value.data; + final long key = (((long) value.assetCookie) << 32) | value.data; Drawable dr = getCachedDrawable(key); if (dr != null) { @@ -1733,7 +1732,7 @@ public class Resources { return dr; } - private Drawable getCachedDrawable(int key) { + private Drawable getCachedDrawable(long key) { synchronized (mTmpValue) { WeakReference<Drawable.ConstantState> wr = mDrawableCache.get(key); if (wr != null) { // we have the key diff --git a/core/java/android/preference/PreferenceScreen.java b/core/java/android/preference/PreferenceScreen.java index 5353b531dd06..6ea2528b5455 100644 --- a/core/java/android/preference/PreferenceScreen.java +++ b/core/java/android/preference/PreferenceScreen.java @@ -22,6 +22,7 @@ import android.content.DialogInterface; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.text.TextUtils; import android.util.AttributeSet; import android.view.View; import android.widget.Adapter; @@ -147,13 +148,20 @@ public final class PreferenceScreen extends PreferenceGroup implements AdapterVi ListView listView = new ListView(context); bind(listView); - Dialog dialog = mDialog = new Dialog(context, com.android.internal.R.style.Theme_NoTitleBar); + // Set the title bar if title is available, else no title bar + final CharSequence title = getTitle(); + Dialog dialog = mDialog = new Dialog(context, !TextUtils.isEmpty(title) + ? com.android.internal.R.style.Theme_NoTitleBar + : com.android.internal.R.style.Theme); dialog.setContentView(listView); + if (!TextUtils.isEmpty(title)) { + dialog.setTitle(title); + } dialog.setOnDismissListener(this); if (state != null) { dialog.onRestoreInstanceState(state); } - + // Add the screen to the list of preferences screens opened as dialogs getPreferenceManager().addPreferencesScreen(dialog); diff --git a/core/java/android/server/BluetoothDeviceService.java b/core/java/android/server/BluetoothDeviceService.java index 3a89abd2f32d..8c843efd7e4e 100644 --- a/core/java/android/server/BluetoothDeviceService.java +++ b/core/java/android/server/BluetoothDeviceService.java @@ -1224,6 +1224,8 @@ public class BluetoothDeviceService extends IBluetoothDevice.Stub { break; } pw.println("getHeadsetAddress() = " + headset.getHeadsetAddress()); + pw.println("getBatteryUsageHint() = " + headset.getBatteryUsageHint()); + headset.close(); } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 8f581940da26..9fc143d6c19e 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -81,6 +81,28 @@ public class TextToSpeech { } /** + * Internal constants for the TTS functionality + * + * {@hide} + */ + public class Engine { + // default values for a TTS engine when settings are not found in the provider + public static final int FALLBACK_TTS_DEFAULT_RATE = 100; // 1x + public static final int FALLBACK_TTS_DEFAULT_PITCH = 100;// 1x + public static final int FALLBACK_TTS_USE_DEFAULTS = 0; // false + public static final String FALLBACK_TTS_DEFAULT_LANG = "eng"; + public static final String FALLBACK_TTS_DEFAULT_COUNTRY = ""; + public static final String FALLBACK_TTS_DEFAULT_VARIANT = ""; + + // return codes for a TTS engine's check data activity + public static final int CHECK_VOICE_DATA_PASS = 1; + public static final int CHECK_VOICE_DATA_FAIL = 0; + public static final int CHECK_VOICE_DATA_BAD_DATA = -1; + public static final int CHECK_VOICE_DATA_MISSING_DATA = -2; + public static final int CHECK_VOICE_DATA_MISSING_DATA_NO_SDCARD = -3; + } + + /** * Connection needed for the TTS. */ private ServiceConnection mServiceConnection; diff --git a/core/java/android/test/InstrumentationTestCase.java b/core/java/android/test/InstrumentationTestCase.java index 470ab0d9a48e..2145d7cc7891 100644 --- a/core/java/android/test/InstrumentationTestCase.java +++ b/core/java/android/test/InstrumentationTestCase.java @@ -241,7 +241,13 @@ public class InstrumentationTestCase extends TestCase { try { final Field keyCodeField = KeyEvent.class.getField("KEYCODE_" + key); final int keyCode = keyCodeField.getInt(null); - instrumentation.sendKeyDownUpSync(keyCode); + try { + instrumentation.sendKeyDownUpSync(keyCode); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } } catch (NoSuchFieldException e) { Log.w("ActivityTestCase", "Unknown keycode: KEYCODE_" + key); break; @@ -266,7 +272,13 @@ public class InstrumentationTestCase extends TestCase { final Instrumentation instrumentation = getInstrumentation(); for (int i = 0; i < count; i++) { - instrumentation.sendKeyDownUpSync(keys[i]); + try { + instrumentation.sendKeyDownUpSync(keys[i]); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } } instrumentation.waitForIdleSync(); @@ -292,7 +304,13 @@ public class InstrumentationTestCase extends TestCase { final int keyCount = keys[i]; final int keyCode = keys[i + 1]; for (int j = 0; j < keyCount; j++) { - instrumentation.sendKeyDownUpSync(keyCode); + try { + instrumentation.sendKeyDownUpSync(keyCode); + } catch (SecurityException e) { + // Ignore security exceptions that are now thrown + // when trying to send to another app, to retain + // compatibility with existing tests. + } } } diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index bccb3a690cca..1a4eb699b56d 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -184,6 +184,9 @@ public class DateUtils */ public static final String HOUR_MINUTE_24 = "%H:%M"; public static final String MONTH_FORMAT = "%B"; + /** + * This is not actually a useful month name in all locales. + */ public static final String ABBREV_MONTH_FORMAT = "%b"; public static final String NUMERIC_MONTH_FORMAT = "%m"; public static final String MONTH_DAY_FORMAT = "%-d"; @@ -1444,7 +1447,8 @@ public class DateUtils if (numericDate) { monthFormat = NUMERIC_MONTH_FORMAT; } else if (abbrevMonth) { - monthFormat = ABBREV_MONTH_FORMAT; + monthFormat = + res.getString(com.android.internal.R.string.short_format_month); } else { monthFormat = MONTH_FORMAT; } diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 245148df4525..a0959135bfe3 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -37,8 +37,7 @@ public class DisplayMetrics { * The device's density. * @hide */ - public static final int DEVICE_DENSITY = SystemProperties.getInt("ro.sf.lcd_density", - DEFAULT_DENSITY); + public static final int DEVICE_DENSITY = getDeviceDensity(); /** * The absolute width of the display in pixels. @@ -161,4 +160,13 @@ public class DisplayMetrics { ", height=" + heightPixels + ", scaledDensity=" + scaledDensity + ", xdpi=" + xdpi + ", ydpi=" + ydpi + "}"; } + + private static int getDeviceDensity() { + // qemu.sf.lcd_density can be used to override ro.sf.lcd_density + // when running in the emulator, allowing for dynamic configurations. + // The reason for this is that ro.sf.lcd_density is write-once and is + // set by the init process when it parses build.prop before anything else. + return SystemProperties.getInt("qemu.sf.lcd_density", + SystemProperties.getInt("ro.sf.lcd_density", DEFAULT_DENSITY)); + } } diff --git a/core/java/android/util/LongSparseArray.java b/core/java/android/util/LongSparseArray.java new file mode 100644 index 000000000000..d90045f8625c --- /dev/null +++ b/core/java/android/util/LongSparseArray.java @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2009 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. + */ + +package android.util; + +import com.android.internal.util.ArrayUtils; + +/** + * SparseArrays map longs to Objects. Unlike a normal array of Objects, + * there can be gaps in the indices. It is intended to be more efficient + * than using a HashMap to map Longs to Objects. + * + * @hide + */ +public class LongSparseArray<E> { + private static final Object DELETED = new Object(); + private boolean mGarbage = false; + + /** + * Creates a new SparseArray containing no mappings. + */ + public LongSparseArray() { + this(10); + } + + /** + * Creates a new SparseArray containing no mappings that will not + * require any additional memory allocation to store the specified + * number of mappings. + */ + public LongSparseArray(int initialCapacity) { + initialCapacity = ArrayUtils.idealIntArraySize(initialCapacity); + + mKeys = new long[initialCapacity]; + mValues = new Object[initialCapacity]; + mSize = 0; + } + + /** + * Gets the Object mapped from the specified key, or <code>null</code> + * if no such mapping has been made. + */ + public E get(long key) { + return get(key, null); + } + + /** + * Gets the Object mapped from the specified key, or the specified Object + * if no such mapping has been made. + */ + public E get(long key, E valueIfKeyNotFound) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i < 0 || mValues[i] == DELETED) { + return valueIfKeyNotFound; + } else { + return (E) mValues[i]; + } + } + + /** + * Removes the mapping from the specified key, if there was any. + */ + public void delete(long key) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + if (mValues[i] != DELETED) { + mValues[i] = DELETED; + mGarbage = true; + } + } + } + + /** + * Alias for {@link #delete(long)}. + */ + public void remove(long key) { + delete(key); + } + + private void gc() { + // Log.e("SparseArray", "gc start with " + mSize); + + int n = mSize; + int o = 0; + long[] keys = mKeys; + Object[] values = mValues; + + for (int i = 0; i < n; i++) { + Object val = values[i]; + + if (val != DELETED) { + if (i != o) { + keys[o] = keys[i]; + values[o] = val; + } + + o++; + } + } + + mGarbage = false; + mSize = o; + + // Log.e("SparseArray", "gc end with " + mSize); + } + + /** + * Adds a mapping from the specified key to the specified value, + * replacing the previous mapping from the specified key if there + * was one. + */ + public void put(long key, E value) { + int i = binarySearch(mKeys, 0, mSize, key); + + if (i >= 0) { + mValues[i] = value; + } else { + i = ~i; + + if (i < mSize && mValues[i] == DELETED) { + mKeys[i] = key; + mValues[i] = value; + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + + // Search again because indices may have changed. + i = ~binarySearch(mKeys, 0, mSize, key); + } + + if (mSize >= mKeys.length) { + int n = ArrayUtils.idealIntArraySize(mSize + 1); + + long[] nkeys = new long[n]; + Object[] nvalues = new Object[n]; + + // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } + + if (mSize - i != 0) { + // Log.e("SparseArray", "move " + (mSize - i)); + System.arraycopy(mKeys, i, mKeys, i + 1, mSize - i); + System.arraycopy(mValues, i, mValues, i + 1, mSize - i); + } + + mKeys[i] = key; + mValues[i] = value; + mSize++; + } + } + + /** + * Returns the number of key-value mappings that this SparseArray + * currently stores. + */ + public int size() { + if (mGarbage) { + gc(); + } + + return mSize; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the key from the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public long keyAt(int index) { + if (mGarbage) { + gc(); + } + + return mKeys[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, returns + * the value from the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public E valueAt(int index) { + if (mGarbage) { + gc(); + } + + return (E) mValues[index]; + } + + /** + * Given an index in the range <code>0...size()-1</code>, sets a new + * value for the <code>index</code>th key-value mapping that this + * SparseArray stores. + */ + public void setValueAt(int index, E value) { + if (mGarbage) { + gc(); + } + + mValues[index] = value; + } + + /** + * Returns the index for which {@link #keyAt} would return the + * specified key, or a negative number if the specified + * key is not mapped. + */ + public int indexOfKey(long key) { + if (mGarbage) { + gc(); + } + + return binarySearch(mKeys, 0, mSize, key); + } + + /** + * Returns an index for which {@link #valueAt} would return the + * specified key, or a negative number if no keys map to the + * specified value. + * Beware that this is a linear search, unlike lookups by key, + * and that multiple keys can map to the same value and this will + * find only one of them. + */ + public int indexOfValue(E value) { + if (mGarbage) { + gc(); + } + + for (int i = 0; i < mSize; i++) + if (mValues[i] == value) + return i; + + return -1; + } + + /** + * Removes all key-value mappings from this SparseArray. + */ + public void clear() { + int n = mSize; + Object[] values = mValues; + + for (int i = 0; i < n; i++) { + values[i] = null; + } + + mSize = 0; + mGarbage = false; + } + + /** + * Puts a key/value pair into the array, optimizing for the case where + * the key is greater than all existing keys in the array. + */ + public void append(long key, E value) { + if (mSize != 0 && key <= mKeys[mSize - 1]) { + put(key, value); + return; + } + + if (mGarbage && mSize >= mKeys.length) { + gc(); + } + + int pos = mSize; + if (pos >= mKeys.length) { + int n = ArrayUtils.idealIntArraySize(pos + 1); + + long[] nkeys = new long[n]; + Object[] nvalues = new Object[n]; + + // Log.e("SparseArray", "grow " + mKeys.length + " to " + n); + System.arraycopy(mKeys, 0, nkeys, 0, mKeys.length); + System.arraycopy(mValues, 0, nvalues, 0, mValues.length); + + mKeys = nkeys; + mValues = nvalues; + } + + mKeys[pos] = key; + mValues[pos] = value; + mSize = pos + 1; + } + + private static int binarySearch(long[] a, int start, int len, long key) { + int high = start + len, low = start - 1, guess; + + while (high - low > 1) { + guess = (high + low) / 2; + + if (a[guess] < key) + low = guess; + else + high = guess; + } + + if (high == start + len) + return ~(start + len); + else if (a[high] == key) + return high; + else + return ~high; + } + + private void checkIntegrity() { + for (int i = 1; i < mSize; i++) { + if (mKeys[i] <= mKeys[i - 1]) { + for (int j = 0; j < mSize; j++) { + Log.e("FAIL", j + ": " + mKeys[j] + " -> " + mValues[j]); + } + + throw new RuntimeException(); + } + } + } + + private long[] mKeys; + private Object[] mValues; + private int mSize; +}
\ No newline at end of file diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java index 77393009db05..7542e88367e2 100644 --- a/core/java/android/webkit/JWebCoreJavaBridge.java +++ b/core/java/android/webkit/JWebCoreJavaBridge.java @@ -18,6 +18,7 @@ package android.webkit; import android.os.Handler; import android.os.Message; +import android.security.Keystore; import android.util.Log; final class JWebCoreJavaBridge extends Handler { @@ -187,18 +188,11 @@ final class JWebCoreJavaBridge extends Handler { } private String[] getKeyStrengthList() { - // FIXME: fake the list for now - String[] list = new String[2]; - list[0] = "1024"; - list[1] = "512"; - return list; + return Keystore.getInstance().getSupportedKeyStrenghs(); } private String getSignedPublicKey(int index, String challenge, String url) { - // FIXME: do nothing for now - Log.w(LOGTAG, "getSignedPublicKey for " + index + " and challenge=" - + challenge + " and url=" + url); - return ""; + return Keystore.getInstance().generateKeyPair(index, challenge, url); } private native void nativeConstructor(); diff --git a/core/java/com/android/internal/backup/GoogleTransport.java b/core/java/com/android/internal/backup/GoogleTransport.java deleted file mode 100644 index c089c23bdfcf..000000000000 --- a/core/java/com/android/internal/backup/GoogleTransport.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.android.internal.backup; - -import android.backup.RestoreSet; -import android.content.pm.PackageInfo; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; - -/** - * Backup transport for saving data to Google cloud storage. - */ - -public class GoogleTransport extends IBackupTransport.Stub { - - public long requestBackupTime() throws RemoteException { - return 0; // !!! TODO: implement real backoff policy - } - - public int startSession() throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public int endSession() throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor data) - throws RemoteException { - // TODO Auto-generated method stub - return 0; - } - - // Restore handling - public RestoreSet[] getAvailableRestoreSets() throws android.os.RemoteException { - // !!! TODO: real implementation - return null; - } - - public PackageInfo[] getAppSet(int token) throws android.os.RemoteException { - // !!! TODO: real implementation - return new PackageInfo[0]; - } - - public int getRestoreData(int token, PackageInfo packageInfo, ParcelFileDescriptor data) - throws android.os.RemoteException { - // !!! TODO: real implementation - return 0; - } -} diff --git a/core/java/com/android/internal/backup/LocalTransport.java b/core/java/com/android/internal/backup/LocalTransport.java index 577ecb4620bd..3ef8666ce722 100644 --- a/core/java/com/android/internal/backup/LocalTransport.java +++ b/core/java/com/android/internal/backup/LocalTransport.java @@ -12,6 +12,8 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.util.Log; +import org.bouncycastle.util.encoders.Base64; + import java.io.File; import java.io.FileFilter; import java.io.FileInputStream; @@ -78,25 +80,35 @@ public class LocalTransport extends IBackupTransport.Stub { byte[] buf = new byte[bufSize]; while (changeSet.readNextHeader()) { String key = changeSet.getKey(); + String base64Key = new String(Base64.encode(key.getBytes())); + File entityFile = new File(packageDir, base64Key); + int dataSize = changeSet.getDataSize(); - if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize); - if (dataSize > bufSize) { - bufSize = dataSize; - buf = new byte[bufSize]; - } - changeSet.readEntityData(buf, 0, dataSize); - if (DEBUG) Log.v(TAG, " + data size " + dataSize); - - File entityFile = new File(packageDir, key); - FileOutputStream entity = new FileOutputStream(entityFile); - try { - entity.write(buf, 0, dataSize); - } catch (IOException e) { - Log.e(TAG, "Unable to update key file " - + entityFile.getAbsolutePath()); - err = -1; - } finally { - entity.close(); + + if (DEBUG) Log.v(TAG, "Got change set key=" + key + " size=" + dataSize + + " key64=" + base64Key); + + if (dataSize >= 0) { + FileOutputStream entity = new FileOutputStream(entityFile); + + if (dataSize > bufSize) { + bufSize = dataSize; + buf = new byte[bufSize]; + } + changeSet.readEntityData(buf, 0, dataSize); + if (DEBUG) Log.v(TAG, " data size " + dataSize); + + try { + entity.write(buf, 0, dataSize); + } catch (IOException e) { + Log.e(TAG, "Unable to update key file " + + entityFile.getAbsolutePath()); + err = -1; + } finally { + entity.close(); + } + } else { + entityFile.delete(); } } } catch (IOException e) { @@ -167,7 +179,8 @@ public class LocalTransport extends IBackupTransport.Stub { int size = (int) f.length(); byte[] buf = new byte[size]; in.read(buf); - out.writeEntityHeader(f.getName(), size); + String key = new String(Base64.decode(f.getName())); + out.writeEntityHeader(key, size); out.writeEntityData(buf, size); } } catch (Exception e) { diff --git a/core/jni/Android.mk b/core/jni/Android.mk index fee83969e8c1..faa04f794a74 100644 --- a/core/jni/Android.mk +++ b/core/jni/Android.mk @@ -119,7 +119,8 @@ LOCAL_SRC_FILES:= \ com_android_internal_graphics_NativeUtils.cpp \ android_backup_BackupDataInput.cpp \ android_backup_BackupDataOutput.cpp \ - android_backup_FileBackupHelper.cpp + android_backup_FileBackupHelper.cpp \ + android_backup_RestoreHelperBase.cpp LOCAL_C_INCLUDES += \ $(JNI_H_INCLUDE) \ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 6d829fc26374..18f6d5f957af 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -158,6 +158,7 @@ extern int register_android_location_GpsLocationProvider(JNIEnv* env); extern int register_android_backup_BackupDataInput(JNIEnv *env); extern int register_android_backup_BackupDataOutput(JNIEnv *env); extern int register_android_backup_FileBackupHelper(JNIEnv *env); +extern int register_android_backup_RestoreHelperBase(JNIEnv *env); static AndroidRuntime* gCurRuntime = NULL; @@ -1131,6 +1132,7 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_backup_BackupDataInput), REG_JNI(register_android_backup_BackupDataOutput), REG_JNI(register_android_backup_FileBackupHelper), + REG_JNI(register_android_backup_RestoreHelperBase), }; /* diff --git a/core/jni/android_backup_RestoreHelperBase.cpp b/core/jni/android_backup_RestoreHelperBase.cpp new file mode 100644 index 000000000000..3173420a317e --- /dev/null +++ b/core/jni/android_backup_RestoreHelperBase.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2009 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. + */ + +#define LOG_TAG "FileBackupHelper_native" +#include <utils/Log.h> + +#include "JNIHelp.h" +#include <android_runtime/AndroidRuntime.h> + +#include <utils/BackupHelpers.h> + +namespace android +{ + +// java.io.FileDescriptor +static jfieldID s_descriptorField = 0; + +static int +ctor(JNIEnv* env, jobject clazz) +{ + return (int)new RestoreHelperBase(); +} + +static void +dtor(JNIEnv* env, jobject clazz, jint ptr) +{ + delete (RestoreHelperBase*)ptr; +} + +static int +writeFile_native(JNIEnv* env, jobject clazz, jint ptr, jstring filenameObj, int backupReaderPtr) +{ + int err; + RestoreHelperBase* restore = (RestoreHelperBase*)ptr; + BackupDataReader* reader = (BackupDataReader*)backupReaderPtr; + char const* filename; + + filename = env->GetStringUTFChars(filenameObj, NULL); + + err = restore->WriteFile(String8(filename), reader); + + env->ReleaseStringUTFChars(filenameObj, filename); + + return err; +} + +static int +writeSnapshot_native(JNIEnv* env, jobject clazz, jint ptr, jobject fileDescriptor) +{ + int err; + + RestoreHelperBase* restore = (RestoreHelperBase*)ptr; + int fd = env->GetIntField(fileDescriptor, s_descriptorField); + + err = restore->WriteSnapshot(fd); + + return err; +} + +static const JNINativeMethod g_methods[] = { + { "ctor", "()I", (void*)ctor }, + { "dtor", "(I)V", (void*)dtor }, + { "writeFile_native", "(ILjava/lang/String;I)I", (void*)writeFile_native }, + { "writeSnapshot_native", "(ILjava/io/FileDescriptor;)I", (void*)writeSnapshot_native }, +}; + +int register_android_backup_RestoreHelperBase(JNIEnv* env) +{ + jclass clazz; + + clazz = env->FindClass("java/io/FileDescriptor"); + LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor"); + s_descriptorField = env->GetFieldID(clazz, "descriptor", "I"); + LOG_FATAL_IF(s_descriptorField == NULL, + "Unable to find descriptor field in java.io.FileDescriptor"); + + return AndroidRuntime::registerNativeMethods(env, "android/backup/RestoreHelperBase", + g_methods, NELEM(g_methods)); +} + +} diff --git a/core/jni/android_server_BluetoothEventLoop.cpp b/core/jni/android_server_BluetoothEventLoop.cpp index ff8f28a5f5dd..ad24136ac28f 100644 --- a/core/jni/android_server_BluetoothEventLoop.cpp +++ b/core/jni/android_server_BluetoothEventLoop.cpp @@ -162,6 +162,19 @@ static const DBusObjectPathVTable agent_vtable = { NULL, agent_event_filter, NULL, NULL, NULL, NULL }; +static unsigned int unix_events_to_dbus_flags(short events) { + return (events & DBUS_WATCH_READABLE ? POLLIN : 0) | + (events & DBUS_WATCH_WRITABLE ? POLLOUT : 0) | + (events & DBUS_WATCH_ERROR ? POLLERR : 0) | + (events & DBUS_WATCH_HANGUP ? POLLHUP : 0); +} + +static short dbus_flags_to_unix_events(unsigned int flags) { + return (flags & POLLIN ? DBUS_WATCH_READABLE : 0) | + (flags & POLLOUT ? DBUS_WATCH_WRITABLE : 0) | + (flags & POLLERR ? DBUS_WATCH_ERROR : 0) | + (flags & POLLHUP ? DBUS_WATCH_HANGUP : 0); +} static jboolean setUpEventLoop(native_data_t *nat) { LOGV(__FUNCTION__); @@ -385,8 +398,7 @@ static void handleWatchAdd(native_data_t *nat) { read(nat->controlFdR, &newFD, sizeof(int)); read(nat->controlFdR, &flags, sizeof(unsigned int)); read(nat->controlFdR, &watch, sizeof(DBusWatch *)); - int events = (flags & DBUS_WATCH_READABLE ? POLLIN : 0) - | (flags & DBUS_WATCH_WRITABLE ? POLLOUT : 0); + short events = dbus_flags_to_unix_events(flags); for (int y = 0; y<nat->pollMemberCount; y++) { if ((nat->pollData[y].fd == newFD) && @@ -430,8 +442,7 @@ static void handleWatchRemove(native_data_t *nat) { read(nat->controlFdR, &removeFD, sizeof(int)); read(nat->controlFdR, &flags, sizeof(unsigned int)); - int events = (flags & DBUS_WATCH_READABLE ? POLLIN : 0) - | (flags & DBUS_WATCH_WRITABLE ? POLLOUT : 0); + short events = dbus_flags_to_unix_events(flags); for (int y = 0; y < nat->pollMemberCount; y++) { if ((nat->pollData[y].fd == removeFD) && @@ -495,13 +506,12 @@ static void *eventLoopMain(void *ptr) { } } } else { - int event = nat->pollData[i].revents; - int flags = (event & POLLIN ? DBUS_WATCH_READABLE : 0) | - (event & POLLOUT ? DBUS_WATCH_WRITABLE : 0); - dbus_watch_handle(nat->watchData[i], event); - nat->pollData[i].revents = 0; - // can only do one - it may have caused a 'remove' - break; + short events = nat->pollData[i].revents; + unsigned int flags = unix_events_to_dbus_flags(events); + dbus_watch_handle(nat->watchData[i], flags); + nat->pollData[i].revents = 0; + // can only do one - it may have caused a 'remove' + break; } } while (dbus_connection_dispatch(nat->conn) == diff --git a/core/res/res/drawable/call_contact.png b/core/res/res/drawable/call_contact.png Binary files differnew file mode 100644 index 000000000000..1abeb5da3bcb --- /dev/null +++ b/core/res/res/drawable/call_contact.png diff --git a/core/res/res/drawable/create_contact.png b/core/res/res/drawable/create_contact.png Binary files differnew file mode 100644 index 000000000000..5c5718bafc00 --- /dev/null +++ b/core/res/res/drawable/create_contact.png diff --git a/core/res/res/drawable/search_dropdown_background_apps.9.png b/core/res/res/drawable/search_dropdown_background_apps.9.png Binary files differindex 56b697d97b8e..804260afa97f 100644 --- a/core/res/res/drawable/search_dropdown_background_apps.9.png +++ b/core/res/res/drawable/search_dropdown_background_apps.9.png diff --git a/core/res/res/values-ar-rEG/donottranslate-cldr.xml b/core/res/res/values-ar-rEG/donottranslate-cldr.xml index 4826a41179c9..2c20ffcdf38e 100644 --- a/core/res/res/values-ar-rEG/donottranslate-cldr.xml +++ b/core/res/res/values-ar-rEG/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s، %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s، %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s، %3$s %2$s - %6$s، %8$s %7$s، %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-bg-rBG/donottranslate-cldr.xml b/core/res/res/values-bg-rBG/donottranslate-cldr.xml index 010b97459250..cda072a5d624 100644 --- a/core/res/res/values-bg-rBG/donottranslate-cldr.xml +++ b/core/res/res/values-bg-rBG/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s %9$s, %1$s - %8$s %7$s y, %6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ca-rES/donottranslate-cldr.xml b/core/res/res/values-ca-rES/donottranslate-cldr.xml index 4eabba770187..d5abeef1a96a 100644 --- a/core/res/res/values-ca-rES/donottranslate-cldr.xml +++ b/core/res/res/values-ca-rES/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s de %2$s - %8$s de %7$s de %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s de %2$s de %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s de %2$s - %6$s %8$s de %7$s de %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-cs-rCZ/donottranslate-cldr.xml b/core/res/res/values-cs-rCZ/donottranslate-cldr.xml index 0670080aa5e9..e933f20a067d 100644 --- a/core/res/res/values-cs-rCZ/donottranslate-cldr.xml +++ b/core/res/res/values-cs-rCZ/donottranslate-cldr.xml @@ -107,10 +107,10 @@ <string name="abbrev_month_day_year">%-e.%-m.%Y</string> <string name="month_day">%-e. %B</string> <string name="month">%-B</string> - <string name="month_year">%Y %B</string> - <string name="abbrev_month_day">%b %-e</string> - <string name="abbrev_month">%-b</string> - <string name="abbrev_month_year">%Y %b</string> + <string name="month_year">%-B %Y</string> + <string name="abbrev_month_day">%-e.%-m</string> + <string name="abbrev_month">%-B</string> + <string name="abbrev_month_year">%-B %Y</string> <string name="time1_time2">%1$s - %2$s</string> <string name="date1_date2">%2$s - %5$s</string> <string name="numeric_md1_md2">%3$s.%2$s. - %8$s.%7$s.</string> @@ -121,26 +121,27 @@ <string name="numeric_md1_time1_md2_time2">%5$s %3$s.%2$s - %10$s %8$s.%7$s</string> <string name="numeric_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %2$s-%3$s - %10$s %6$s, %7$s-%8$s</string> <string name="numeric_mdy1_time1_mdy2_time2">%5$s %3$s.%2$s.%4$s - %10$s %8$s.%7$s.%9$s</string> - <string name="wday1_date1_time1_wday2_date2_time2">%3$s %1$s %2$s - %6$s %4$s %5$s</string> - <string name="wday1_date1_wday2_date2">%1$s %2$s - %4$s %5$s</string> + <string name="wday1_date1_time1_wday2_date2_time2">%3$s %1$s, %2$s - %6$s %4$s, %5$s</string> + <string name="wday1_date1_wday2_date2">%1$s, %2$s - %4$s, %5$s</string> <string name="date1_time1_date2_time2">%3$s %2$s - %6$s %5$s</string> - <string name="time_wday_date">%1$s %2$s %3$s</string> - <string name="wday_date">%2$s %3$s</string> + <string name="time_wday_date">%1$s %2$s, %3$s</string> + <string name="wday_date">%2$s, %3$s</string> <string name="time_wday">%1$s %2$s</string> <string name="same_year_md1_md2">%3$s. %2$s - %8$s. %7$s</string> - <string name="same_year_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string> + <string name="same_year_wday1_md1_wday2_md2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s</string> <string name="same_year_md1_time1_md2_time2">%5$s %3$s. %2$s - %10$s %8$s. %7$s</string> <string name="same_month_md1_time1_md2_time2">%5$s %3$s. %2$s - %10$s %8$s. %7$s</string> - <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string> - <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string> + <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %3$s. %2$s - %10$s %6$s, %8$s. %7$s</string> + <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %3$s. %2$s - %10$s %6$s, %8$s. %7$s</string> <string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s. %2$s %4$s - %10$s %8$s. %7$s %9$s</string> <string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s. %2$s %4$s - %10$s %8$s. %7$s %9$s</string> - <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %4$s %2$s %3$s - %10$s %6$s, %9$s %7$s %8$s</string> - <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %4$s %2$s %3$s - %10$s %6$s, %9$s %7$s %8$s</string> - <string name="same_month_wday1_mdy1_wday2_mdy2">%1$s, %4$s %2$s %3$s - %6$s, %9$s %7$s %8$s</string> + <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %3$s. %2$s %4$s - %10$s %6$s, %8$s. %7$s %9$s</string> + <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %3$s. %2$s %4$s - %10$s %6$s, %8$s. %7$s %9$s</string> + <string name="same_month_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s %4$s - %6$s, %8$s. %7$s %9$s</string> <string name="same_month_md1_md2">%3$s.-%8$s. %2$s</string> - <string name="same_month_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string> + <string name="same_month_wday1_md1_wday2_md2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s</string> <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%B</string> </resources> diff --git a/core/res/res/values-cs/donottranslate-cldr.xml b/core/res/res/values-cs/donottranslate-cldr.xml index 0670080aa5e9..e933f20a067d 100644 --- a/core/res/res/values-cs/donottranslate-cldr.xml +++ b/core/res/res/values-cs/donottranslate-cldr.xml @@ -107,10 +107,10 @@ <string name="abbrev_month_day_year">%-e.%-m.%Y</string> <string name="month_day">%-e. %B</string> <string name="month">%-B</string> - <string name="month_year">%Y %B</string> - <string name="abbrev_month_day">%b %-e</string> - <string name="abbrev_month">%-b</string> - <string name="abbrev_month_year">%Y %b</string> + <string name="month_year">%-B %Y</string> + <string name="abbrev_month_day">%-e.%-m</string> + <string name="abbrev_month">%-B</string> + <string name="abbrev_month_year">%-B %Y</string> <string name="time1_time2">%1$s - %2$s</string> <string name="date1_date2">%2$s - %5$s</string> <string name="numeric_md1_md2">%3$s.%2$s. - %8$s.%7$s.</string> @@ -121,26 +121,27 @@ <string name="numeric_md1_time1_md2_time2">%5$s %3$s.%2$s - %10$s %8$s.%7$s</string> <string name="numeric_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %2$s-%3$s - %10$s %6$s, %7$s-%8$s</string> <string name="numeric_mdy1_time1_mdy2_time2">%5$s %3$s.%2$s.%4$s - %10$s %8$s.%7$s.%9$s</string> - <string name="wday1_date1_time1_wday2_date2_time2">%3$s %1$s %2$s - %6$s %4$s %5$s</string> - <string name="wday1_date1_wday2_date2">%1$s %2$s - %4$s %5$s</string> + <string name="wday1_date1_time1_wday2_date2_time2">%3$s %1$s, %2$s - %6$s %4$s, %5$s</string> + <string name="wday1_date1_wday2_date2">%1$s, %2$s - %4$s, %5$s</string> <string name="date1_time1_date2_time2">%3$s %2$s - %6$s %5$s</string> - <string name="time_wday_date">%1$s %2$s %3$s</string> - <string name="wday_date">%2$s %3$s</string> + <string name="time_wday_date">%1$s %2$s, %3$s</string> + <string name="wday_date">%2$s, %3$s</string> <string name="time_wday">%1$s %2$s</string> <string name="same_year_md1_md2">%3$s. %2$s - %8$s. %7$s</string> - <string name="same_year_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string> + <string name="same_year_wday1_md1_wday2_md2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s</string> <string name="same_year_md1_time1_md2_time2">%5$s %3$s. %2$s - %10$s %8$s. %7$s</string> <string name="same_month_md1_time1_md2_time2">%5$s %3$s. %2$s - %10$s %8$s. %7$s</string> - <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string> - <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s %2$s %3$s - %10$s %6$s %7$s %8$s</string> + <string name="same_year_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %3$s. %2$s - %10$s %6$s, %8$s. %7$s</string> + <string name="same_month_wday1_md1_time1_wday2_md2_time2">%5$s %1$s, %3$s. %2$s - %10$s %6$s, %8$s. %7$s</string> <string name="same_year_mdy1_time1_mdy2_time2">%5$s %3$s. %2$s %4$s - %10$s %8$s. %7$s %9$s</string> <string name="same_month_mdy1_time1_mdy2_time2">%5$s %3$s. %2$s %4$s - %10$s %8$s. %7$s %9$s</string> - <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %4$s %2$s %3$s - %10$s %6$s, %9$s %7$s %8$s</string> - <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %4$s %2$s %3$s - %10$s %6$s, %9$s %7$s %8$s</string> - <string name="same_month_wday1_mdy1_wday2_mdy2">%1$s, %4$s %2$s %3$s - %6$s, %9$s %7$s %8$s</string> + <string name="same_year_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %3$s. %2$s %4$s - %10$s %6$s, %8$s. %7$s %9$s</string> + <string name="same_month_wday1_mdy1_time1_wday2_mdy2_time2">%5$s %1$s, %3$s. %2$s %4$s - %10$s %6$s, %8$s. %7$s %9$s</string> + <string name="same_month_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s %4$s - %6$s, %8$s. %7$s %9$s</string> <string name="same_month_md1_md2">%3$s.-%8$s. %2$s</string> - <string name="same_month_wday1_md1_wday2_md2">%1$s %2$s %3$s - %6$s %7$s %8$s</string> + <string name="same_month_wday1_md1_wday2_md2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s</string> <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%B</string> </resources> diff --git a/core/res/res/values-da-rDK/donottranslate-cldr.xml b/core/res/res/values-da-rDK/donottranslate-cldr.xml index 4a2a6563cff3..8cef5b26c88c 100644 --- a/core/res/res/values-da-rDK/donottranslate-cldr.xml +++ b/core/res/res/values-da-rDK/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s den %3$s. %2$s - %6$s den %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-de-rAT/donottranslate-cldr.xml b/core/res/res/values-de-rAT/donottranslate-cldr.xml index e6112ba6850a..559e1ee94922 100644 --- a/core/res/res/values-de-rAT/donottranslate-cldr.xml +++ b/core/res/res/values-de-rAT/donottranslate-cldr.xml @@ -110,4 +110,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-de-rCH/donottranslate-cldr.xml b/core/res/res/values-de-rCH/donottranslate-cldr.xml index b611c08463e7..2ca6787e2bf0 100644 --- a/core/res/res/values-de-rCH/donottranslate-cldr.xml +++ b/core/res/res/values-de-rCH/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-de-rDE/donottranslate-cldr.xml b/core/res/res/values-de-rDE/donottranslate-cldr.xml index b611c08463e7..2ca6787e2bf0 100644 --- a/core/res/res/values-de-rDE/donottranslate-cldr.xml +++ b/core/res/res/values-de-rDE/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-de-rLI/donottranslate-cldr.xml b/core/res/res/values-de-rLI/donottranslate-cldr.xml index b611c08463e7..2ca6787e2bf0 100644 --- a/core/res/res/values-de-rLI/donottranslate-cldr.xml +++ b/core/res/res/values-de-rLI/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-de/donottranslate-cldr.xml b/core/res/res/values-de/donottranslate-cldr.xml index b611c08463e7..2ca6787e2bf0 100644 --- a/core/res/res/values-de/donottranslate-cldr.xml +++ b/core/res/res/values-de/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-el-rGR/donottranslate-cldr.xml b/core/res/res/values-el-rGR/donottranslate-cldr.xml index f76281a224b5..e8f02fb0d47b 100644 --- a/core/res/res/values-el-rGR/donottranslate-cldr.xml +++ b/core/res/res/values-el-rGR/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rAU/donottranslate-cldr.xml b/core/res/res/values-en-rAU/donottranslate-cldr.xml index 223a22be337e..5d1a8f6ce60e 100644 --- a/core/res/res/values-en-rAU/donottranslate-cldr.xml +++ b/core/res/res/values-en-rAU/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rCA/donottranslate-cldr.xml b/core/res/res/values-en-rCA/donottranslate-cldr.xml index 32fa2b03a62e..5d7300ec692a 100644 --- a/core/res/res/values-en-rCA/donottranslate-cldr.xml +++ b/core/res/res/values-en-rCA/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%2$s %3$s - %7$s %8$s, %9$s</string> <string name="same_month_mdy1_mdy2">%2$s %3$s-%8$s, %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %2$s %3$s - %6$s, %7$s %8$s, %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rGB/donottranslate-cldr.xml b/core/res/res/values-en-rGB/donottranslate-cldr.xml index b90112fc8260..b115c6e00b3d 100644 --- a/core/res/res/values-en-rGB/donottranslate-cldr.xml +++ b/core/res/res/values-en-rGB/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rIE/donottranslate-cldr.xml b/core/res/res/values-en-rIE/donottranslate-cldr.xml index 4143da521e02..15fc8e82b0ab 100644 --- a/core/res/res/values-en-rIE/donottranslate-cldr.xml +++ b/core/res/res/values-en-rIE/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rIN/donottranslate-cldr.xml b/core/res/res/values-en-rIN/donottranslate-cldr.xml index 6522d6776ee9..2507ee8fa450 100644 --- a/core/res/res/values-en-rIN/donottranslate-cldr.xml +++ b/core/res/res/values-en-rIN/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rNZ/donottranslate-cldr.xml b/core/res/res/values-en-rNZ/donottranslate-cldr.xml index d29455a0f177..07d4fe8ea1e8 100644 --- a/core/res/res/values-en-rNZ/donottranslate-cldr.xml +++ b/core/res/res/values-en-rNZ/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rSG/donottranslate-cldr.xml b/core/res/res/values-en-rSG/donottranslate-cldr.xml index 56c58e2444c0..286cc0e554f2 100644 --- a/core/res/res/values-en-rSG/donottranslate-cldr.xml +++ b/core/res/res/values-en-rSG/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%2$s %3$s – %7$s %8$s, %9$s</string> <string name="same_month_mdy1_mdy2">%2$s %3$s – %8$s, %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %2$s %3$s – %6$s, %7$s %8$s, %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rUS/donottranslate-cldr.xml b/core/res/res/values-en-rUS/donottranslate-cldr.xml index 56c58e2444c0..286cc0e554f2 100644 --- a/core/res/res/values-en-rUS/donottranslate-cldr.xml +++ b/core/res/res/values-en-rUS/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%2$s %3$s – %7$s %8$s, %9$s</string> <string name="same_month_mdy1_mdy2">%2$s %3$s – %8$s, %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %2$s %3$s – %6$s, %7$s %8$s, %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-en-rZA/donottranslate-cldr.xml b/core/res/res/values-en-rZA/donottranslate-cldr.xml index e1aac0466346..9e8681be4f81 100644 --- a/core/res/res/values-en-rZA/donottranslate-cldr.xml +++ b/core/res/res/values-en-rZA/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-es-rES/donottranslate-cldr.xml b/core/res/res/values-es-rES/donottranslate-cldr.xml index b5162917023f..c1dc58b815d0 100644 --- a/core/res/res/values-es-rES/donottranslate-cldr.xml +++ b/core/res/res/values-es-rES/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s – %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s – %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-es-rUS/donottranslate-cldr.xml b/core/res/res/values-es-rUS/donottranslate-cldr.xml index 587a615c79d7..24923c3e7c6f 100644 --- a/core/res/res/values-es-rUS/donottranslate-cldr.xml +++ b/core/res/res/values-es-rUS/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s de %2$s al %8$s de %7$s de %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s de %2$s de %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s de %2$s al %6$s %8$s de %7$s de %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-es/donottranslate-cldr.xml b/core/res/res/values-es/donottranslate-cldr.xml index b5162917023f..c1dc58b815d0 100644 --- a/core/res/res/values-es/donottranslate-cldr.xml +++ b/core/res/res/values-es/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s – %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s – %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-fi-rFI/donottranslate-cldr.xml b/core/res/res/values-fi-rFI/donottranslate-cldr.xml index 40cda53a1eaf..dd12e575a4d9 100644 --- a/core/res/res/values-fi-rFI/donottranslate-cldr.xml +++ b/core/res/res/values-fi-rFI/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s – %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.–%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s. %2$s – %6$s %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-fr-rBE/donottranslate-cldr.xml b/core/res/res/values-fr-rBE/donottranslate-cldr.xml index 0795cc5229b6..ea4ecf2a045a 100644 --- a/core/res/res/values-fr-rBE/donottranslate-cldr.xml +++ b/core/res/res/values-fr-rBE/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s au %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s au %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-fr-rCA/donottranslate-cldr.xml b/core/res/res/values-fr-rCA/donottranslate-cldr.xml index 7802ee04eb76..346b97107331 100644 --- a/core/res/res/values-fr-rCA/donottranslate-cldr.xml +++ b/core/res/res/values-fr-rCA/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">du %3$s %2$s au %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">du %1$s %3$s %2$s au %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-fr-rCH/donottranslate-cldr.xml b/core/res/res/values-fr-rCH/donottranslate-cldr.xml index bbda44a64337..0a9835f4eee4 100644 --- a/core/res/res/values-fr-rCH/donottranslate-cldr.xml +++ b/core/res/res/values-fr-rCH/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s au %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s au %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-fr-rFR/donottranslate-cldr.xml b/core/res/res/values-fr-rFR/donottranslate-cldr.xml index 76d41411038e..f340e8384116 100644 --- a/core/res/res/values-fr-rFR/donottranslate-cldr.xml +++ b/core/res/res/values-fr-rFR/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s – %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s – %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-fr/donottranslate-cldr.xml b/core/res/res/values-fr/donottranslate-cldr.xml index 76d41411038e..f340e8384116 100644 --- a/core/res/res/values-fr/donottranslate-cldr.xml +++ b/core/res/res/values-fr/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s – %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s – %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-he-rIL/donottranslate-cldr.xml b/core/res/res/values-he-rIL/donottranslate-cldr.xml index 1c8a6f75372c..e3feb1e33146 100644 --- a/core/res/res/values-he-rIL/donottranslate-cldr.xml +++ b/core/res/res/values-he-rIL/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-hi-rIN/donottranslate-cldr.xml b/core/res/res/values-hi-rIN/donottranslate-cldr.xml index ba4ded719356..03da5159d1c5 100644 --- a/core/res/res/values-hi-rIN/donottranslate-cldr.xml +++ b/core/res/res/values-hi-rIN/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s-%2$s-%3$s – %7$s-%8$s</string> <string name="same_month_mdy1_mdy2">%9$s-%2$s-%3$s – %8$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %9$s-%2$s-%3$s – %6$s, yyyy-%7$s-%8$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-hu-rHU/donottranslate-cldr.xml b/core/res/res/values-hu-rHU/donottranslate-cldr.xml index 8dcb426a0b6f..b56f520bc346 100644 --- a/core/res/res/values-hu-rHU/donottranslate-cldr.xml +++ b/core/res/res/values-hu-rHU/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s. %2$s %3$s. - %7$s %8$s.</string> <string name="same_month_mdy1_mdy2">%9$s. %2$s %3$s-%8$s.</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s. %2$s %3$s., %1$s - %7$s %8$s., %6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-id-rID/donottranslate-cldr.xml b/core/res/res/values-id-rID/donottranslate-cldr.xml index feac981e50b9..22860a71e473 100644 --- a/core/res/res/values-id-rID/donottranslate-cldr.xml +++ b/core/res/res/values-id-rID/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s-%2$s-%3$s – %7$s-%8$s</string> <string name="same_month_mdy1_mdy2">%9$s-%2$s-%3$s – %8$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %9$s-%2$s-%3$s – %6$s, yyyy-%7$s-%8$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-it-rCH/donottranslate-cldr.xml b/core/res/res/values-it-rCH/donottranslate-cldr.xml index 6b76f8ea822d..6d9b550d88fe 100644 --- a/core/res/res/values-it-rCH/donottranslate-cldr.xml +++ b/core/res/res/values-it-rCH/donottranslate-cldr.xml @@ -138,4 +138,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-it-rIT/donottranslate-cldr.xml b/core/res/res/values-it-rIT/donottranslate-cldr.xml index 929f8995c441..1bf79923e8b4 100644 --- a/core/res/res/values-it-rIT/donottranslate-cldr.xml +++ b/core/res/res/values-it-rIT/donottranslate-cldr.xml @@ -138,4 +138,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-it/donottranslate-cldr.xml b/core/res/res/values-it/donottranslate-cldr.xml index 929f8995c441..1bf79923e8b4 100644 --- a/core/res/res/values-it/donottranslate-cldr.xml +++ b/core/res/res/values-it/donottranslate-cldr.xml @@ -138,4 +138,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ja-rJP/donottranslate-cldr.xml b/core/res/res/values-ja-rJP/donottranslate-cldr.xml index ae0743305879..10f2f79e1d98 100644 --- a/core/res/res/values-ja-rJP/donottranslate-cldr.xml +++ b/core/res/res/values-ja-rJP/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s年%2$s%3$s日~%7$s月%8$s日</string> <string name="same_month_mdy1_mdy2">%9$s年%2$s%3$s日~%8$s日</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s年%2$s%3$s日(%1$s)~%7$s月%8$s日(%6$s)</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ja/donottranslate-cldr.xml b/core/res/res/values-ja/donottranslate-cldr.xml index ae0743305879..10f2f79e1d98 100644 --- a/core/res/res/values-ja/donottranslate-cldr.xml +++ b/core/res/res/values-ja/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s年%2$s%3$s日~%7$s月%8$s日</string> <string name="same_month_mdy1_mdy2">%9$s年%2$s%3$s日~%8$s日</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s年%2$s%3$s日(%1$s)~%7$s月%8$s日(%6$s)</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ko-rKR/donottranslate-cldr.xml b/core/res/res/values-ko-rKR/donottranslate-cldr.xml index 4ec1ba49c429..45ca90a4cbc4 100644 --- a/core/res/res/values-ko-rKR/donottranslate-cldr.xml +++ b/core/res/res/values-ko-rKR/donottranslate-cldr.xml @@ -131,4 +131,5 @@ <string name="same_year_mdy1_mdy2">%9$s년 %2$s %3$s일 ~ %7$s월 %8$s일</string> <string name="same_month_mdy1_mdy2">%9$s년 %2$s %3$s일~%8$s일</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s년 %2$s %3$s일 %1$s ~ %7$s월 %8$s일 %6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ko/donottranslate-cldr.xml b/core/res/res/values-ko/donottranslate-cldr.xml index 4ec1ba49c429..45ca90a4cbc4 100644 --- a/core/res/res/values-ko/donottranslate-cldr.xml +++ b/core/res/res/values-ko/donottranslate-cldr.xml @@ -131,4 +131,5 @@ <string name="same_year_mdy1_mdy2">%9$s년 %2$s %3$s일 ~ %7$s월 %8$s일</string> <string name="same_month_mdy1_mdy2">%9$s년 %2$s %3$s일~%8$s일</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s년 %2$s %3$s일 %1$s ~ %7$s월 %8$s일 %6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-lt-rLT/donottranslate-cldr.xml b/core/res/res/values-lt-rLT/donottranslate-cldr.xml index 17f228d04bf2..636a180cf9cd 100644 --- a/core/res/res/values-lt-rLT/donottranslate-cldr.xml +++ b/core/res/res/values-lt-rLT/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s m. %2$s %3$s d. - %7$s %8$s d.</string> <string name="same_month_mdy1_mdy2">%9$s m. %2$s %3$s d.-%8$s d.</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s m. %2$s %3$s d.,%1$s - %7$s %8$s d.,%6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-lv-rLV/donottranslate-cldr.xml b/core/res/res/values-lv-rLV/donottranslate-cldr.xml index e6c3b796953b..3dec1d2e3726 100644 --- a/core/res/res/values-lv-rLV/donottranslate-cldr.xml +++ b/core/res/res/values-lv-rLV/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s. gada %3$s. %2$s - %8$s. %7$s</string> <string name="same_month_mdy1_mdy2">%9$s. gada %3$s.-%8$s. %2$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %9$s. gada %3$s. %2$s - %6$s, y. gada %8$s. %7$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-nb/donottranslate-cldr.xml b/core/res/res/values-nb/donottranslate-cldr.xml index 17c9b24e7564..ecf01118298f 100644 --- a/core/res/res/values-nb/donottranslate-cldr.xml +++ b/core/res/res/values-nb/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s–%8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.–%8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s. %2$s–%6$s %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-nl-rBE/donottranslate-cldr.xml b/core/res/res/values-nl-rBE/donottranslate-cldr.xml index 72a4694bdb0d..5b4cbf7826cd 100644 --- a/core/res/res/values-nl-rBE/donottranslate-cldr.xml +++ b/core/res/res/values-nl-rBE/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-nl-rNL/donottranslate-cldr.xml b/core/res/res/values-nl-rNL/donottranslate-cldr.xml index 549d816deccb..b6231b622790 100644 --- a/core/res/res/values-nl-rNL/donottranslate-cldr.xml +++ b/core/res/res/values-nl-rNL/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-nl/donottranslate-cldr.xml b/core/res/res/values-nl/donottranslate-cldr.xml index 549d816deccb..b6231b622790 100644 --- a/core/res/res/values-nl/donottranslate-cldr.xml +++ b/core/res/res/values-nl/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s - %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-pl-rPL/donottranslate-cldr.xml b/core/res/res/values-pl-rPL/donottranslate-cldr.xml index c4bed0d716ce..4ad17bf11c46 100644 --- a/core/res/res/values-pl-rPL/donottranslate-cldr.xml +++ b/core/res/res/values-pl-rPL/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-pl/donottranslate-cldr.xml b/core/res/res/values-pl/donottranslate-cldr.xml index c4bed0d716ce..4ad17bf11c46 100644 --- a/core/res/res/values-pl/donottranslate-cldr.xml +++ b/core/res/res/values-pl/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-pt-rBR/donottranslate-cldr.xml b/core/res/res/values-pt-rBR/donottranslate-cldr.xml index 7b3304c88273..47290552dd2e 100644 --- a/core/res/res/values-pt-rBR/donottranslate-cldr.xml +++ b/core/res/res/values-pt-rBR/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s de %2$s - %8$s de %7$s de %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s de %2$s de %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s de %2$s - %6$s, %8$s de %7$s de %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-pt-rPT/donottranslate-cldr.xml b/core/res/res/values-pt-rPT/donottranslate-cldr.xml index b14dbcb08483..be0fdb7d1d83 100644 --- a/core/res/res/values-pt-rPT/donottranslate-cldr.xml +++ b/core/res/res/values-pt-rPT/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s de %2$s - %8$s de %7$s de %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s de %2$s de %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s de %2$s - %6$s, %8$s de %7$s de %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-pt/donottranslate-cldr.xml b/core/res/res/values-pt/donottranslate-cldr.xml index 7b3304c88273..47290552dd2e 100644 --- a/core/res/res/values-pt/donottranslate-cldr.xml +++ b/core/res/res/values-pt/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s de %2$s - %8$s de %7$s de %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s de %2$s de %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s de %2$s - %6$s, %8$s de %7$s de %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ro-rRO/donottranslate-cldr.xml b/core/res/res/values-ro-rRO/donottranslate-cldr.xml index 3672c944ef12..4622445ca6c2 100644 --- a/core/res/res/values-ro-rRO/donottranslate-cldr.xml +++ b/core/res/res/values-ro-rRO/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s - %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ru-rRU/donottranslate-cldr.xml b/core/res/res/values-ru-rRU/donottranslate-cldr.xml index 2b8c235e121b..21c06ff3a0ab 100644 --- a/core/res/res/values-ru-rRU/donottranslate-cldr.xml +++ b/core/res/res/values-ru-rRU/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s г.</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s г.</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s - %8$s %7$s %9$s г.</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-ru/donottranslate-cldr.xml b/core/res/res/values-ru/donottranslate-cldr.xml index 2b8c235e121b..21c06ff3a0ab 100644 --- a/core/res/res/values-ru/donottranslate-cldr.xml +++ b/core/res/res/values-ru/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s г.</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s г.</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s - %8$s %7$s %9$s г.</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-sk-rSK/donottranslate-cldr.xml b/core/res/res/values-sk-rSK/donottranslate-cldr.xml index ea2a5abc39c3..2b3c6d907574 100644 --- a/core/res/res/values-sk-rSK/donottranslate-cldr.xml +++ b/core/res/res/values-sk-rSK/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s. - %8$s. %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-sl-rSI/donottranslate-cldr.xml b/core/res/res/values-sl-rSI/donottranslate-cldr.xml index b9c30c90c66d..2b2b9c31450e 100644 --- a/core/res/res/values-sl-rSI/donottranslate-cldr.xml +++ b/core/res/res/values-sl-rSI/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s. – %8$s. %7$s. %9$s</string> <string name="same_month_mdy1_mdy2">%3$s.–%8$s. %2$s. %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s., %3$s. %2$s. – %6$s., %8$s. %7$s. %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-sr-rRS/donottranslate-cldr.xml b/core/res/res/values-sr-rRS/donottranslate-cldr.xml index f88de6b1f1e5..55ca96829f4e 100644 --- a/core/res/res/values-sr-rRS/donottranslate-cldr.xml +++ b/core/res/res/values-sr-rRS/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s. %2$s - %8$s. %7$s %9$s.</string> <string name="same_month_mdy1_mdy2">%3$s.-%8$s. %2$s %9$s.</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s. %2$s - %6$s, %8$s. %7$s %9$s.</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-sv-rSE/donottranslate-cldr.xml b/core/res/res/values-sv-rSE/donottranslate-cldr.xml index c846719c800d..a6ffc9aa77af 100644 --- a/core/res/res/values-sv-rSE/donottranslate-cldr.xml +++ b/core/res/res/values-sv-rSE/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s–%8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s–%6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-th-rTH/donottranslate-cldr.xml b/core/res/res/values-th-rTH/donottranslate-cldr.xml index 53cd4d7cf0e6..b3c76a33abf5 100644 --- a/core/res/res/values-th-rTH/donottranslate-cldr.xml +++ b/core/res/res/values-th-rTH/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s – %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s %3$s %2$s – %6$s %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-tr-rTR/donottranslate-cldr.xml b/core/res/res/values-tr-rTR/donottranslate-cldr.xml index 2475b2100385..d61230593437 100644 --- a/core/res/res/values-tr-rTR/donottranslate-cldr.xml +++ b/core/res/res/values-tr-rTR/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s - %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s-%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%3$s %2$s %9$s %1$s - %8$s %7$s y %6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-uk-rUA/donottranslate-cldr.xml b/core/res/res/values-uk-rUA/donottranslate-cldr.xml index 51dabd0ce6ac..55d39832c12c 100644 --- a/core/res/res/values-uk-rUA/donottranslate-cldr.xml +++ b/core/res/res/values-uk-rUA/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%3$s %2$s – %8$s %7$s %9$s</string> <string name="same_month_mdy1_mdy2">%3$s–%8$s %2$s %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %3$s %2$s – %6$s, %8$s %7$s %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-vi-rVN/donottranslate-cldr.xml b/core/res/res/values-vi-rVN/donottranslate-cldr.xml index 0d9eebc8c063..71178cc85c99 100644 --- a/core/res/res/values-vi-rVN/donottranslate-cldr.xml +++ b/core/res/res/values-vi-rVN/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">Ngày %3$s tháng %2$s - Ngày %8$s tháng %7$s năm %9$s</string> <string name="same_month_mdy1_mdy2">Ngày %3$s tháng %2$s - Ngày %8$s tháng M năm %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, ngày %3$s %2$s - %6$s, ngày %8$s %7$s năm %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-zh-rCN/donottranslate-cldr.xml b/core/res/res/values-zh-rCN/donottranslate-cldr.xml index 6685a7d9302c..8c386a4b18d8 100644 --- a/core/res/res/values-zh-rCN/donottranslate-cldr.xml +++ b/core/res/res/values-zh-rCN/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s年%2$s%3$s日至%7$s月%8$s日</string> <string name="same_month_mdy1_mdy2">%9$s年%2$s%3$s日至%8$s日</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s年%2$s%3$s日%1$s至%7$s月%8$s日%6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values-zh-rTW/donottranslate-cldr.xml b/core/res/res/values-zh-rTW/donottranslate-cldr.xml index 6685a7d9302c..8c386a4b18d8 100644 --- a/core/res/res/values-zh-rTW/donottranslate-cldr.xml +++ b/core/res/res/values-zh-rTW/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%9$s年%2$s%3$s日至%7$s月%8$s日</string> <string name="same_month_mdy1_mdy2">%9$s年%2$s%3$s日至%8$s日</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%9$s年%2$s%3$s日%1$s至%7$s月%8$s日%6$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values/donottranslate-cldr.xml b/core/res/res/values/donottranslate-cldr.xml index 56c58e2444c0..286cc0e554f2 100644 --- a/core/res/res/values/donottranslate-cldr.xml +++ b/core/res/res/values/donottranslate-cldr.xml @@ -143,4 +143,5 @@ <string name="same_year_mdy1_mdy2">%2$s %3$s – %7$s %8$s, %9$s</string> <string name="same_month_mdy1_mdy2">%2$s %3$s – %8$s, %9$s</string> <string name="same_year_wday1_mdy1_wday2_mdy2">%1$s, %2$s %3$s – %6$s, %7$s %8$s, %9$s</string> + <string name="short_format_month">%b</string> </resources> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 05a20aa6cf8d..bdfccd6b6f3e 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -1302,6 +1302,9 @@ <string name="battery_low_percent_format">less than <xliff:g id="number">%d%%</xliff:g> remaining.</string> + <!-- When the battery is low, this is the label of the button to go to the + power usage activity to find out what drained the battery. --> + <string name="battery_low_why">Why?</string> <!-- Title of the alert when something went wrong in the factory test. --> <string name="factorytest_failed">Factory test failed</string> diff --git a/graphics/java/android/graphics/Canvas.java b/graphics/java/android/graphics/Canvas.java index 06d53e3a99f9..4498e1a2e117 100644 --- a/graphics/java/android/graphics/Canvas.java +++ b/graphics/java/android/graphics/Canvas.java @@ -1404,7 +1404,11 @@ public class Canvas { protected void finalize() throws Throwable { super.finalize(); - finalizer(mNativeCanvas); + // If the constructor threw an exception before setting mNativeCanvas, the native finalizer + // must not be invoked. + if (mNativeCanvas != 0) { + finalizer(mNativeCanvas); + } } /** diff --git a/include/utils/BackupHelpers.h b/include/utils/BackupHelpers.h index fa7f8d5cf827..c78b99a8dca0 100644 --- a/include/utils/BackupHelpers.h +++ b/include/utils/BackupHelpers.h @@ -19,6 +19,7 @@ #include <utils/Errors.h> #include <utils/String8.h> +#include <utils/KeyedVector.h> namespace android { @@ -32,6 +33,27 @@ typedef struct { int dataSize; // size of the data, not including the padding, -1 means delete } entity_header_v1; +struct SnapshotHeader { + int magic0; + int fileCount; + int magic1; + int totalSize; +}; + +struct FileState { + int modTime_sec; + int modTime_nsec; + int size; + int crc32; + int nameLen; +}; + +struct FileRec { + String8 file; + bool deleted; + FileState s; +}; + /** * Writes the data. @@ -94,11 +116,25 @@ private: int type; entity_header_v1 entity; } m_header; + String8 m_key; }; int back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD, char const* const* files, char const* const *keys, int fileCount); +class RestoreHelperBase +{ +public: + RestoreHelperBase(); + ~RestoreHelperBase(); + + status_t WriteFile(const String8& filename, BackupDataReader* in); + status_t WriteSnapshot(int fd); + +private: + void* m_buf; + KeyedVector<String8,FileRec> m_files; +}; #define TEST_BACKUP_HELPERS 1 diff --git a/libs/surfaceflinger/DisplayHardware/DisplayHardware.cpp b/libs/surfaceflinger/DisplayHardware/DisplayHardware.cpp index f14d7e99c2d3..eec645edc907 100644 --- a/libs/surfaceflinger/DisplayHardware/DisplayHardware.cpp +++ b/libs/surfaceflinger/DisplayHardware/DisplayHardware.cpp @@ -189,9 +189,14 @@ void DisplayHardware::init(uint32_t dpy) char property[PROPERTY_VALUE_MAX]; - if (property_get("ro.sf.lcd_density", property, NULL) <= 0) { - LOGW("ro.sf.lcd_density not defined, using 160 dpi by default."); - strcpy(property, "160"); + /* Read density from build-specific ro.sf.lcd_density property + * except if it is overriden by qemu.sf.lcd_density. + */ + if (property_get("qemu.sf.lcd_density", property, NULL) <= 0) { + if (property_get("ro.sf.lcd_density", property, NULL) <= 0) { + LOGW("ro.sf.lcd_density not defined, using 160 dpi by default."); + strcpy(property, "160"); + } } mDensity = atoi(property) * (1.0f/160.0f); diff --git a/libs/utils/BackupData.cpp b/libs/utils/BackupData.cpp index 34b37edd28a0..6a7f05646472 100644 --- a/libs/utils/BackupData.cpp +++ b/libs/utils/BackupData.cpp @@ -205,12 +205,17 @@ BackupDataReader::ReadNextHeader(bool* done, int* type) amt = read(m_fd, &m_header, sizeof(m_header)); *done = m_done = (amt == 0); CHECK_SIZE(amt, sizeof(m_header)); + m_pos += sizeof(m_header); + if (type) { + *type = m_header.type; + } // validate and fix up the fields. m_header.type = fromlel(m_header.type); switch (m_header.type) { case BACKUP_HEADER_ENTITY_V1: + { m_header.entity.keyLen = fromlel(m_header.entity.keyLen); if (m_header.entity.keyLen <= 0) { LOGD("Entity header at %d has keyLen<=0: 0x%08x\n", (int)m_pos, @@ -219,15 +224,27 @@ BackupDataReader::ReadNextHeader(bool* done, int* type) } m_header.entity.dataSize = fromlel(m_header.entity.dataSize); m_entityCount++; + + // read the rest of the header (filename) + size_t size = m_header.entity.keyLen; + char* buf = m_key.lockBuffer(size); + if (buf == NULL) { + m_status = ENOMEM; + return m_status; + } + int amt = read(m_fd, buf, size+1); + CHECK_SIZE(amt, (int)size+1); + m_key.unlockBuffer(size); + m_pos += size+1; + SKIP_PADDING(); + m_dataEndPos = m_pos + m_header.entity.dataSize; + break; + } default: LOGD("Chunk header at %d has invalid type: 0x%08x", (int)m_pos, (int)m_header.type); m_status = EINVAL; } - m_pos += sizeof(m_header); - if (type) { - *type = m_header.type; - } return m_status; } @@ -247,20 +264,8 @@ BackupDataReader::ReadEntityHeader(String8* key, size_t* dataSize) if (m_header.type != BACKUP_HEADER_ENTITY_V1) { return EINVAL; } - size_t size = m_header.entity.keyLen; - char* buf = key->lockBuffer(size); - if (key == NULL) { - key->unlockBuffer(); - m_status = ENOMEM; - return m_status; - } - int amt = read(m_fd, buf, size+1); - CHECK_SIZE(amt, (int)size+1); - key->unlockBuffer(size); - m_pos += size+1; + *key = m_key; *dataSize = m_header.entity.dataSize; - SKIP_PADDING(); - m_dataEndPos = m_pos + *dataSize; return NO_ERROR; } @@ -285,20 +290,24 @@ ssize_t BackupDataReader::ReadEntityData(void* data, size_t size) { if (m_status != NO_ERROR) { - return m_status; + return -1; } int remaining = m_dataEndPos - m_pos; //LOGD("ReadEntityData size=%d m_pos=0x%x m_dataEndPos=0x%x remaining=%d\n", // size, m_pos, m_dataEndPos, remaining); - if (size > remaining) { - size = remaining; - } if (remaining <= 0) { return 0; } + if (size > remaining) { + size = remaining; + } + //LOGD(" reading %d bytes", size); int amt = read(m_fd, data, size); - CHECK_SIZE(amt, (int)size); - m_pos += size; + if (amt < 0) { + m_status = errno; + return -1; + } + m_pos += amt; return amt; } diff --git a/libs/utils/BackupHelpers.cpp b/libs/utils/BackupHelpers.cpp index c1d5404eebc7..d65a457f3987 100644 --- a/libs/utils/BackupHelpers.cpp +++ b/libs/utils/BackupHelpers.cpp @@ -47,27 +47,6 @@ namespace android { #define LOGP(x...) LOGD(x) #endif -struct SnapshotHeader { - int magic0; - int fileCount; - int magic1; - int totalSize; -}; - -struct FileState { - int modTime_sec; - int modTime_nsec; - int size; - int crc32; - int nameLen; -}; - -struct FileRec { - char const* file; // this object does not own this string - bool deleted; - FileState s; -}; - const static int ROUND_UP[4] = { 0, 3, 2, 1 }; static inline int @@ -310,7 +289,8 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD for (int i=0; i<fileCount; i++) { String8 key(keys[i]); FileRec r; - char const* file = r.file = files[i]; + char const* file = files[i]; + r.file = file; struct stat st; err = stat(file, &st); @@ -351,20 +331,20 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD } else if (cmp > 0) { // file added - LOGP("file added: %s", g.file); - write_update_file(dataStream, q, g.file); + LOGP("file added: %s", g.file.string()); + write_update_file(dataStream, q, g.file.string()); m++; } else { // both files exist, check them const FileState& f = oldSnapshot.valueAt(n); - int fd = open(g.file, O_RDONLY); + int fd = open(g.file.string(), O_RDONLY); if (fd < 0) { // We can't open the file. Don't report it as a delete either. Let the // server keep the old version. Maybe they'll be able to deal with it // on restore. - LOGP("Unable to open file %s - skipping", g.file); + LOGP("Unable to open file %s - skipping", g.file.string()); } else { g.s.crc32 = compute_crc32(fd); @@ -375,7 +355,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD g.s.modTime_sec, g.s.modTime_nsec, g.s.size, g.s.crc32); if (f.modTime_sec != g.s.modTime_sec || f.modTime_nsec != g.s.modTime_nsec || f.size != g.s.size || f.crc32 != g.s.crc32) { - write_update_file(dataStream, fd, p, g.file); + write_update_file(dataStream, fd, p, g.file.string()); } close(fd); @@ -395,7 +375,7 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD while (m<fileCount) { const String8& q = newSnapshot.keyAt(m); FileRec& g = newSnapshot.editValueAt(m); - write_update_file(dataStream, q, g.file); + write_update_file(dataStream, q, g.file.string()); m++; } @@ -404,6 +384,86 @@ back_up_files(int oldSnapshotFD, BackupDataWriter* dataStream, int newSnapshotFD return 0; } +#define RESTORE_BUF_SIZE (8*1024) + +RestoreHelperBase::RestoreHelperBase() +{ + m_buf = malloc(RESTORE_BUF_SIZE); +} + +RestoreHelperBase::~RestoreHelperBase() +{ + free(m_buf); +} + +status_t +RestoreHelperBase::WriteFile(const String8& filename, BackupDataReader* in) +{ + ssize_t err; + size_t dataSize; + String8 key; + int fd; + void* buf = m_buf; + ssize_t amt; + int mode; + int crc; + struct stat st; + FileRec r; + + err = in->ReadEntityHeader(&key, &dataSize); + if (err != NO_ERROR) { + return err; + } + + // TODO: World readable/writable for now. + mode = 0666; + + // Write the file and compute the crc + crc = crc32(0L, Z_NULL, 0); + fd = open(filename.string(), O_CREAT|O_RDWR|O_TRUNC, mode); + if (fd == -1) { + LOGW("Could not open file %s -- %s", filename.string(), strerror(errno)); + return errno; + } + + while ((amt = in->ReadEntityData(buf, RESTORE_BUF_SIZE)) > 0) { + err = write(fd, buf, amt); + if (err != amt) { + close(fd); + LOGW("Error '%s' writing '%s'", strerror(errno), filename.string()); + return errno; + } + crc = crc32(crc, (Bytef*)buf, amt); + } + + close(fd); + + // Record for the snapshot + err = stat(filename.string(), &st); + if (err != 0) { + LOGW("Error stating file that we just created %s", filename.string()); + return errno; + } + + r.file = filename; + r.deleted = false; + r.s.modTime_sec = st.st_mtime; + r.s.modTime_nsec = 0; // workaround sim breakage + //r.s.modTime_nsec = st.st_mtime_nsec; + r.s.size = st.st_size; + r.s.crc32 = crc; + + m_files.add(key, r); + + return NO_ERROR; +} + +status_t +RestoreHelperBase::WriteSnapshot(int fd) +{ + return write_snapshot_file(fd, m_files);; +} + #if TEST_BACKUP_HELPERS #define SCRATCH_DIR "/data/backup_helper_test/" @@ -560,7 +620,6 @@ backup_helper_test_four() FileState states[4]; FileRec r; r.deleted = false; - r.file = NULL; states[0].modTime_sec = 0xfedcba98; states[0].modTime_nsec = 0xdeadbeef; diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 6f430c43c09e..3db52ebd00bf 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -158,14 +158,18 @@ public class SettingsProvider extends ContentProvider { getContext().checkCallingOrSelfPermission( android.Manifest.permission.WRITE_SECURE_SETTINGS) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Cannot write secure settings table"); - + throw new SecurityException( + String.format("Permission denial: writing to secure settings requires %1$s", + android.Manifest.permission.WRITE_SECURE_SETTINGS)); + // TODO: Move gservices into its own provider so we don't need this nonsense. } else if ("gservices".equals(args.table) && getContext().checkCallingOrSelfPermission( android.Manifest.permission.WRITE_GSERVICES) != PackageManager.PERMISSION_GRANTED) { - throw new SecurityException("Cannot write gservices table"); + throw new SecurityException( + String.format("Permission denial: writing to gservices settings requires %1$s", + android.Manifest.permission.WRITE_GSERVICES)); } } diff --git a/packages/TtsService/src/android/tts/TtsService.java b/packages/TtsService/src/android/tts/TtsService.java index f7dd0e80c18d..e9c4ab74d27f 100755 --- a/packages/TtsService/src/android/tts/TtsService.java +++ b/packages/TtsService/src/android/tts/TtsService.java @@ -15,10 +15,8 @@ */ package android.tts; -import android.speech.tts.ITts.Stub; -import android.speech.tts.ITtsCallback; - import android.app.Service; +import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -31,6 +29,9 @@ import android.os.IBinder; import android.os.RemoteCallbackList; import android.os.RemoteException; import android.preference.PreferenceManager; +import android.speech.tts.ITts.Stub; +import android.speech.tts.ITtsCallback; +import android.speech.tts.TextToSpeech; import android.util.Log; import java.util.ArrayList; import java.util.Arrays; @@ -101,7 +102,7 @@ public class TtsService extends Service implements OnCompletionListener { private MediaPlayer mPlayer; private TtsService mSelf; - private SharedPreferences prefs; + private ContentResolver mResolver; private final ReentrantLock speechQueueLock = new ReentrantLock(); private final ReentrantLock synthesizerLock = new ReentrantLock(); @@ -113,9 +114,7 @@ public class TtsService extends Service implements OnCompletionListener { super.onCreate(); Log.i("TTS", "TTS starting"); - // TODO: Make this work when the settings are done in the main Settings - // app. - prefs = PreferenceManager.getDefaultSharedPreferences(this); + mResolver = getContentResolver(); String soLibPath = "/system/lib/libttspico.so"; nativeSynth = new SynthProxy(soLibPath); @@ -129,10 +128,7 @@ public class TtsService extends Service implements OnCompletionListener { mSpeechQueue = new ArrayList<SpeechItem>(); mPlayer = null; - // TODO set default settings - //setLanguage(prefs.getString("lang_pref", "en-rUS")); - setLanguage("eng", "USA", ""); - setSpeechRate(Integer.parseInt(prefs.getString("rate_pref", "140"))); + setDefaultSettings(); } @Override @@ -147,26 +143,50 @@ public class TtsService extends Service implements OnCompletionListener { mCallbacks.kill(); } + + private void setDefaultSettings() { + + // TODO handle default language + setLanguage("eng", "USA", ""); + + // speech rate + setSpeechRate(getDefaultRate()); + + // TODO handle default pitch + } + + + private boolean isDefaultEnforced() { + return (android.provider.Settings.Secure.getInt(mResolver, + android.provider.Settings.Secure.TTS_USE_DEFAULTS, + TextToSpeech.Engine.FALLBACK_TTS_USE_DEFAULTS) + == 1 ); + } + + + private int getDefaultRate() { + return android.provider.Settings.Secure.getInt(mResolver, + android.provider.Settings.Secure.TTS_DEFAULT_RATE, + TextToSpeech.Engine.FALLBACK_TTS_DEFAULT_RATE); + } + + private void setSpeechRate(int rate) { - if (prefs.getBoolean("override_pref", false)) { - // This is set to the default here so that the preview in the prefs - // activity will show the change without a restart, even if apps are - // not allowed to change the defaults. - rate = Integer.parseInt(prefs.getString("rate_pref", "140")); + if (isDefaultEnforced()) { + nativeSynth.setSpeechRate(getDefaultRate()); + } else { + nativeSynth.setSpeechRate(rate); } - nativeSynth.setSpeechRate(rate); } private void setLanguage(String lang, String country, String variant) { - if (prefs.getBoolean("override_pref", false)) { - // This is set to the default here so that the preview in the prefs - // activity will show the change without a restart, even if apps are - // not - // allowed to change the defaults. - lang = prefs.getString("lang_pref", "en-rUS"); - } Log.v("TTS", "TtsService.setLanguage("+lang+", "+country+", "+variant+")"); - nativeSynth.setLanguage(lang, country, variant); + if (isDefaultEnforced()) { + nativeSynth.setLanguage(lang, country, variant); + } else { + // TODO handle default language + nativeSynth.setLanguage("eng", "USA", ""); + } } /** diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index d842d341fa35..fa88111a2707 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -21,14 +21,17 @@ import android.app.IActivityManager; import android.app.IApplicationThread; import android.app.IBackupAgent; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.PackageManager; +import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; import android.os.Bundle; @@ -48,9 +51,10 @@ import android.backup.BackupManager; import android.backup.RestoreSet; import com.android.internal.backup.LocalTransport; -import com.android.internal.backup.GoogleTransport; import com.android.internal.backup.IBackupTransport; +import com.android.server.PackageManagerBackupAgent; + import java.io.EOFException; import java.io.File; import java.io.FileDescriptor; @@ -69,6 +73,7 @@ class BackupManagerService extends IBackupManager.Stub { private static final String TAG = "BackupManagerService"; private static final boolean DEBUG = true; + // Default time to wait after data changes before we back up the data private static final long COLLECTION_INTERVAL = 1000; //private static final long COLLECTION_INTERVAL = 3 * 60 * 1000; @@ -103,6 +108,9 @@ class BackupManagerService extends IBackupManager.Stub { // Backups that we haven't started yet. private HashMap<ApplicationInfo,BackupRequest> mPendingBackups = new HashMap<ApplicationInfo,BackupRequest>(); + // Do we need to back up the package manager metadata on the next pass? + private boolean mDoPackageManager; + private static final String PACKAGE_MANAGER_SENTINEL = "@pm@"; // Backups that we have started. These are separate to prevent starvation // if an app keeps re-enqueuing itself. private ArrayList<BackupRequest> mBackupQueue; @@ -122,7 +130,7 @@ class BackupManagerService extends IBackupManager.Stub { // Current active transport & restore session private int mTransportId; - private IBackupTransport mTransport; + private IBackupTransport mLocalTransport, mGoogleTransport; private RestoreSession mActiveRestoreSession; private File mStateDir; @@ -146,15 +154,26 @@ class BackupManagerService extends IBackupManager.Stub { mJournalDir.mkdirs(); makeJournalLocked(); // okay because no other threads are running yet - // Build our mapping of uid to backup client services + // Build our mapping of uid to backup client services. This implicitly + // schedules a backup pass on the Package Manager metadata the first + // time anything needs to be backed up. synchronized (mBackupParticipants) { addPackageParticipantsLocked(null); } - // Stand up our default transport - //!!! TODO: default to cloud transport, not local - mTransportId = BackupManager.TRANSPORT_LOCAL; - mTransport = createTransport(mTransportId); + // Set up our transport options and initialize the default transport + // TODO: Have transports register themselves somehow? + // TODO: Don't create transports that we don't need to? + //mTransportId = BackupManager.TRANSPORT_LOCAL; + mTransportId = BackupManager.TRANSPORT_GOOGLE; + mLocalTransport = new LocalTransport(context); // This is actually pretty cheap + mGoogleTransport = null; + + // Attach to the Google backup transport. + Intent intent = new Intent().setComponent(new ComponentName( + "com.google.android.backup", + "com.google.android.backup.BackupTransportService")); + context.bindService(intent, mGoogleConnection, Context.BIND_AUTO_CREATE); // Now that we know about valid backup participants, parse any // leftover journal files and schedule a new backup pass @@ -249,6 +268,19 @@ class BackupManagerService extends IBackupManager.Stub { } }; + // ----- Track connection to GoogleBackupTransport service ----- + ServiceConnection mGoogleConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName name, IBinder service) { + if (DEBUG) Log.v(TAG, "Connected to Google transport"); + mGoogleTransport = IBackupTransport.Stub.asInterface(service); + } + + public void onServiceDisconnected(ComponentName name) { + if (DEBUG) Log.v(TAG, "Disconnected from Google transport"); + mGoogleTransport = null; + } + }; + // ----- Run the actual backup process asynchronously ----- private class BackupHandler extends Handler { @@ -256,6 +288,13 @@ class BackupManagerService extends IBackupManager.Stub { switch (msg.what) { case MSG_RUN_BACKUP: + { + IBackupTransport transport = getTransport(mTransportId); + if (transport == null) { + Log.v(TAG, "Backup requested but no transport available"); + break; + } + // snapshot the pending-backup set and work on that File oldJournal = mJournal; synchronized (mQueueLock) { @@ -288,8 +327,10 @@ class BackupManagerService extends IBackupManager.Stub { // deleted. If we crash prior to that, the old journal is parsed // at next boot and the journaled requests fulfilled. } - (new PerformBackupThread(mTransport, mBackupQueue, oldJournal)).run(); + + (new PerformBackupThread(transport, mBackupQueue, oldJournal)).start(); break; + } case MSG_RUN_FULL_BACKUP: break; @@ -298,7 +339,7 @@ class BackupManagerService extends IBackupManager.Stub { { int token = msg.arg1; IBackupTransport transport = (IBackupTransport)msg.obj; - (new PerformRestoreThread(transport, token)).run(); + (new PerformRestoreThread(transport, token)).start(); break; } } @@ -332,12 +373,13 @@ class BackupManagerService extends IBackupManager.Stub { mBackupParticipants.put(uid, set); } set.add(app); + backUpPackageManagerData(); } } } - // Remove the given package's backup services from our known active set. If - // 'packageName' is null, *all* backup services will be removed. + // Remove the given package's entry from our known active set. If + // 'packageName' is null, *all* participating apps will be removed. void removePackageParticipantsLocked(String packageName) { if (DEBUG) Log.v(TAG, "removePackageParticipantsLocked: " + packageName); List<ApplicationInfo> allApps = null; @@ -375,6 +417,7 @@ class BackupManagerService extends IBackupManager.Stub { for (ApplicationInfo entry: set) { if (entry.packageName.equals(app.packageName)) { set.remove(entry); + backUpPackageManagerData(); break; } } @@ -387,6 +430,7 @@ class BackupManagerService extends IBackupManager.Stub { // Returns the set of all applications that define an android:backupAgent attribute private List<ApplicationInfo> allAgentApps() { + // !!! TODO: cache this and regenerate only when necessary List<ApplicationInfo> allApps = mPackageManager.getInstalledApplications(0); int N = allApps.size(); if (N > 0) { @@ -416,25 +460,35 @@ class BackupManagerService extends IBackupManager.Stub { addPackageParticipantsLockedInner(packageName, allApps); } - // Instantiate the given transport - private IBackupTransport createTransport(int transportID) { - IBackupTransport transport = null; + private void backUpPackageManagerData() { + // No need to schedule a backup just for the metadata; just piggyback on + // the next actual data backup. + synchronized(this) { + mDoPackageManager = true; + } + } + + // The queue lock should be held when scheduling a backup pass + private void scheduleBackupPassLocked(long timeFromNowMillis) { + mBackupHandler.removeMessages(MSG_RUN_BACKUP); + mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, timeFromNowMillis); + } + + // Return the given transport + private IBackupTransport getTransport(int transportID) { switch (transportID) { case BackupManager.TRANSPORT_LOCAL: - if (DEBUG) Log.v(TAG, "Initializing local transport"); - transport = new LocalTransport(mContext); - break; + Log.v(TAG, "Supplying local transport"); + return mLocalTransport; case BackupManager.TRANSPORT_GOOGLE: - if (DEBUG) Log.v(TAG, "Initializing Google transport"); - //!!! TODO: stand up the google backup transport for real here - transport = new GoogleTransport(); - break; + Log.v(TAG, "Supplying Google transport"); + return mGoogleTransport; default: Log.e(TAG, "Asked for unknown transport " + transportID); + return null; } - return transport; } // fire off a backup agent, blocking until it attaches or times out @@ -533,7 +587,29 @@ class BackupManagerService extends IBackupManager.Stub { return; } - // The transport is up and running; now run all the backups in our queue + // The transport is up and running. First, back up the package manager + // metadata if necessary + boolean doPackageManager; + synchronized (BackupManagerService.this) { + doPackageManager = mDoPackageManager; + mDoPackageManager = false; + } + if (doPackageManager) { + // The package manager doesn't have a proper <application> etc, but since + // it's running here in the system process we can just set up its agent + // directly and use a synthetic BackupRequest. + if (DEBUG) Log.i(TAG, "Running PM backup pass as well"); + + PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( + mPackageManager, allAgentApps()); + BackupRequest pmRequest = new BackupRequest(new ApplicationInfo(), false); + pmRequest.appInfo.packageName = PACKAGE_MANAGER_SENTINEL; + processOneBackup(pmRequest, + IBackupAgent.Stub.asInterface(pmAgent.onBind()), + mTransport); + } + + // Now run all the backups in our queue doQueuedBackups(mTransport); // Finally, tear down the transport @@ -584,8 +660,15 @@ class BackupManagerService extends IBackupManager.Stub { // Look up the package info & signatures. This is first so that if it // throws an exception, there's no file setup yet that would need to // be unraveled. - PackageInfo packInfo = mPackageManager.getPackageInfo(packageName, + PackageInfo packInfo; + if (packageName.equals(PACKAGE_MANAGER_SENTINEL)) { + // The metadata 'package' is synthetic + packInfo = new PackageInfo(); + packInfo.packageName = packageName; + } else { + packInfo = mPackageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES); + } // !!! TODO: get the state file dir from the transport File savedStateName = new File(mStateDir, packageName); @@ -673,6 +756,38 @@ class BackupManagerService extends IBackupManager.Stub { return null; } + private boolean signaturesMatch(Signature[] storedSigs, Signature[] deviceSigs) { + // Allow unsigned apps, but not signed on one device and unsigned on the other + // !!! TODO: is this the right policy? + if ((storedSigs == null || storedSigs.length == 0) + && (deviceSigs == null || deviceSigs.length == 0)) { + return true; + } + if (storedSigs == null || deviceSigs == null) { + return false; + } + + // !!! TODO: this demands that every stored signature match one + // that is present on device, and does not demand the converse. + // Is this this right policy? + int nStored = storedSigs.length; + int nDevice = deviceSigs.length; + + for (int i=0; i < nStored; i++) { + boolean match = false; + for (int j=0; j < nDevice; j++) { + if (storedSigs[i].equals(deviceSigs[j])) { + match = true; + break; + } + } + if (!match) { + return false; + } + } + return true; + } + class PerformRestoreThread extends Thread { private IBackupTransport mTransport; private int mToken; @@ -717,6 +832,13 @@ class BackupManagerService extends IBackupManager.Stub { // !!! TODO: pick out the set for this token mImage = images[0]; + // Pull the Package Manager metadata from the restore set first + PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent( + mPackageManager, allAgentApps()); + PackageInfo pmApp = new PackageInfo(); + pmApp.packageName = PACKAGE_MANAGER_SENTINEL; + processOneRestore(pmApp, IBackupAgent.Stub.asInterface(pmAgent.onBind())); + // build the set of apps we will attempt to restore PackageInfo[] packages = mTransport.getAppSet(mImage.token); HashSet<PackageInfo> appsToRestore = new HashSet<PackageInfo>(); @@ -724,7 +846,15 @@ class BackupManagerService extends IBackupManager.Stub { // get the real PackageManager idea of the package PackageInfo app = isRestorable(pkg); if (app != null) { - appsToRestore.add(app); + // Validate against the backed-up signature block, too + Signature[] storedSigs + = pmAgent.getRestoredSignatures(app.packageName); + // !!! TODO: check app version here as well + if (signaturesMatch(storedSigs, app.signatures)) { + appsToRestore.add(app); + } else { + Log.w(TAG, "Sig mismatch on restore of " + app.packageName); + } } } @@ -895,8 +1025,7 @@ class BackupManagerService extends IBackupManager.Stub { // Schedule a backup pass in a few minutes. As backup-eligible data // keeps changing, continue to defer the backup pass until things // settle down, to avoid extra overhead. - mBackupHandler.removeMessages(MSG_RUN_BACKUP); - mBackupHandler.sendEmptyMessageDelayed(MSG_RUN_BACKUP, COLLECTION_INTERVAL); + scheduleBackupPassLocked(COLLECTION_INTERVAL); } } else { Log.w(TAG, "dataChanged but no participant pkg " + packageName); @@ -922,14 +1051,14 @@ class BackupManagerService extends IBackupManager.Stub { if (DEBUG) Log.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { - mBackupHandler.removeMessages(MSG_RUN_BACKUP); - mBackupHandler.sendEmptyMessage(MSG_RUN_BACKUP); + scheduleBackupPassLocked(0); } } // Report the currently active transport public int getCurrentTransport() { mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport"); + Log.v(TAG, "getCurrentTransport() returning " + mTransportId); return mTransportId; } @@ -937,14 +1066,9 @@ class BackupManagerService extends IBackupManager.Stub { public int selectBackupTransport(int transportId) { mContext.enforceCallingPermission("android.permission.BACKUP", "selectBackupTransport"); - int prevTransport = -1; - IBackupTransport newTransport = createTransport(transportId); - if (newTransport != null) { - // !!! TODO: a method on the old transport that says it's being deactivated? - mTransport = newTransport; - prevTransport = mTransportId; - mTransportId = transportId; - } + int prevTransport = mTransportId; + mTransportId = transportId; + Log.v(TAG, "selectBackupTransport() set " + mTransportId + " returning " + prevTransport); return prevTransport; } @@ -1005,7 +1129,7 @@ class BackupManagerService extends IBackupManager.Stub { RestoreSet[] mRestoreSets = null; RestoreSession(int transportID) { - mRestoreTransport = createTransport(transportID); + mRestoreTransport = getTransport(transportID); } // --- Binder interface --- diff --git a/services/java/com/android/server/EntropyService.java b/services/java/com/android/server/EntropyService.java new file mode 100644 index 000000000000..e51a0afb4ef2 --- /dev/null +++ b/services/java/com/android/server/EntropyService.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2009 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. + */ + +package com.android.server; + +import java.io.File; +import java.io.IOException; + +import android.os.Binder; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +/** + * A service designed to load and periodically save "randomness" + * for the Linux kernel. + * + * <p>When a Linux system starts up, the entropy pool associated with + * {@code /dev/random} may be in a fairly predictable state. Applications which + * depend strongly on randomness may find {@code /dev/random} or + * {@code /dev/urandom} returning predictable data. In order to counteract + * this effect, it's helpful to carry the entropy pool information across + * shutdowns and startups. + * + * <p>This class was modeled after the script in + * <a href="http://www.kernel.org/doc/man-pages/online/pages/man4/random.4.html">man + * 4 random</a>. + * + * <p>TODO: Investigate attempting to write entropy data at shutdown time + * instead of periodically. + */ +public class EntropyService extends Binder { + private static final String ENTROPY_FILENAME = getSystemDir() + "/entropy.dat"; + private static final String TAG = "EntropyService"; + private static final int ENTROPY_WHAT = 1; + private static final int ENTROPY_WRITE_PERIOD = 3 * 60 * 60 * 1000; // 3 hrs + private static final String RANDOM_DEV = "/dev/urandom"; + + /** + * Handler that periodically updates the entropy on disk. + */ + private final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what != ENTROPY_WHAT) { + Log.e(TAG, "Will not process invalid message"); + return; + } + writeEntropy(); + scheduleEntropyWriter(); + } + }; + + public EntropyService() { + loadInitialEntropy(); + writeEntropy(); + scheduleEntropyWriter(); + } + + private void scheduleEntropyWriter() { + mHandler.removeMessages(ENTROPY_WHAT); + mHandler.sendEmptyMessageDelayed(ENTROPY_WHAT, ENTROPY_WRITE_PERIOD); + } + + private void loadInitialEntropy() { + try { + RandomBlock.fromFile(ENTROPY_FILENAME).toFile(RANDOM_DEV); + } catch (IOException e) { + Log.w(TAG, "unable to load initial entropy (first boot?)", e); + } + } + + private void writeEntropy() { + try { + RandomBlock.fromFile(RANDOM_DEV).toFile(ENTROPY_FILENAME); + } catch (IOException e) { + Log.e(TAG, "unable to write entropy", e); + } + } + + private static String getSystemDir() { + File dataDir = Environment.getDataDirectory(); + File systemDir = new File(dataDir, "system"); + systemDir.mkdirs(); + return systemDir.toString(); + } +} diff --git a/services/java/com/android/server/PackageManagerBackupAgent.java b/services/java/com/android/server/PackageManagerBackupAgent.java new file mode 100644 index 000000000000..6bd32a02f464 --- /dev/null +++ b/services/java/com/android/server/PackageManagerBackupAgent.java @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2009 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. + */ + +package com.android.server; + +import android.app.BackupAgent; +import android.backup.BackupDataInput; +import android.backup.BackupDataOutput; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; +import android.os.ParcelFileDescriptor; +import android.util.Log; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; + +// !!!TODO: take this out +import java.util.zip.CRC32; + +/** + * We back up the signatures of each package so that during a system restore, + * we can verify that the app whose data we think we have matches the app + * actually resident on the device. + * + * Since the Package Manager isn't a proper "application" we just provide a + * direct IBackupAgent implementation and hand-construct it at need. + */ +public class PackageManagerBackupAgent extends BackupAgent { + private static final String TAG = "PMBA"; + private static final boolean DEBUG = true; + + private List<ApplicationInfo> mAllApps; + private PackageManager mPackageManager; + private HashMap<String, Signature[]> mRestoredSignatures; + + // We're constructed with the set of applications that are participating + // in backup. This set changes as apps are installed & removed. + PackageManagerBackupAgent(PackageManager packageMgr, List<ApplicationInfo> apps) { + mPackageManager = packageMgr; + mAllApps = apps; + mRestoredSignatures = null; + } + + public Signature[] getRestoredSignatures(String packageName) { + if (mRestoredSignatures == null) { + return null; + } + + return mRestoredSignatures.get(packageName); + } + + // The backed up data is the signature block for each app, keyed by + // the package name. + public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data, + ParcelFileDescriptor newState) { + HashSet<String> existing = parseStateFile(oldState); + + // For each app we have on device, see if we've backed it up yet. If not, + // write its signature block to the output, keyed on the package name. + for (ApplicationInfo app : mAllApps) { + String packName = app.packageName; + if (!existing.contains(packName)) { + // We haven't stored this app's signatures yet, so we do that now + try { + PackageInfo info = mPackageManager.getPackageInfo(packName, + PackageManager.GET_SIGNATURES); + // build a byte array out of the signature list + byte[] sigs = flattenSignatureArray(info.signatures); +// !!! TODO: take out this debugging + if (DEBUG) { + CRC32 crc = new CRC32(); + crc.update(sigs); + Log.i(TAG, "+ flat sig array for " + packName + " : " + + crc.getValue()); + } + data.writeEntityHeader(packName, sigs.length); + data.writeEntityData(sigs, sigs.length); + } catch (NameNotFoundException e) { + // Weird; we just found it, and now are told it doesn't exist. + // Treat it as having been removed from the device. + existing.add(packName); + } catch (IOException e) { + // Real error writing data + Log.e(TAG, "Unable to write package backup data file!"); + return; + } + } else { + // We've already backed up this app. Remove it from the set so + // we can tell at the end what has disappeared from the device. + if (!existing.remove(packName)) { + Log.d(TAG, "*** failed to remove " + packName + " from package set!"); + } + } + } + + // At this point, the only entries in 'existing' are apps that were + // mentioned in the saved state file, but appear to no longer be present + // on the device. Write a deletion entity for them. + for (String app : existing) { + try { + data.writeEntityHeader(app, -1); + } catch (IOException e) { + Log.e(TAG, "Unable to write package deletions!"); + return; + } + } + + // Finally, write the new state blob -- just the list of all apps we handled + writeStateFile(mAllApps, newState); + } + + // "Restore" here is a misnomer. What we're really doing is reading back the + // set of app signatures associated with each backed-up app in this restore + // image. We'll use those later to determine what we can legitimately restore. + public void onRestore(BackupDataInput data, ParcelFileDescriptor newState) + throws IOException { + List<ApplicationInfo> restoredApps = new ArrayList<ApplicationInfo>(); + HashMap<String, Signature[]> sigMap = new HashMap<String, Signature[]>(); + + while (data.readNextHeader()) { + int dataSize = data.getDataSize(); + byte[] buf = new byte[dataSize]; + data.readEntityData(buf, 0, dataSize); + + Signature[] sigs = unflattenSignatureArray(buf); + String pkg = data.getKey(); +// !!! TODO: take out this debugging + if (DEBUG) { + CRC32 crc = new CRC32(); + crc.update(buf); + Log.i(TAG, "- unflat sig array for " + pkg + " : " + + crc.getValue()); + } + + ApplicationInfo app = new ApplicationInfo(); + app.packageName = pkg; + restoredApps.add(app); + sigMap.put(pkg, sigs); + } + + mRestoredSignatures = sigMap; + } + + + // Util: convert an array of Signatures into a flattened byte buffer. The + // flattened format contains enough info to reconstruct the signature array. + private byte[] flattenSignatureArray(Signature[] allSigs) { + ByteArrayOutputStream outBuf = new ByteArrayOutputStream(); + DataOutputStream out = new DataOutputStream(outBuf); + + // build the set of subsidiary buffers + try { + // first the # of signatures in the array + out.writeInt(allSigs.length); + + // then the signatures themselves, length + flattened buffer + for (Signature sig : allSigs) { + byte[] flat = sig.toByteArray(); + out.writeInt(flat.length); + out.write(flat); + } + } catch (IOException e) { + // very strange; we're writing to memory here. abort. + return null; + } + + return outBuf.toByteArray(); + } + + private Signature[] unflattenSignatureArray(byte[] buffer) { + ByteArrayInputStream inBufStream = new ByteArrayInputStream(buffer); + DataInputStream in = new DataInputStream(inBufStream); + Signature[] sigs = null; + + try { + int num = in.readInt(); + sigs = new Signature[num]; + for (int i = 0; i < num; i++) { + int len = in.readInt(); + byte[] flatSig = new byte[len]; + in.read(flatSig); + sigs[i] = new Signature(flatSig); + } + } catch (EOFException e) { + // clean termination + if (sigs == null) { + Log.w(TAG, "Empty signature block found"); + } + } catch (IOException e) { + Log.d(TAG, "Unable to unflatten sigs"); + return null; + } + + return sigs; + } + + // Util: parse out an existing state file into a usable structure + private HashSet<String> parseStateFile(ParcelFileDescriptor stateFile) { + HashSet<String> set = new HashSet<String>(); + // The state file is just the list of app names we have stored signatures for + FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor()); + DataInputStream in = new DataInputStream(instream); + + int bufSize = 256; + byte[] buf = new byte[bufSize]; + try { + int nameSize = in.readInt(); + if (bufSize < nameSize) { + bufSize = nameSize + 32; + buf = new byte[bufSize]; + } + in.read(buf, 0, nameSize); + String pkg = new String(buf, 0, nameSize); + set.add(pkg); + } catch (EOFException eof) { + // safe; we're done + } catch (IOException e) { + // whoops, bad state file. abort. + Log.e(TAG, "Unable to read Package Manager state file"); + return null; + } + return set; + } + + // Util: write a set of names into a new state file + private void writeStateFile(List<ApplicationInfo> apps, ParcelFileDescriptor stateFile) { + FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor()); + DataOutputStream out = new DataOutputStream(outstream); + + for (ApplicationInfo app : apps) { + try { + byte[] pkgNameBuf = app.packageName.getBytes(); + out.writeInt(pkgNameBuf.length); + out.write(pkgNameBuf); + } catch (IOException e) { + Log.e(TAG, "Unable to write package manager state file!"); + return; + } + } + } +} diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 73478e4b2432..fd3bacaca0ab 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -19,7 +19,6 @@ package com.android.server; import com.android.internal.app.ResolverActivity; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; -import com.android.server.PackageManagerService.PreferredActivity; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -27,10 +26,7 @@ import org.xmlpull.v1.XmlSerializer; import android.app.ActivityManagerNative; import android.app.IActivityManager; -import android.app.PendingIntent; -import android.app.PendingIntent.CanceledException; import android.content.ComponentName; -import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; @@ -152,6 +148,7 @@ class PackageManagerService extends IPackageManager.Stub { final Context mContext; final boolean mFactoryTest; + final boolean mNoDexOpt; final DisplayMetrics mMetrics; final int mDefParseFlags; final String[] mSeparateProcesses; @@ -303,6 +300,7 @@ class PackageManagerService extends IPackageManager.Stub { mContext = context; mFactoryTest = factoryTest; + mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); mSettings = new Settings(); mSettings.addSharedUserLP("android.uid.system", @@ -372,6 +370,10 @@ class PackageManagerService extends IPackageManager.Stub { startTime); int scanMode = SCAN_MONITOR; + if (mNoDexOpt) { + Log.w(TAG, "Running ENG build: no pre-dexopt!"); + scanMode |= SCAN_NO_DEX; + } final HashSet<String> libFiles = new HashSet<String>(); @@ -932,33 +934,7 @@ class PackageManagerService extends IPackageManager.Stub { }); } - public void freeStorage(final long freeStorageSize, final PendingIntent opFinishedIntent) { - mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.CLEAR_APP_CACHE, null); - // Queue up an async operation since clearing cache may take a little while. - mHandler.post(new Runnable() { - public void run() { - mHandler.removeCallbacks(this); - int retCode = -1; - if (mInstaller != null) { - retCode = mInstaller.freeCache(freeStorageSize); - if (retCode < 0) { - Log.w(TAG, "Couldn't clear application caches"); - } - } - if(opFinishedIntent != null) { - try { - // Callback via pending intent - opFinishedIntent.send((retCode >= 0) ? 1 : 0); - } catch (CanceledException e1) { - Log.i(TAG, "Failed to send pending intent"); - } - } - } - }); - } - - public void freeStorageWithIntent(final long freeStorageSize, final IntentSender pi) { + public void freeStorage(final long freeStorageSize, final IntentSender pi) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CLEAR_APP_CACHE, null); // Queue up an async operation since clearing cache may take a little while. @@ -993,10 +969,11 @@ class PackageManagerService extends IPackageManager.Stub { if (Config.LOGV) Log.v(TAG, "getActivityInfo " + component + ": " + a); if (a != null && mSettings.isEnabledLP(a.info, flags)) { ActivityInfo ainfo = PackageParser.generateActivityInfo(a, flags); - if (ainfo != null && (flags & PackageManager.GET_EXPANDABLE) != 0) { + if (ainfo != null) { ApplicationInfo appInfo = getApplicationInfo(component.getPackageName(), - PackageManager.GET_EXPANDABLE | PackageManager.GET_SUPPORTS_DENSITIES); - if (appInfo != null && !appInfo.expandable) { + PackageManager.GET_SUPPORTS_DENSITIES); + if (appInfo != null && + (appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) == 0) { // Check if the screen size is same as what the application expect. CompatibilityInfo info = new CompatibilityInfo(appInfo); DisplayMetrics metrics = new DisplayMetrics(); @@ -1009,11 +986,13 @@ class PackageManagerService extends IPackageManager.Stub { // Don't allow an app that cannot expand to handle rotation. ainfo.configChanges &= ~ ActivityInfo.CONFIG_ORIENTATION; } else { - appInfo.expandable = true; + appInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; } if (DEBUG_SETTINGS) { Log.d(TAG, "component=" + component + - ", expandable:" + appInfo.expandable); + ", expandable:" + + ((appInfo.flags & + ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0)); } } } @@ -1269,28 +1248,6 @@ class PackageManagerService extends IPackageManager.Stub { return chooseBestActivity(intent, resolvedType, flags, query); } - public ResolveInfo resolveIntentForPackage(Intent intent, String resolvedType, - int flags, String packageName) { - ComponentName comp = intent.getComponent(); - if (comp != null) { - // if this is an explicit intent, it must have the same the packageName - if (packageName.equals(comp.getPackageName())) { - return resolveIntent(intent, resolvedType, flags); - } - return null; - } else { - List<ResolveInfo> query = null; - synchronized (mPackages) { - PackageParser.Package pkg = mPackages.get(packageName); - if (pkg != null) { - query = (List<ResolveInfo>) mActivities. - queryIntentForPackage(intent, resolvedType, flags, pkg.activities); - } - } - return chooseBestActivity(intent, resolvedType, flags, query); - } - } - private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query) { if (query != null) { @@ -1409,8 +1366,17 @@ class PackageManagerService extends IPackageManager.Stub { } synchronized (mPackages) { - return (List<ResolveInfo>)mActivities. - queryIntent(intent, resolvedType, flags); + String pkgName = intent.getPackage(); + if (pkgName == null) { + return (List<ResolveInfo>)mActivities.queryIntent(intent, + resolvedType, flags); + } + PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg != null) { + return (List<ResolveInfo>) mActivities.queryIntentForPackage(intent, + resolvedType, flags, pkg.activities); + } + return null; } } @@ -1577,9 +1543,30 @@ class PackageManagerService extends IPackageManager.Stub { public List<ResolveInfo> queryIntentReceivers(Intent intent, String resolvedType, int flags) { + ComponentName comp = intent.getComponent(); + if (comp != null) { + List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + ActivityInfo ai = getReceiverInfo(comp, flags); + if (ai != null) { + ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + list.add(ri); + } + return list; + } + synchronized (mPackages) { - return (List<ResolveInfo>)mReceivers. - queryIntent(intent, resolvedType, flags); + String pkgName = intent.getPackage(); + if (pkgName == null) { + return (List<ResolveInfo>)mReceivers.queryIntent(intent, + resolvedType, flags); + } + PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg != null) { + return (List<ResolveInfo>) mReceivers.queryIntentForPackage(intent, + resolvedType, flags, pkg.receivers); + } + return null; } } @@ -1612,7 +1599,17 @@ class PackageManagerService extends IPackageManager.Stub { } synchronized (mPackages) { - return (List<ResolveInfo>)mServices.queryIntent(intent, resolvedType, flags); + String pkgName = intent.getPackage(); + if (pkgName == null) { + return (List<ResolveInfo>)mServices.queryIntent(intent, + resolvedType, flags); + } + PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg != null) { + return (List<ResolveInfo>)mServices.queryIntentForPackage(intent, + resolvedType, flags, pkg.services); + } + return null; } } @@ -1925,7 +1922,56 @@ class PackageManagerService extends IPackageManager.Stub { } return true; } + + public boolean performDexOpt(String packageName) { + if (!mNoDexOpt) { + return false; + } + + PackageParser.Package p; + synchronized (mPackages) { + p = mPackages.get(packageName); + if (p == null || p.mDidDexOpt) { + return false; + } + } + synchronized (mInstallLock) { + return performDexOptLI(p, false) == DEX_OPT_PERFORMED; + } + } + + static final int DEX_OPT_SKIPPED = 0; + static final int DEX_OPT_PERFORMED = 1; + static final int DEX_OPT_FAILED = -1; + + private int performDexOptLI(PackageParser.Package pkg, boolean forceDex) { + boolean performed = false; + if ((pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { + String path = pkg.mScanPath; + int ret = 0; + try { + if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) { + ret = mInstaller.dexopt(path, pkg.applicationInfo.uid, + !pkg.mForwardLocked); + pkg.mDidDexOpt = true; + performed = true; + } + } catch (FileNotFoundException e) { + Log.w(TAG, "Apk not found for dexopt: " + path); + ret = -1; + } catch (IOException e) { + Log.w(TAG, "Exception reading apk: " + path, e); + ret = -1; + } + if (ret < 0) { + //error from installer + return DEX_OPT_FAILED; + } + } + return performed ? DEX_OPT_PERFORMED : DEX_OPT_SKIPPED; + } + private PackageParser.Package scanPackageLI( File scanFile, File destCodeFile, File destResourceFile, PackageParser.Package pkg, int parseFlags, int scanMode) { @@ -2221,23 +2267,11 @@ class PackageManagerService extends IPackageManager.Stub { } } - if ((scanMode&SCAN_NO_DEX) == 0 - && (pkg.applicationInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0) { - int ret = 0; - try { - if (forceDex || dalvik.system.DexFile.isDexOptNeeded(path)) { - ret = mInstaller.dexopt(path, pkg.applicationInfo.uid, - (scanMode&SCAN_FORWARD_LOCKED) == 0); - } - } catch (FileNotFoundException e) { - Log.w(TAG, "Apk not found for dexopt: " + path); - ret = -1; - } catch (IOException e) { - Log.w(TAG, "Exception reading apk: " + path, e); - ret = -1; - } - if (ret < 0) { - //error from installer + pkg.mForwardLocked = (scanMode&SCAN_FORWARD_LOCKED) != 0; + pkg.mScanPath = path; + + if ((scanMode&SCAN_NO_DEX) == 0) { + if (performDexOptLI(pkg, forceDex) == DEX_OPT_FAILED) { mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT; return null; } @@ -3106,6 +3140,27 @@ class PackageManagerService extends IPackageManager.Stub { (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0); } + public List queryIntentForPackage(Intent intent, String resolvedType, int flags, + ArrayList<PackageParser.Service> packageServices) { + if (packageServices == null) { + return null; + } + mFlags = flags; + final boolean defaultOnly = (flags&PackageManager.MATCH_DEFAULT_ONLY) != 0; + int N = packageServices.size(); + ArrayList<ArrayList<PackageParser.ServiceIntentInfo>> listCut = + new ArrayList<ArrayList<PackageParser.ServiceIntentInfo>>(N); + + ArrayList<PackageParser.ServiceIntentInfo> intentFilters; + for (int i = 0; i < N; ++i) { + intentFilters = packageServices.get(i).intents; + if (intentFilters != null && intentFilters.size() > 0) { + listCut.add(intentFilters); + } + } + return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut); + } + public final void addService(PackageParser.Service s) { mServices.put(s.component, s); if (SHOW_INFO || Config.LOGV) Log.v( diff --git a/services/java/com/android/server/RandomBlock.java b/services/java/com/android/server/RandomBlock.java new file mode 100644 index 000000000000..4ac1c6e21b3f --- /dev/null +++ b/services/java/com/android/server/RandomBlock.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2009 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. + */ + +package com.android.server; + +import android.util.Log; + +import java.io.Closeable; +import java.io.DataOutput; +import java.io.EOFException; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; + +/** + * A 4k block of random {@code byte}s. + */ +class RandomBlock { + + private static final String TAG = "RandomBlock"; + private static final int BLOCK_SIZE = 4096; + private byte[] block = new byte[BLOCK_SIZE]; + + private RandomBlock() { } + + static RandomBlock fromFile(String filename) throws IOException { + Log.v(TAG, "reading from file " + filename); + InputStream stream = null; + try { + stream = new FileInputStream(filename); + return fromStream(stream); + } finally { + close(stream); + } + } + + private static RandomBlock fromStream(InputStream in) throws IOException { + RandomBlock retval = new RandomBlock(); + int total = 0; + while(total < BLOCK_SIZE) { + int result = in.read(retval.block, total, BLOCK_SIZE - total); + if (result == -1) { + throw new EOFException(); + } + total += result; + } + return retval; + } + + void toFile(String filename) throws IOException { + Log.v(TAG, "writing to file " + filename); + RandomAccessFile out = null; + try { + out = new RandomAccessFile(filename, "rws"); + toDataOut(out); + truncateIfPossible(out); + } finally { + close(out); + } + } + + private static void truncateIfPossible(RandomAccessFile f) { + try { + f.setLength(BLOCK_SIZE); + } catch (IOException e) { + // ignore this exception. Sometimes, the file we're trying to + // write is a character device, such as /dev/urandom, and + // these character devices do not support setting the length. + } + } + + private void toDataOut(DataOutput out) throws IOException { + out.write(block); + } + + private static void close(Closeable c) { + try { + if (c == null) { + return; + } + c.close(); + } catch (IOException e) { + Log.w(TAG, "IOException thrown while closing Closeable", e); + } + } +} diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index f6c1525d4bfd..3e4d5f97f9b7 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -97,6 +97,9 @@ class ServerThread extends Thread { // Critical services... try { + Log.i(TAG, "Starting Entropy Service."); + ServiceManager.addService("entropy", new EntropyService()); + Log.i(TAG, "Starting Power Manager."); power = new PowerManagerService(); ServiceManager.addService(Context.POWER_SERVICE, power); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 755f9c867c8c..8cf1cc2e813f 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -181,7 +181,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // The flags that are set for all calls we make to the package manager. static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES - | PackageManager.GET_SUPPORTS_DENSITIES | PackageManager.GET_EXPANDABLE; + | PackageManager.GET_SUPPORTS_DENSITIES; private static final String SYSTEM_SECURE = "ro.secure"; @@ -809,6 +809,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen */ int[] mProcDeaths = new int[20]; + /** + * This is set if we had to do a delayed dexopt of an app before launching + * it, to increasing the ANR timeouts in that case. + */ + boolean mDidDexOpt; + String mDebugApp = null; boolean mWaitForDebugger = false; boolean mDebugTransient = false; @@ -1007,6 +1013,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen processNextBroadcast(true); } break; case BROADCAST_TIMEOUT_MSG: { + if (mDidDexOpt) { + mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(BROADCAST_TIMEOUT_MSG); + mHandler.sendMessageDelayed(nmsg, BROADCAST_TIMEOUT); + return; + } broadcastTimeout(); } break; case PAUSE_TIMEOUT_MSG: { @@ -1017,9 +1029,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen activityPaused(token, null, true); } break; case IDLE_TIMEOUT_MSG: { - IBinder token = (IBinder)msg.obj; + if (mDidDexOpt) { + mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); + return; + } // We don't at this point know if the activity is fullscreen, // so we need to be conservative and assume it isn't. + IBinder token = (IBinder)msg.obj; Log.w(TAG, "Activity idle timeout for " + token); activityIdleInternal(token, true); } break; @@ -1035,6 +1054,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen activityIdle(token); } break; case SERVICE_TIMEOUT_MSG: { + if (mDidDexOpt) { + mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(SERVICE_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, SERVICE_TIMEOUT); + return; + } serviceTimeout((ProcessRecord)msg.obj); } break; case UPDATE_TIME_ZONE: { @@ -1071,6 +1097,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } break; case LAUNCH_TIMEOUT_MSG: { + if (mDidDexOpt) { + mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); + mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT); + return; + } synchronized (ActivityManagerService.this) { if (mLaunchingActivity.isHeld()) { Log.w(TAG, "Launch timeout has expired, giving up wake lock!"); @@ -1091,6 +1123,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } case PROC_START_TIMEOUT_MSG: { + if (mDidDexOpt) { + mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(PROC_START_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, PROC_START_TIMEOUT); + return; + } ProcessRecord app = (ProcessRecord)msg.obj; synchronized (ActivityManagerService.this) { processStartTimedOutLocked(app); @@ -1607,6 +1646,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return proc; } + private void ensurePackageDexOpt(String packageName) { + IPackageManager pm = ActivityThread.getPackageManager(); + try { + if (pm.performDexOpt(packageName)) { + mDidDexOpt = true; + } + } catch (RemoteException e) { + } + } + private boolean isNextTransitionForward() { int transit = mWindowManager.getPendingAppTransition(); return transit == WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN @@ -1666,6 +1715,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.isHomeActivity) { mHomeProcess = app; } + ensurePackageDexOpt(r.intent.getComponent().getPackageName()); app.thread.scheduleLaunchActivity(new Intent(r.intent), r, r.info, r.icicle, results, newIntents, !andResume, isNextTransitionForward()); @@ -4819,6 +4869,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE) || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL); } + ensurePackageDexOpt(app.info.packageName); + if (app.instrumentationInfo != null) { + ensurePackageDexOpt(app.instrumentationInfo.packageName); + } thread.bindApplication(processName, app.instrumentationInfo != null ? app.instrumentationInfo : app.info, providers, app.instrumentationClass, app.instrumentationProfileFile, @@ -4907,6 +4961,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Check whether the next backup agent is in this process... if (!badApp && mBackupTarget != null && mBackupTarget.appInfo.uid == app.info.uid) { if (DEBUG_BACKUP) Log.v(TAG, "New app is backup target, launching agent for " + app); + ensurePackageDexOpt(mBackupTarget.appInfo.packageName); try { thread.scheduleCreateBackupAgent(mBackupTarget.appInfo, mBackupTarget.backupMode); } catch (Exception e) { @@ -6918,6 +6973,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } app.pubProviders.put(cpi.name, cpr); app.addPackage(cpi.applicationInfo.packageName); + ensurePackageDexOpt(cpi.applicationInfo.packageName); } } return providers; @@ -7904,7 +7960,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // is there an Activity in this package that handles ACTION_APP_ERROR? Intent intent = new Intent(Intent.ACTION_APP_ERROR); - ResolveInfo info = pm.resolveIntentForPackage(intent, null, 0, installerPackageName); + intent.setPackage(installerPackageName); + ResolveInfo info = pm.resolveIntent(intent, null, 0); if (info == null || info.activityInfo == null) { return null; } @@ -9541,6 +9598,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } + ensurePackageDexOpt(r.serviceInfo.packageName); app.thread.scheduleCreateService(r, r.serviceInfo); created = true; } finally { @@ -11097,6 +11155,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_BROADCAST_LIGHT) Log.v(TAG, "Delivering to component " + r.curComponent + ": " + r); + ensurePackageDexOpt(r.intent.getComponent().getPackageName()); app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, r.resultCode, r.resultData, r.resultExtras, r.ordered); started = true; diff --git a/services/java/com/android/server/am/HistoryRecord.java b/services/java/com/android/server/am/HistoryRecord.java index 944ea02dbf79..b3fc313f4abf 100644 --- a/services/java/com/android/server/am/HistoryRecord.java +++ b/services/java/com/android/server/am/HistoryRecord.java @@ -463,6 +463,12 @@ class HistoryRecord extends IApplicationToken.Stub { return false; } + if (service.mDidDexOpt) { + // Give more time since we were dexopting. + service.mDidDexOpt = false; + return false; + } + if (r.app.instrumentationClass == null) { service.appNotRespondingLocked(r.app, r, "keyDispatchingTimedOut"); } else { diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 866334b2d28e..2d58659dfd3b 100755 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -56,9 +56,9 @@ public final class UsageStatsService extends IUsageStats.Stub { private static final String TAG = "UsageStats"; // Current on-disk Parcel version - private static final int VERSION = 1004; + private static final int VERSION = 1005; - private static final int CHECKIN_VERSION = 3; + private static final int CHECKIN_VERSION = 4; private static final String FILE_PREFIX = "usage-"; @@ -82,7 +82,9 @@ public final class UsageStatsService extends IUsageStats.Stub { // this lock held. final Object mFileLock; // Order of locks is mFileLock followed by mStatsLock to avoid deadlocks - private String mResumedPkg; + private String mLastResumedPkg; + private String mLastResumedComp; + private boolean mIsResumed; private File mFile; private String mFileLeaf; //private File mBackupFile; @@ -92,11 +94,16 @@ public final class UsageStatsService extends IUsageStats.Stub { private int mLastWriteDay; static class TimeStats { + int count; int[] times = new int[NUM_LAUNCH_TIME_BINS]; TimeStats() { } + void incCount() { + count++; + } + void add(int val) { final int[] bins = LAUNCH_TIME_BINS; for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { @@ -109,6 +116,7 @@ public final class UsageStatsService extends IUsageStats.Stub { } TimeStats(Parcel in) { + count = in.readInt(); final int[] localTimes = times; for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { localTimes[i] = in.readInt(); @@ -116,6 +124,7 @@ public final class UsageStatsService extends IUsageStats.Stub { } void writeToParcel(Parcel out) { + out.writeInt(count); final int[] localTimes = times; for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { out.writeInt(localTimes[i]); @@ -152,8 +161,10 @@ public final class UsageStatsService extends IUsageStats.Stub { } } - void updateResume() { - mLaunchCount ++; + void updateResume(boolean launched) { + if (launched) { + mLaunchCount ++; + } mResumedTime = SystemClock.elapsedRealtime(); } @@ -162,6 +173,15 @@ public final class UsageStatsService extends IUsageStats.Stub { mUsageTime += (mPausedTime - mResumedTime); } + void addLaunchCount(String comp) { + TimeStats times = mLaunchTimes.get(comp); + if (times == null) { + times = new TimeStats(); + mLaunchTimes.put(comp, times); + } + times.incCount(); + } + void addLaunchTime(String comp, int millis) { TimeStats times = mLaunchTimes.get(comp); if (times == null) { @@ -436,43 +456,70 @@ public final class UsageStatsService extends IUsageStats.Stub { public void noteResumeComponent(ComponentName componentName) { enforceCallingPermission(); String pkgName; - if ((componentName == null) || - ((pkgName = componentName.getPackageName()) == null)) { - return; - } - if ((mResumedPkg != null) && (mResumedPkg.equalsIgnoreCase(pkgName))) { - // Moving across activities in same package. just return - return; - } - if (localLOGV) Log.i(TAG, "started component:"+pkgName); synchronized (mStatsLock) { + if ((componentName == null) || + ((pkgName = componentName.getPackageName()) == null)) { + return; + } + + final boolean samePackage = pkgName.equals(mLastResumedPkg); + if (mIsResumed) { + if (samePackage) { + Log.w(TAG, "Something wrong here, didn't expect " + + pkgName + " to be resumed"); + return; + } + + if (mLastResumedPkg != null) { + // We last resumed some other package... just pause it now + // to recover. + Log.w(TAG, "Unexpected resume of " + pkgName + + " while already resumed in " + mLastResumedPkg); + PkgUsageStatsExtended pus = mStats.get(mLastResumedPkg); + if (pus != null) { + pus.updatePause(); + } + } + } + + final boolean sameComp = samePackage + && componentName.getClassName().equals(mLastResumedComp); + + mIsResumed = true; + mLastResumedPkg = pkgName; + mLastResumedComp = componentName.getClassName(); + + if (localLOGV) Log.i(TAG, "started component:" + pkgName); PkgUsageStatsExtended pus = mStats.get(pkgName); if (pus == null) { pus = new PkgUsageStatsExtended(); mStats.put(pkgName, pus); } - pus.updateResume(); + pus.updateResume(!samePackage); + if (!sameComp) { + pus.addLaunchCount(mLastResumedComp); + } } - mResumedPkg = pkgName; } public void notePauseComponent(ComponentName componentName) { enforceCallingPermission(); - String pkgName; - if ((componentName == null) || - ((pkgName = componentName.getPackageName()) == null)) { - return; - } - if ((mResumedPkg == null) || (!pkgName.equalsIgnoreCase(mResumedPkg))) { - Log.w(TAG, "Something wrong here, Didn't expect "+pkgName+" to be paused"); - return; - } - if (localLOGV) Log.i(TAG, "paused component:"+pkgName); - - // Persist current data to file if needed. - writeStatsToFile(false); synchronized (mStatsLock) { + String pkgName; + if ((componentName == null) || + ((pkgName = componentName.getPackageName()) == null)) { + return; + } + if (!mIsResumed) { + Log.w(TAG, "Something wrong here, didn't expect " + + pkgName + " to be paused"); + return; + } + mIsResumed = false; + + if (localLOGV) Log.i(TAG, "paused component:"+pkgName); + PkgUsageStatsExtended pus = mStats.get(pkgName); if (pus == null) { // Weird some error here @@ -481,6 +528,9 @@ public final class UsageStatsService extends IUsageStats.Stub { } pus.updatePause(); } + + // Persist current data to file if needed. + writeStatsToFile(false); } public void noteLaunchTime(ComponentName componentName, int millis) { @@ -631,9 +681,9 @@ public final class UsageStatsService extends IUsageStats.Stub { if (isCompactOutput) { sb.append("P:"); sb.append(pkgName); - sb.append(","); + sb.append(','); sb.append(pus.mLaunchCount); - sb.append(","); + sb.append(','); sb.append(pus.mUsageTime); sb.append('\n'); final int NC = pus.mLaunchTimes.size(); @@ -642,6 +692,8 @@ public final class UsageStatsService extends IUsageStats.Stub { sb.append("A:"); sb.append(ent.getKey()); TimeStats times = ent.getValue(); + sb.append(','); + sb.append(times.count); for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { sb.append(","); sb.append(times.times[i]); @@ -665,25 +717,26 @@ public final class UsageStatsService extends IUsageStats.Stub { sb.append(" "); sb.append(ent.getKey()); TimeStats times = ent.getValue(); + sb.append(": "); + sb.append(times.count); + sb.append(" starts"); int lastBin = 0; - boolean first = true; for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { if (times.times[i] != 0) { - sb.append(first ? ": " : ", "); + sb.append(", "); sb.append(lastBin); sb.append('-'); sb.append(LAUNCH_TIME_BINS[i]); - sb.append('='); + sb.append("ms="); sb.append(times.times[i]); - first = false; } lastBin = LAUNCH_TIME_BINS[i]; } if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { - sb.append(first ? ": " : ", "); + sb.append(", "); sb.append(">="); sb.append(lastBin); - sb.append('='); + sb.append("ms="); sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); } sb.append('\n'); diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java index 42c885d44d91..dd3d38c80f65 100644 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ b/services/java/com/android/server/status/StatusBarPolicy.java @@ -728,6 +728,23 @@ public class StatusBarPolicy { b.setView(v); b.setIcon(android.R.drawable.ic_dialog_alert); b.setPositiveButton(android.R.string.ok, null); + + final Intent intent = new Intent(Intent.ACTION_POWER_USAGE_SUMMARY); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK + | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + | Intent.FLAG_ACTIVITY_NO_HISTORY); + if (intent.resolveActivity(mContext.getPackageManager()) != null) { + b.setNegativeButton(com.android.internal.R.string.battery_low_why, + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + mContext.startActivity(intent); + if (mLowBatteryDialog != null) { + mLowBatteryDialog.dismiss(); + } + } + }); + } AlertDialog d = b.create(); d.setOnDismissListener(mLowBatteryListener); diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java index 9395d6625064..890f930c337b 100644 --- a/telephony/java/android/telephony/SmsManager.java +++ b/telephony/java/android/telephony/SmsManager.java @@ -22,7 +22,6 @@ import android.os.ServiceManager; import android.text.TextUtils; import com.android.internal.telephony.EncodeException; -import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.ISms; import com.android.internal.telephony.IccConstants; import com.android.internal.telephony.SmsRawData; @@ -31,14 +30,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import static android.telephony.SmsMessage.ENCODING_7BIT; -import static android.telephony.SmsMessage.ENCODING_8BIT; -import static android.telephony.SmsMessage.ENCODING_16BIT; -import static android.telephony.SmsMessage.ENCODING_UNKNOWN; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES; -import static android.telephony.SmsMessage.MAX_USER_DATA_BYTES_WITH_HEADER; -import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS; -import static android.telephony.SmsMessage.MAX_USER_DATA_SEPTETS_WITH_HEADER; +/* + * TODO(code review): Curious question... Why are a lot of these + * methods not declared as static, since they do not seem to require + * any local object state? Assumedly this cannot be changed without + * interfering with the API... + */ /** * Manages SMS operations such as sending data, text, and pdu SMS messages. @@ -88,7 +85,7 @@ public final class SmsManager { } /** - * Divide a text message into several messages, none bigger than + * Divide a message text into several fragments, none bigger than * the maximum SMS message size. * * @param text the original message. Must not be null. @@ -96,40 +93,7 @@ public final class SmsManager { * comprise the original message */ public ArrayList<String> divideMessage(String text) { - int size = text.length(); - int[] params = SmsMessage.calculateLength(text, false); - /* SmsMessage.calculateLength returns an int[4] with: - * int[0] being the number of SMS's required, - * int[1] the number of code units used, - * int[2] is the number of code units remaining until the next message. - * int[3] is the encoding type that should be used for the message. - */ - int messageCount = params[0]; - int encodingType = params[3]; - ArrayList<String> result = new ArrayList<String>(messageCount); - - int start = 0; - int limit; - - if (messageCount > 1) { - limit = (encodingType == ENCODING_7BIT)? - MAX_USER_DATA_SEPTETS_WITH_HEADER: MAX_USER_DATA_BYTES_WITH_HEADER; - } else { - limit = (encodingType == ENCODING_7BIT)? - MAX_USER_DATA_SEPTETS: MAX_USER_DATA_BYTES; - } - - try { - while (start < size) { - int end = GsmAlphabet.findLimitIndex(text, start, limit, encodingType); - result.add(text.substring(start, end)); - start = end; - } - } - catch (EncodeException e) { - // ignore it. - } - return result; + return SmsMessage.fragmentText(text); } /** diff --git a/telephony/java/android/telephony/SmsMessage.java b/telephony/java/android/telephony/SmsMessage.java index b60da5a96ba5..775b0343b751 100644 --- a/telephony/java/android/telephony/SmsMessage.java +++ b/telephony/java/android/telephony/SmsMessage.java @@ -17,12 +17,17 @@ package android.telephony; import android.os.Parcel; +import android.util.Log; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.EncodeException; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SmsMessageBase.SubmitPduBase; +import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; + +import java.lang.Math; +import java.util.ArrayList; import static android.telephony.TelephonyManager.PHONE_TYPE_CDMA; @@ -44,19 +49,41 @@ public class SmsMessage { UNKNOWN, CLASS_0, CLASS_1, CLASS_2, CLASS_3; } - /** Unknown encoding scheme (see TS 23.038) */ + /** + * TODO(cleanup): given that we now have more than one possible + * 7bit encoding, this result starts to look rather vague and + * maybe confusing... If this is just an indication of code unit + * size, maybe that is no problem. Otherwise, should we try to + * create an aggregate collection of GSM and CDMA encodings? CDMA + * contains a superset of the encodings we use (it does not + * support 8-bit GSM, but we also do not use that encoding + * currently)... We could get rid of these and directly reference + * the CDMA encoding definitions... + */ + + /** User data text encoding code unit size */ public static final int ENCODING_UNKNOWN = 0; - /** 7-bit encoding scheme (see TS 23.038) */ public static final int ENCODING_7BIT = 1; - /** 8-bit encoding scheme (see TS 23.038) */ public static final int ENCODING_8BIT = 2; - /** 16-bit encoding scheme (see TS 23.038) */ public static final int ENCODING_16BIT = 3; /** The maximum number of payload bytes per message */ public static final int MAX_USER_DATA_BYTES = 140; /** + * TODO(cleanup): It would be more flexible and less fragile to + * rewrite this (meaning get rid of the following constant) such + * that an actual UDH is taken into consideration (meaning its + * length is measured), allowing for messages that actually + * contain other UDH fields... Hence it is actually a shame to + * extend the API with this constant. If necessary, maybe define + * the size of such a header and let the math for calculating + * max_octets/septets be done elsewhere. And, while I am griping, + * if we use the word septet, we should use the word octet in + * corresponding places, not byte... + */ + + /** * The maximum number of payload bytes per message if a user data header * is present. This assumes the header only contains the * CONCATENATED_8_BIT_REFERENCE element. @@ -222,6 +249,15 @@ public class SmsMessage { } } + /* + * TODO(cleanup): It would make some sense if the result of + * preprocessing a message to determine the proper encoding (ie + * the resulting datastructure from calculateLength) could be + * passed as an argument to the actual final encoding function. + * This would better ensure that the logic behind size calculation + * actually matched the encoding. + */ + /** * Calculates the number of SMS's required to encode the message body and * the number of characters remaining until the next message. @@ -232,46 +268,76 @@ public class SmsMessage { * space chars. If false, and if the messageBody contains * non-7-bit encodable characters, length is calculated * using a 16-bit encoding. - * @return an int[4] with int[0] being the number of SMS's required, int[1] - * the number of code units used, and int[2] is the number of code - * units remaining until the next message. int[3] is the encoding - * type that should be used for the message. + * @return an int[4] with int[0] being the number of SMS's + * required, int[1] the number of code units used, and + * int[2] is the number of code units remaining until the + * next message. int[3] is an indicator of the encoding + * code unit size (see the ENCODING_* definitions in this + * class). */ public static int[] calculateLength(CharSequence msgBody, boolean use7bitOnly) { int activePhone = TelephonyManager.getDefault().getPhoneType(); + TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ? + com.android.internal.telephony.gsm.SmsMessage.calculateLength(msgBody, use7bitOnly) : + com.android.internal.telephony.cdma.SmsMessage.calculateLength(msgBody, use7bitOnly); int ret[] = new int[4]; + ret[0] = ted.msgCount; + ret[1] = ted.codeUnitCount; + ret[2] = ted.codeUnitsRemaining; + ret[3] = ted.codeUnitSize; + return ret; + } - int septets = (PHONE_TYPE_CDMA == activePhone) ? - com.android.internal.telephony.cdma.SmsMessage.calc7bitEncodedLength(msgBody, - use7bitOnly) : - com.android.internal.telephony.gsm.SmsMessage.calc7bitEncodedLength(msgBody, - use7bitOnly); - if (septets != -1) { - ret[1] = septets; - if (septets > MAX_USER_DATA_SEPTETS) { - ret[0] = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1; - ret[2] = MAX_USER_DATA_SEPTETS_WITH_HEADER - - (septets % MAX_USER_DATA_SEPTETS_WITH_HEADER); - } else { - ret[0] = 1; - ret[2] = MAX_USER_DATA_SEPTETS - septets; - } - ret[3] = ENCODING_7BIT; + /** + * Divide a message text into several fragments, none bigger than + * the maximum SMS message text size. + * + * @param text text, must not be null. + * @return an <code>ArrayList</code> of strings that, in order, + * comprise the original msg text + */ + public static ArrayList<String> fragmentText(String text) { + int activePhone = TelephonyManager.getDefault().getPhoneType(); + TextEncodingDetails ted = (PHONE_TYPE_CDMA == activePhone) ? + com.android.internal.telephony.gsm.SmsMessage.calculateLength(text, false) : + com.android.internal.telephony.cdma.SmsMessage.calculateLength(text, false); + + // TODO(cleanup): The code here could be rolled into the logic + // below cleanly if these MAX_* constants were defined more + // flexibly... + + int limit; + if (ted.msgCount > 1) { + limit = (ted.codeUnitSize == ENCODING_7BIT) ? + MAX_USER_DATA_SEPTETS_WITH_HEADER : MAX_USER_DATA_BYTES_WITH_HEADER; } else { - int octets = msgBody.length() * 2; - ret[1] = msgBody.length(); - if (octets > MAX_USER_DATA_BYTES) { - ret[0] = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; - ret[2] = (MAX_USER_DATA_BYTES_WITH_HEADER - - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; - } else { - ret[0] = 1; - ret[2] = (MAX_USER_DATA_BYTES - octets)/2; - } - ret[3] = ENCODING_16BIT; + limit = (ted.codeUnitSize == ENCODING_7BIT) ? + MAX_USER_DATA_SEPTETS : MAX_USER_DATA_BYTES; } - return ret; + int pos = 0; // Index in code units. + int textLen = text.length(); + ArrayList<String> result = new ArrayList<String>(ted.msgCount); + while (pos < textLen) { + int nextPos = 0; // Counts code units. + if (ted.codeUnitSize == ENCODING_7BIT) { + if (PHONE_TYPE_CDMA == activePhone) { + nextPos = pos + Math.min(limit, textLen - pos); + } else { + nextPos = GsmAlphabet.findGsmSeptetLimitIndex(text, pos, limit); + } + } else { // Assume unicode. + nextPos = pos + Math.min(limit / 2, textLen - pos); + } + if ((nextPos <= pos) || (nextPos > textLen)) { + Log.e(LOG_TAG, "fragmentText failed (" + pos + " >= " + nextPos + " or " + + nextPos + " >= " + textLen + ")"); + break; + } + result.add(text.substring(pos, nextPos)); + pos = nextPos; + } + return result; } /** diff --git a/telephony/java/com/android/internal/telephony/GsmAlphabet.java b/telephony/java/com/android/internal/telephony/GsmAlphabet.java index 8e2941b9c78b..e8095e172b86 100644 --- a/telephony/java/com/android/internal/telephony/GsmAlphabet.java +++ b/telephony/java/com/android/internal/telephony/GsmAlphabet.java @@ -576,52 +576,6 @@ public class GsmAlphabet { return size; } - /** - * Returns the index into <code>s</code> of the first character - * after <code>limit</code> octets have been reached, starting at - * index <code>start</code>. This is used when dividing messages - * in UCS2 encoding into units within the SMS message size limit. - * - * @param s source string - * @param start index of where to start counting septets - * @param limit maximum septets to include, - * e.g. <code>MAX_USER_DATA_BYTES</code> - * @return index of first character that won't fit, or the length - * of the entire string if everything fits - */ - public static int - findUCS2LimitIndex(String s, int start, int limit) { - int numCharToBeEncoded = s.length() - start; - return ((numCharToBeEncoded*2 > limit)? limit/2: numCharToBeEncoded) + start; - } - - /** - * Returns the index into <code>s</code> of the first character - * after <code>limit</code> septets/octets have been reached - * according to the <code>encodingType</code>, starting at - * index <code>start</code>. This is used when dividing messages - * units within the SMS message size limit. - * - * @param s source string - * @param start index of where to start counting septets - * @param limit maximum septets to include, - * e.g. <code>MAX_USER_DATA_BYTES</code> - * @return index of first character that won't fit, or the length - * of the entire string if everything fits - */ - public static int - findLimitIndex(String s, int start, int limit, int encodingType) throws EncodeException { - if (encodingType == SmsMessage.ENCODING_7BIT) { - return findGsmSeptetLimitIndex(s, start, limit); - } - else if (encodingType == SmsMessage.ENCODING_16BIT) { - return findUCS2LimitIndex(s, start, limit); - } - else { - throw new EncodeException("Unsupported encoding type: " + encodingType); - } - } - // Set in the static initializer private static int sGsmSpaceChar; diff --git a/telephony/java/com/android/internal/telephony/SmsMessageBase.java b/telephony/java/com/android/internal/telephony/SmsMessageBase.java index 4d32c3502dbc..3c7dd458f94b 100644 --- a/telephony/java/com/android/internal/telephony/SmsMessageBase.java +++ b/telephony/java/com/android/internal/telephony/SmsMessageBase.java @@ -86,6 +86,38 @@ public abstract class SmsMessageBase { /** TP-Message-Reference - Message Reference of sent message. @hide */ public int messageRef; + /** + * For a specific text string, this object describes protocol + * properties of encoding it for transmission as message user + * data. + */ + public static class TextEncodingDetails { + /** + *The number of SMS's required to encode the text. + */ + public int msgCount; + + /** + * The number of code units consumed so far, where code units + * are basically characters in the encoding -- for example, + * septets for the standard ASCII and GSM encodings, and 16 + * bits for Unicode. + */ + public int codeUnitCount; + + /** + * How many code units are still available without spilling + * into an additional message. + */ + public int codeUnitsRemaining; + + /** + * The encoding code unit size (specified using + * android.telephony.SmsMessage ENCODING_*). + */ + public int codeUnitSize; + } + public static abstract class SubmitPduBase { public byte[] encodedScAddress; // Null if not applicable. public byte[] encodedMessage; diff --git a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java index 79e1cd6b2628..2b4a70099c50 100644 --- a/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java +++ b/telephony/java/com/android/internal/telephony/cdma/CdmaSMSDispatcher.java @@ -34,6 +34,7 @@ import com.android.internal.telephony.SmsMessageBase; import com.android.internal.telephony.SMSDispatcher; import com.android.internal.telephony.cdma.SmsMessage; import com.android.internal.telephony.cdma.sms.SmsEnvelope; +import com.android.internal.telephony.cdma.sms.UserData; import com.android.internal.util.HexDump; import java.io.ByteArrayOutputStream; @@ -302,8 +303,12 @@ final class CdmaSMSDispatcher extends SMSDispatcher { deliveryIntent = deliveryIntents.get(i); } - SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(scAddr, destAddr, - parts.get(i), deliveryIntent != null, smsHeader); + UserData uData = new UserData(); + uData.payloadStr = parts.get(i); + uData.userDataHeader = smsHeader; + + SmsMessage.SubmitPdu submitPdu = SmsMessage.getSubmitPdu(destAddr, + uData, deliveryIntent != null); sendSubmitPdu(submitPdu, sentIntent, deliveryIntent); } diff --git a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java index bbdd0dd90194..63d2c47ebe24 100644 --- a/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/cdma/SmsMessage.java @@ -357,14 +357,18 @@ public class SmsMessage extends SmsMessageBase { } /** - * Calculate the number of septets needed to encode the message. + * Get an SMS-SUBMIT PDU for a data message to a destination address & port * - * @param messageBody the message to encode - * @param force ignore (but still count) illegal characters if true - * @return septet count, or -1 on failure + * @param destAddr the address of the destination for the message + * @param userDara the data for the message + * @param statusReportRequested Indicates whether a report is requested for this message. + * @return a <code>SubmitPdu</code> containing the encoded SC + * address, if applicable, and the encoded message. + * Returns null on encode error. */ - public static int calc7bitEncodedLength(CharSequence msgBody, boolean force) { - return BearerData.calc7bitEncodedLength(msgBody.toString(), force); + public static SubmitPdu getSubmitPdu(String destAddr, UserData userData, + boolean statusReportRequested) { + return privateGetSubmitPdu(destAddr, statusReportRequested, userData); } /** @@ -442,6 +446,18 @@ public class SmsMessage extends SmsMessageBase { } /** + * Calculate the number of septets needed to encode the message. + * + * @param messageBody the message to encode + * @param use7bitOnly ignore (but still count) illegal characters if true + * @return TextEncodingDetails + */ + public static TextEncodingDetails calculateLength(CharSequence messageBody, + boolean use7bitOnly) { + return BearerData.calcTextEncodingDetails(messageBody.toString(), use7bitOnly); + } + + /** * Returns the teleservice type of the message. * @return the teleservice: * {@link com.android.internal.telephony.cdma.sms.SmsEnvelope#TELESERVICE_NOT_SET}, @@ -627,12 +643,15 @@ public class SmsMessage extends SmsMessageBase { bearerData.userData = userData; bearerData.hasUserDataHeader = (userData.userDataHeader != null); + int teleservice = bearerData.hasUserDataHeader ? + SmsEnvelope.TELESERVICE_WEMT : SmsEnvelope.TELESERVICE_WMT; + byte[] encodedBearerData = BearerData.encode(bearerData); if (encodedBearerData == null) return null; SmsEnvelope envelope = new SmsEnvelope(); envelope.messageType = SmsEnvelope.MESSAGE_TYPE_POINT_TO_POINT; - envelope.teleService = SmsEnvelope.TELESERVICE_WMT; + envelope.teleService = teleservice; envelope.destAddress = destAddr; envelope.bearerReply = RETURN_ACK; envelope.bearerData = encodedBearerData; diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java index 3c45aa4fb456..a835deef1a9e 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/BearerData.java @@ -17,6 +17,7 @@ package com.android.internal.telephony.cdma.sms; import android.util.Log; +import android.util.SparseIntArray; import android.telephony.SmsMessage; @@ -26,6 +27,7 @@ import com.android.internal.telephony.IccUtils; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.cdma.sms.UserData; +import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; import com.android.internal.util.HexDump; import com.android.internal.util.BitwiseInputStream; @@ -35,7 +37,7 @@ import com.android.internal.util.BitwiseOutputStream; /** * An object to encode and decode CDMA SMS bearer data. */ -public final class BearerData{ +public final class BearerData { private final static String LOG_TAG = "SMS"; /** @@ -385,59 +387,64 @@ public final class BearerData{ outStream.skip(3); } - private static class SeptetData { - byte data[]; - int septetCount; + private static int countAsciiSeptets(CharSequence msg, boolean force) { + int msgLen = msg.length(); + if (force) return msgLen; + for (int i = 0; i < msgLen; i++) { + if (UserData.charToAscii.get(msg.charAt(i), -1) == -1) { + return -1; + } + } + return msgLen; + } - SeptetData(byte[] data, int septetCount) { - this.data = data; - this.septetCount = septetCount; + /** + * Calculate the message text encoding length, fragmentation, and other details. + * + * @param force ignore (but still count) illegal characters if true + * @return septet count, or -1 on failure + */ + public static TextEncodingDetails calcTextEncodingDetails(CharSequence msg, + boolean force7BitEncoding) { + TextEncodingDetails ted; + int septets = countAsciiSeptets(msg, force7BitEncoding); + if (septets != -1 && septets <= SmsMessage.MAX_USER_DATA_SEPTETS) { + ted = new TextEncodingDetails(); + ted.msgCount = 1; + ted.codeUnitCount = septets; + ted.codeUnitsRemaining = SmsMessage.MAX_USER_DATA_SEPTETS - septets; + ted.codeUnitSize = SmsMessage.ENCODING_7BIT; + } else { + ted = com.android.internal.telephony.gsm.SmsMessage.calculateLength( + msg, force7BitEncoding); } + return ted; } - private static SeptetData encode7bitAscii(String msg, boolean force) + private static byte[] encode7bitAscii(String msg, boolean force) throws CodingException { try { BitwiseOutputStream outStream = new BitwiseOutputStream(msg.length()); - byte[] expandedData = msg.getBytes("US-ASCII"); - for (int i = 0; i < expandedData.length; i++) { - int charCode = expandedData[i]; - // Test ourselves for ASCII membership, since Java seems not to care. - if ((charCode < UserData.PRINTABLE_ASCII_MIN_INDEX) || - (charCode > UserData.PRINTABLE_ASCII_MAX_INDEX)) { + int msgLen = msg.length(); + for (int i = 0; i < msgLen; i++) { + int charCode = UserData.charToAscii.get(msg.charAt(i), -1); + if (charCode == -1) { if (force) { outStream.write(7, UserData.UNENCODABLE_7_BIT_CHAR); } else { - throw new CodingException("illegal ASCII code (" + charCode + ")"); + throw new CodingException("cannot ASCII encode (" + msg.charAt(i) + ")"); } } else { - outStream.write(7, expandedData[i]); + outStream.write(7, charCode); } } - return new SeptetData(outStream.toByteArray(), expandedData.length); - } catch (java.io.UnsupportedEncodingException ex) { - throw new CodingException("7bit ASCII encode failed: " + ex); + return outStream.toByteArray(); } catch (BitwiseOutputStream.AccessException ex) { throw new CodingException("7bit ASCII encode failed: " + ex); } } - /** - * Calculate the number of septets needed to encode the message. - * - * @param force ignore (but still count) illegal characters if true - * @return septet count, or -1 on failure - */ - public static int calc7bitEncodedLength(String msg, boolean force) { - try { - SeptetData data = encode7bitAscii(msg, force); - return data.septetCount; - } catch (CodingException ex) { - return -1; - } - } - private static byte[] encodeUtf16(String msg) throws CodingException { @@ -452,8 +459,10 @@ public final class BearerData{ throws CodingException { try { - /** - * TODO(cleanup): find some way to do this without the copy. + /* + * TODO(cleanup): It would be nice if GsmAlphabet provided + * an option to produce just the data without prepending + * the length. */ byte []fullData = GsmAlphabet.stringToGsm7BitPacked(msg); byte []data = new byte[fullData.length - 1]; @@ -470,54 +479,65 @@ public final class BearerData{ throws CodingException { byte[] headerData = null; + // TODO: if there is a header, meaning EMS mode, we probably + // also want the total UD length prior to the UDH length... if (uData.userDataHeader != null) headerData = SmsHeader.toByteArray(uData.userDataHeader); int headerDataLen = (headerData == null) ? 0 : headerData.length + 1; // + length octet byte[] payloadData; + int codeUnitCount; if (uData.msgEncodingSet) { if (uData.msgEncoding == UserData.ENCODING_OCTET) { if (uData.payload == null) { Log.e(LOG_TAG, "user data with octet encoding but null payload"); - // TODO(code_review): reasonable for fail case? or maybe bail on encoding? payloadData = new byte[0]; + codeUnitCount = 0; } else { payloadData = uData.payload; + codeUnitCount = uData.payload.length; } } else { if (uData.payloadStr == null) { Log.e(LOG_TAG, "non-octet user data with null payloadStr"); - // TODO(code_review): reasonable for fail case? or maybe bail on encoding? uData.payloadStr = ""; } if (uData.msgEncoding == UserData.ENCODING_GSM_7BIT_ALPHABET) { payloadData = encode7bitGsm(uData.payloadStr); + codeUnitCount = (payloadData.length * 8) / 7; } else if (uData.msgEncoding == UserData.ENCODING_7BIT_ASCII) { - SeptetData septetData = encode7bitAscii(uData.payloadStr, true); - payloadData = septetData.data; + payloadData = encode7bitAscii(uData.payloadStr, true); + codeUnitCount = uData.payloadStr.length(); } else if (uData.msgEncoding == UserData.ENCODING_UNICODE_16) { payloadData = encodeUtf16(uData.payloadStr); + codeUnitCount = uData.payloadStr.length(); } else { throw new CodingException("unsupported user data encoding (" + uData.msgEncoding + ")"); } - uData.numFields = uData.payloadStr.length(); } } else { if (uData.payloadStr == null) { Log.e(LOG_TAG, "user data with null payloadStr"); - // TODO(code_review): reasonable for fail case? or maybe bail on encoding? uData.payloadStr = ""; } try { - SeptetData septetData = encode7bitAscii(uData.payloadStr, false); - payloadData = septetData.data; - uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; + if (headerData == null) { + payloadData = encode7bitAscii(uData.payloadStr, false); + codeUnitCount = uData.payloadStr.length(); + uData.msgEncoding = UserData.ENCODING_7BIT_ASCII; + } else { + // If there is a header, we are in EMS mode, in + // which case we use GSM encodings. + payloadData = encode7bitGsm(uData.payloadStr); + codeUnitCount = (payloadData.length * 8) / 7; + uData.msgEncoding = UserData.ENCODING_GSM_7BIT_ALPHABET; + } } catch (CodingException ex) { payloadData = encodeUtf16(uData.payloadStr); + codeUnitCount = uData.payloadStr.length(); uData.msgEncoding = UserData.ENCODING_UNICODE_16; } uData.msgEncodingSet = true; - uData.numFields = uData.payloadStr.length(); } int totalLength = payloadData.length + headerDataLen; @@ -526,6 +546,7 @@ public final class BearerData{ " > " + SmsMessage.MAX_USER_DATA_BYTES + " bytes)"); } + uData.numFields = codeUnitCount; uData.payload = new byte[totalLength]; if (headerData != null) { uData.payload[0] = (byte)headerData.length; diff --git a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java index 8d4e76943211..d8a48ccd3c42 100644 --- a/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java +++ b/telephony/java/com/android/internal/telephony/cdma/sms/UserData.java @@ -16,6 +16,8 @@ package com.android.internal.telephony.cdma.sms; +import android.util.SparseIntArray; + import com.android.internal.telephony.SmsHeader; import com.android.internal.util.HexDump; @@ -40,6 +42,10 @@ public class UserData { /** * IA5 data encoding character mappings. * (See CCITT Rec. T.50 Tables 1 and 3) + * + * Note this mapping is the the same as for printable ASCII + * characters, with a 0x20 offset, meaning that the ASCII SPACE + * character occurs with code 0x20. */ public static final char[] IA5_MAP = { ' ', '!', '"', '#', '$', '%', '&', '\'', '(', ')', '*', '+', ',', '-', '.', '/', @@ -61,7 +67,16 @@ public class UserData { * Only elements between these indices in the ASCII table are printable. */ public static final int PRINTABLE_ASCII_MIN_INDEX = 0x20; - public static final int PRINTABLE_ASCII_MAX_INDEX = 0x7F; + public static final int ASCII_LF_INDEX = 0x0A; + public static final int ASCII_CR_INDEX = 0x0D; + public static final SparseIntArray charToAscii = new SparseIntArray(); + static { + for (int i = 0; i < IA5_MAP.length; i++) { + charToAscii.put(IA5_MAP[i], PRINTABLE_ASCII_MIN_INDEX + i); + } + charToAscii.put('\r', ASCII_LF_INDEX); + charToAscii.put('\n', ASCII_CR_INDEX); + } /** * Mapping for IA5 values less than 32 are flow control signals diff --git a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java index a15bbdf8cfcd..f1207e4ad600 100644 --- a/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java +++ b/telephony/java/com/android/internal/telephony/gsm/SmsMessage.java @@ -26,6 +26,7 @@ import com.android.internal.telephony.EncodeException; import com.android.internal.telephony.GsmAlphabet; import com.android.internal.telephony.SmsHeader; import com.android.internal.telephony.SmsMessageBase; +import com.android.internal.telephony.SmsMessageBase.TextEncodingDetails; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; @@ -742,17 +743,39 @@ public class SmsMessage extends SmsMessageBase{ /** * Calculate the number of septets needed to encode the message. * - * @param messageBody the message to encode - * @param force ignore (but still count) illegal characters if true - * @return septet count, or -1 on failure + * @param msgBody the message to encode + * @param use7bitOnly ignore (but still count) illegal characters if true + * @return TextEncodingDetails */ - public static int calc7bitEncodedLength(CharSequence messageBody, boolean force) { + public static TextEncodingDetails calculateLength(CharSequence msgBody, + boolean use7bitOnly) { + TextEncodingDetails ted = new TextEncodingDetails(); try { - return GsmAlphabet.countGsmSeptets(messageBody, !force); + int septets = GsmAlphabet.countGsmSeptets(msgBody, !use7bitOnly); + ted.codeUnitCount = septets; + if (septets > MAX_USER_DATA_SEPTETS) { + ted.msgCount = (septets / MAX_USER_DATA_SEPTETS_WITH_HEADER) + 1; + ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS_WITH_HEADER + - (septets % MAX_USER_DATA_SEPTETS_WITH_HEADER); + } else { + ted.msgCount = 1; + ted.codeUnitsRemaining = MAX_USER_DATA_SEPTETS - septets; + } + ted.codeUnitSize = ENCODING_7BIT; } catch (EncodeException ex) { - /* Just fall through to the -1 error result below. */ + int octets = msgBody.length() * 2; + ted.codeUnitCount = msgBody.length(); + if (octets > MAX_USER_DATA_BYTES) { + ted.msgCount = (octets / MAX_USER_DATA_BYTES_WITH_HEADER) + 1; + ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES_WITH_HEADER + - (octets % MAX_USER_DATA_BYTES_WITH_HEADER))/2; + } else { + ted.msgCount = 1; + ted.codeUnitsRemaining = (MAX_USER_DATA_BYTES - octets)/2; + } + ted.codeUnitSize = ENCODING_16BIT; } - return -1; + return ted; } /** {@inheritDoc} */ diff --git a/test-runner/android/test/mock/MockContext.java b/test-runner/android/test/mock/MockContext.java index 9e0cf2cab46e..efc48808ea07 100644 --- a/test-runner/android/test/mock/MockContext.java +++ b/test-runner/android/test/mock/MockContext.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.BroadcastReceiver; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Resources; @@ -100,6 +101,11 @@ public class MockContext extends Context { } @Override + public ApplicationInfo getApplicationInfo() { + throw new UnsupportedOperationException(); + } + + @Override public String getPackageResourcePath() { throw new UnsupportedOperationException(); } diff --git a/test-runner/android/test/mock/MockPackageManager.java b/test-runner/android/test/mock/MockPackageManager.java index 5e7802d2994d..d5cd6ef5c5ba 100644 --- a/test-runner/android/test/mock/MockPackageManager.java +++ b/test-runner/android/test/mock/MockPackageManager.java @@ -16,7 +16,6 @@ package android.test.mock; -import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; @@ -63,11 +62,6 @@ public class MockPackageManager extends PackageManager { } @Override - public ResolveInfo resolveActivity(Intent intent, int flags, String packageName) { - throw new UnsupportedOperationException(); - } - - @Override public int[] getPackageGids(String packageName) throws NameNotFoundException { throw new UnsupportedOperationException(); } @@ -332,21 +326,12 @@ public class MockPackageManager extends PackageManager { long idealStorageSize, IPackageDataObserver observer) { throw new UnsupportedOperationException(); } - - /** - * @hide - to match hiding in superclass - */ - @Override - public void freeStorage( - long idealStorageSize, PendingIntent onFinishedIntent) { - throw new UnsupportedOperationException(); - } /** * @hide - to match hiding in superclass */ @Override - public void freeStorageWithIntent( + public void freeStorage( long idealStorageSize, IntentSender pi) { throw new UnsupportedOperationException(); } diff --git a/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java b/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java index 3daa8abbde9b..fb1b9ad5bcff 100755 --- a/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/AppCacheTest.java @@ -507,7 +507,7 @@ public class AppCacheTest extends AndroidTestCase { try { // Spin lock waiting for call back synchronized(r) { - getPm().freeStorage(idealStorageSize, pi); + getPm().freeStorage(idealStorageSize, pi.getIntentSender()); long waitTime = 0; while(!r.isDone() && (waitTime < MAX_WAIT_TIME)) { r.wait(WAIT_TIME_INCR); diff --git a/tests/backup/src/com/android/backuptest/BackupTestActivity.java b/tests/backup/src/com/android/backuptest/BackupTestActivity.java index d87e85c51d6c..f0c3f93a8f43 100644 --- a/tests/backup/src/com/android/backuptest/BackupTestActivity.java +++ b/tests/backup/src/com/android/backuptest/BackupTestActivity.java @@ -161,8 +161,13 @@ public class BackupTestActivity extends ListActivity new FileRestoreHelper(BackupTestActivity.this)); FileInputStream dataFile = openFileInput("backup_test"); BackupDataInput data = new BackupDataInput(dataFile.getFD()); - dispatch.dispatch(data); + ParcelFileDescriptor state = ParcelFileDescriptor.open( + new File(getFilesDir(), "restore_state"), + ParcelFileDescriptor.MODE_READ_WRITE|ParcelFileDescriptor.MODE_CREATE| + ParcelFileDescriptor.MODE_TRUNCATE); + dispatch.dispatch(data, state); dataFile.close(); + state.close(); } catch (IOException ex) { throw new RuntimeException(ex); } diff --git a/tests/backup/src/com/android/backuptest/BackupTestAgent.java b/tests/backup/src/com/android/backuptest/BackupTestAgent.java index e3566ec33564..0da41519a007 100644 --- a/tests/backup/src/com/android/backuptest/BackupTestAgent.java +++ b/tests/backup/src/com/android/backuptest/BackupTestAgent.java @@ -55,7 +55,7 @@ public class BackupTestAgent extends BackupAgent // dispatch.addHelper(SHARED_PREFS, new SharedPrefsRestoreHelper(this)); dispatch.addHelper(DATA_FILES, new FileRestoreHelper(this)); - dispatch.dispatch(data); + dispatch.dispatch(data, newState); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java index 47a7ec035ea1..d0a1c46bf644 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/Bridge.java @@ -55,6 +55,8 @@ import android.view.View.AttachInfo; import android.view.View.MeasureSpec; import android.view.WindowManager.LayoutParams; import android.widget.FrameLayout; +import android.widget.TabHost; +import android.widget.TabWidget; import java.lang.ref.SoftReference; import java.lang.reflect.Field; @@ -69,10 +71,10 @@ import java.util.Map; * {@link #computeLayout(IXmlPullParser, Object, int, int, String, boolean, Map, Map, IProjectCallback, ILayoutLog)}. */ public final class Bridge implements ILayoutBridge { - + private static final int DEFAULT_TITLE_BAR_HEIGHT = 25; private static final int DEFAULT_STATUS_BAR_HEIGHT = 25; - + public static class StaticMethodNotImplementedException extends RuntimeException { private static final long serialVersionUID = 1L; @@ -82,19 +84,20 @@ public final class Bridge implements ILayoutBridge { } /** - * Maps from id to resource name/type. + * Maps from id to resource name/type. This is for android.R only. */ private final static Map<Integer, String[]> sRMap = new HashMap<Integer, String[]>(); /** - * Same as sRMap except for int[] instead of int resources. + * Same as sRMap except for int[] instead of int resources. This is for android.R only. */ private final static Map<int[], String> sRArrayMap = new HashMap<int[], String>(); /** - * Reverse map compared to sRMap, resource type -> (resource name -> id) + * Reverse map compared to sRMap, resource type -> (resource name -> id). + * This is for android.R only. */ private final static Map<String, Map<String, Integer>> sRFullMap = new HashMap<String, Map<String,Integer>>(); - + private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache = new HashMap<Object, Map<String, SoftReference<Bitmap>>>(); private final static Map<Object, Map<String, SoftReference<NinePatch>>> sProject9PatchCache = @@ -104,7 +107,7 @@ public final class Bridge implements ILayoutBridge { new HashMap<String, SoftReference<Bitmap>>(); private final static Map<String, SoftReference<NinePatch>> sFramework9PatchCache = new HashMap<String, SoftReference<NinePatch>>(); - + private static Map<String, Map<String, Integer>> sEnumValueMap; /** @@ -156,14 +159,14 @@ public final class Bridge implements ILayoutBridge { return sinit(fontOsLocation, enumValueMap); } - + private static synchronized boolean sinit(String fontOsLocation, Map<String, Map<String, Integer>> enumValueMap) { // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener // on static (native) methods which prints the signature on the console and // throws an exception. - // This is useful when testing the rendering in ADT to identify static native + // This is useful when testing the rendering in ADT to identify static native // methods that are ignored -- layoutlib_create makes them returns 0/false/null // which is generally OK yet might be a problem, so this is how you'd find out. // @@ -214,7 +217,7 @@ public final class Bridge implements ILayoutBridge { } else { return false; } - + sEnumValueMap = enumValueMap; // now parse com.android.internal.R (and only this one as android.R is a subset of @@ -226,13 +229,13 @@ public final class Bridge implements ILayoutBridge { // int[] does not implement equals/hashCode, and if the parsing used a different class // loader for the R class, this would NOT work. Class<?> r = com.android.internal.R.class; - + for (Class<?> inner : r.getDeclaredClasses()) { String resType = inner.getSimpleName(); Map<String, Integer> fullMap = new HashMap<String, Integer>(); sRFullMap.put(resType, fullMap); - + for (Field f : inner.getDeclaredFields()) { // only process static final fields. Since the final attribute may have // been altered by layoutlib_create, we only check static @@ -243,7 +246,7 @@ public final class Bridge implements ILayoutBridge { // if the object is an int[] we put it in sRArrayMap sRArrayMap.put((int[]) f.get(null), f.getName()); } else if (type == int.class) { - Integer value = (Integer) f.get(null); + Integer value = (Integer) f.get(null); sRMap.put(value, new String[] { f.getName(), resType }); fullMap.put(f.getName(), value); } else { @@ -281,7 +284,7 @@ public final class Bridge implements ILayoutBridge { themeName = themeName.substring(1); isProjectTheme = true; } - + return computeLayout(layoutDescription, projectKey, screenWidth, screenHeight, DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY, DisplayMetrics.DEFAULT_DENSITY, @@ -294,6 +297,7 @@ public final class Bridge implements ILayoutBridge { * (non-Javadoc) * @see com.android.layoutlib.api.ILayoutBridge#computeLayout(com.android.layoutlib.api.IXmlPullParser, java.lang.Object, int, int, java.lang.String, boolean, java.util.Map, java.util.Map, com.android.layoutlib.api.IProjectCallback, com.android.layoutlib.api.ILayoutLog) */ + @Deprecated public ILayoutResult computeLayout(IXmlPullParser layoutDescription, Object projectKey, int screenWidth, int screenHeight, String themeName, boolean isProjectTheme, Map<String, Map<String, IResourceValue>> projectResources, @@ -319,7 +323,7 @@ public final class Bridge implements ILayoutBridge { if (logger == null) { logger = sDefaultLogger; } - + synchronized (sDefaultLogger) { sLogger = logger; } @@ -327,12 +331,12 @@ public final class Bridge implements ILayoutBridge { // find the current theme and compute the style inheritance map Map<IStyleResourceValue, IStyleResourceValue> styleParentMap = new HashMap<IStyleResourceValue, IStyleResourceValue>(); - + IStyleResourceValue currentTheme = computeStyleMaps(themeName, isProjectTheme, projectResources.get(BridgeConstants.RES_STYLE), frameworkResources.get(BridgeConstants.RES_STYLE), styleParentMap); - - BridgeContext context = null; + + BridgeContext context = null; try { // setup the display Metrics. DisplayMetrics metrics = new DisplayMetrics(); @@ -347,29 +351,32 @@ public final class Bridge implements ILayoutBridge { frameworkResources, styleParentMap, customViewLoader, logger); BridgeInflater inflater = new BridgeInflater(context, customViewLoader); context.setBridgeInflater(inflater); - + IResourceValue windowBackground = null; int screenOffset = 0; if (currentTheme != null) { windowBackground = context.findItemInStyle(currentTheme, "windowBackground"); windowBackground = context.resolveResValue(windowBackground); - + screenOffset = getScreenOffset(currentTheme, context); } - + // we need to make sure the Looper has been initialized for this thread. // this is required for View that creates Handler objects. if (Looper.myLooper() == null) { Looper.prepare(); } - + BridgeXmlBlockParser parser = new BridgeXmlBlockParser(layoutDescription, context, false /* platformResourceFlag */); - + ViewGroup root = new FrameLayout(context); - + View view = inflater.inflate(parser, root); - + + // post-inflate process. For now this supports TabHost/TabWidget + postInflateProcess(view, customViewLoader); + // set the AttachInfo on the root view. AttachInfo info = new AttachInfo(new WindowSession(), new Window(), new Handler(), null); @@ -392,16 +399,19 @@ public final class Bridge implements ILayoutBridge { // measure the views view.measure(w_spec, h_spec); view.layout(0, screenOffset, screenWidth, screenHeight); - + // draw them BridgeCanvas canvas = new BridgeCanvas(screenWidth, screenHeight - screenOffset, logger); - + root.draw(canvas); canvas.dispose(); - + return new LayoutResult(visit(((ViewGroup)view).getChildAt(0), context), canvas.getImage()); + } catch (PostInflateException e) { + return new LayoutResult(ILayoutResult.ERROR, "Error during post inflation process:\n" + + e.getMessage()); } catch (Throwable e) { // get the real cause of the exception. Throwable t = e; @@ -419,7 +429,7 @@ public final class Bridge implements ILayoutBridge { // Make sure to remove static references, otherwise we could not unload the lib BridgeResources.clearSystem(); BridgeAssetManager.clearSystem(); - + // Remove the global logger synchronized (sDefaultLogger) { sLogger = sDefaultLogger; @@ -437,18 +447,18 @@ public final class Bridge implements ILayoutBridge { sProject9PatchCache.remove(projectKey); } } - + /** * Returns details of a framework resource from its integer value. * @param value the integer value * @return an array of 2 strings containing the resource name and type, or null if the id - * does not match any resource. + * does not match any resource. */ public static String[] resolveResourceValue(int value) { return sRMap.get(value); - + } - + /** * Returns the name of a framework resource whose value is an int array. * @param array @@ -456,7 +466,7 @@ public final class Bridge implements ILayoutBridge { public static String resolveResourceValue(int[] array) { return sRArrayMap.get(array); } - + /** * Returns the integer id of a framework resource, from a given resource type and resource name. * @param type the type of the resource @@ -468,15 +478,15 @@ public final class Bridge implements ILayoutBridge { if (map != null) { return map.get(name); } - + return null; } - + static Map<String, Integer> getEnumValues(String attributeName) { if (sEnumValueMap != null) { return sEnumValueMap.get(attributeName); } - + return null; } @@ -507,13 +517,13 @@ public final class Bridge implements ILayoutBridge { return result; } - + /** * Compute style information from the given list of style for the project and framework. * @param themeName the name of the current theme. In order to differentiate project and * platform themes sharing the same name, all project themes must be prepended with * a '*' character. - * @param isProjectTheme Is this a project theme + * @param isProjectTheme Is this a project theme * @param inProjectStyleMap the project style map * @param inFrameworkStyleMap the framework style map * @param outInheritanceMap the map of style inheritance. This is filled by the method @@ -523,23 +533,23 @@ public final class Bridge implements ILayoutBridge { String themeName, boolean isProjectTheme, Map<String, IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap, Map<IStyleResourceValue, IStyleResourceValue> outInheritanceMap) { - + if (inProjectStyleMap != null && inFrameworkStyleMap != null) { // first, get the theme IResourceValue theme = null; - + // project theme names have been prepended with a * if (isProjectTheme) { theme = inProjectStyleMap.get(themeName); } else { theme = inFrameworkStyleMap.get(themeName); } - + if (theme instanceof IStyleResourceValue) { // compute the inheritance map for both the project and framework styles computeStyleInheritance(inProjectStyleMap.values(), inProjectStyleMap, inFrameworkStyleMap, outInheritanceMap); - + // Compute the style inheritance for the framework styles/themes. // Since, for those, the style parent values do not contain 'android:' // we want to force looking in the framework style only to avoid using @@ -547,11 +557,11 @@ public final class Bridge implements ILayoutBridge { // To do this, we pass null in lieu of the project style map. computeStyleInheritance(inFrameworkStyleMap.values(), null /*inProjectStyleMap */, inFrameworkStyleMap, outInheritanceMap); - + return (IStyleResourceValue)theme; } } - + return null; } @@ -573,7 +583,7 @@ public final class Bridge implements ILayoutBridge { // first look for a specified parent. String parentName = style.getParentStyle(); - + // no specified parent? try to infer it from the name of the style. if (parentName == null) { parentName = getParentName(value.getName()); @@ -581,7 +591,7 @@ public final class Bridge implements ILayoutBridge { if (parentName != null) { parentStyle = getStyle(parentName, inProjectStyleMap, inFrameworkStyleMap); - + if (parentStyle != null) { outInheritanceMap.put(style, parentStyle); } @@ -589,7 +599,7 @@ public final class Bridge implements ILayoutBridge { } } } - + /** * Searches for and returns the {@link IStyleResourceValue} from a given name. * <p/>The format of the name can be: @@ -607,27 +617,27 @@ public final class Bridge implements ILayoutBridge { Map<String, IResourceValue> inProjectStyleMap, Map<String, IResourceValue> inFrameworkStyleMap) { boolean frameworkOnly = false; - + String name = parentName; - + // remove the useless @ if it's there if (name.startsWith(BridgeConstants.PREFIX_RESOURCE_REF)) { name = name.substring(BridgeConstants.PREFIX_RESOURCE_REF.length()); } - + // check for framework identifier. if (name.startsWith(BridgeConstants.PREFIX_ANDROID)) { frameworkOnly = true; name = name.substring(BridgeConstants.PREFIX_ANDROID.length()); } - + // at this point we could have the format style/<name>. we want only the name if (name.startsWith(BridgeConstants.REFERENCE_STYLE)) { name = name.substring(BridgeConstants.REFERENCE_STYLE.length()); } IResourceValue parent = null; - + // if allowed, search in the project resources. if (frameworkOnly == false && inProjectStyleMap != null) { parent = inProjectStyleMap.get(name); @@ -637,17 +647,17 @@ public final class Bridge implements ILayoutBridge { if (parent == null) { parent = inFrameworkStyleMap.get(name); } - + // make sure the result is the proper class type and return it. if (parent instanceof IStyleResourceValue) { return (IStyleResourceValue)parent; } - + sLogger.error(String.format("Unable to resolve parent style name: ", parentName)); - + return null; } - + /** * Computes the name of the parent style, or <code>null</code> if the style is a root style. */ @@ -656,10 +666,10 @@ public final class Bridge implements ILayoutBridge { if (index != -1) { return styleName.substring(0, index); } - + return null; } - + /** * Returns the top screen offset. This depends on whether the current theme defines the user * of the title and status bars. @@ -670,7 +680,7 @@ public final class Bridge implements ILayoutBridge { // get the title bar flag from the current theme. IResourceValue value = context.findItemInStyle(currentTheme, "windowNoTitle"); - + // because it may reference something else, we resolve it. value = context.resolveResValue(value); @@ -679,10 +689,10 @@ public final class Bridge implements ILayoutBridge { XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { // get value from the theme. value = context.findItemInStyle(currentTheme, "windowTitleSize"); - + // resolve it value = context.resolveResValue(value); - + // default value offset = DEFAULT_TITLE_BAR_HEIGHT; @@ -690,17 +700,17 @@ public final class Bridge implements ILayoutBridge { if (value != null) { TypedValue typedValue = ResourceHelper.getValue(value.getValue()); if (typedValue != null) { - offset = (int)typedValue.getDimension(context.getResources().mMetrics); + offset = (int)typedValue.getDimension(context.getResources().mMetrics); } } } - + // get the fullscreen flag from the current theme. value = context.findItemInStyle(currentTheme, "windowFullscreen"); - + // because it may reference something else, we resolve it. value = context.resolveResValue(value); - + if (value == null || value.getValue() == null || XmlUtils.convertValueToBoolean(value.getValue(), false /* defValue */) == false) { // FIXME: Right now this is hard-coded in the platform, but once there's a constant, we'll need to use it. @@ -711,6 +721,94 @@ public final class Bridge implements ILayoutBridge { } /** + * Post process on a view hierachy that was just inflated. + * <p/>At the moment this only support TabHost: If {@link TabHost} is detected, look for the + * {@link TabWidget}, and the corresponding {@link FrameLayout} and make new tabs automatically + * based on the content of the {@link FrameLayout}. + * @param view the root view to process. + * @param projectCallback callback to the project. + */ + private void postInflateProcess(View view, IProjectCallback projectCallback) + throws PostInflateException { + if (view instanceof TabHost) { + setupTabHost((TabHost)view, projectCallback); + } else if (view instanceof ViewGroup) { + ViewGroup group = (ViewGroup)view; + final int count = group.getChildCount(); + for (int c = 0 ; c < count ; c++) { + View child = group.getChildAt(c); + postInflateProcess(child, projectCallback); + } + } + } + + /** + * Sets up a {@link TabHost} object. + * @param tabHost the TabHost to setup. + * @param projectCallback The project callback object to access the project R class. + * @throws PostInflateException + */ + private void setupTabHost(TabHost tabHost, IProjectCallback projectCallback) + throws PostInflateException { + // look for the TabWidget, and the FrameLayout. They have their own specific names + View v = tabHost.findViewById(android.R.id.tabs); + + if (v == null) { + throw new PostInflateException( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n"); + } + + if ((v instanceof TabWidget) == false) { + throw new PostInflateException(String.format( + "TabHost requires a TabWidget with id \"android:id/tabs\".\n" + + "View found with id 'tabs' is '%s'", v.getClass().getCanonicalName())); + } + + v = tabHost.findViewById(android.R.id.tabcontent); + + if (v == null) { + // TODO: see if we can fake tabs even without the FrameLayout (same below when the framelayout is empty) + throw new PostInflateException( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\"."); + } + + if ((v instanceof FrameLayout) == false) { + throw new PostInflateException(String.format( + "TabHost requires a FrameLayout with id \"android:id/tabcontent\".\n" + + "View found with id 'tabcontent' is '%s'", v.getClass().getCanonicalName())); + } + + FrameLayout content = (FrameLayout)v; + + // now process the content of the framelayout and dynamically create tabs for it. + final int count = content.getChildCount(); + + if (count == 0) { + throw new PostInflateException( + "The FrameLayout for the TabHost has no content. Rendering failed.\n"); + } + + // this must be called before addTab() so that the TabHost searches its TabWidget + // and FrameLayout. + tabHost.setup(); + + // for each child of the framelayout, add a new TabSpec + for (int i = 0 ; i < count ; i++) { + View child = content.getChildAt(i); + String tabSpec = String.format("tab_spec%d", i+1); + int id = child.getId(); + String[] resource = projectCallback.resolveResourceValue(id); + String name; + if (resource != null) { + name = resource[0]; // 0 is resource name, 1 is resource type. + } else { + name = String.format("Tab %d", i+1); // default name if id is unresolved. + } + tabHost.addTab(tabHost.newTabSpec(tabSpec).setIndicator(name).setContent(id)); + } + } + + /** * Returns the bitmap for a specific path, from a specific project cache, or from the * framework cache. * @param value the path of the bitmap @@ -750,7 +848,7 @@ public final class Bridge implements ILayoutBridge { map = new HashMap<String, SoftReference<Bitmap>>(); sProjectBitmapCache.put(projectKey, map); } - + map.put(value, new SoftReference<Bitmap>(bmp)); } else { sFrameworkBitmapCache.put(value, new SoftReference<Bitmap>(bmp)); @@ -767,7 +865,7 @@ public final class Bridge implements ILayoutBridge { static NinePatch getCached9Patch(String value, Object projectKey) { if (projectKey != null) { Map<String, SoftReference<NinePatch>> map = sProject9PatchCache.get(projectKey); - + if (map != null) { SoftReference<NinePatch> ref = map.get(value); if (ref != null) { @@ -780,7 +878,7 @@ public final class Bridge implements ILayoutBridge { return ref.get(); } } - + return null; } @@ -798,13 +896,21 @@ public final class Bridge implements ILayoutBridge { map = new HashMap<String, SoftReference<NinePatch>>(); sProject9PatchCache.put(projectKey, map); } - + map.put(value, new SoftReference<NinePatch>(ninePatch)); } else { sFramework9PatchCache.put(value, new SoftReference<NinePatch>(ninePatch)); } } + private static final class PostInflateException extends Exception { + private static final long serialVersionUID = 1L; + + public PostInflateException(String message) { + super(message); + } + } + /** * Implementation of {@link IWindowSession} so that mSession is not null in * the {@link SurfaceView}. @@ -839,7 +945,7 @@ public final class Bridge implements ILayoutBridge { // pass for now. return false; } - + @SuppressWarnings("unused") public MotionEvent getPendingPointerMove(IWindow arg0) throws RemoteException { // pass for now. @@ -863,7 +969,7 @@ public final class Bridge implements ILayoutBridge { public void getDisplayFrame(IWindow window, Rect outDisplayFrame) { // pass for now. } - + @SuppressWarnings("unused") public void remove(IWindow arg0) throws RemoteException { // pass for now. @@ -883,13 +989,13 @@ public final class Bridge implements ILayoutBridge { Rect visibleInsets) { // pass for now. } - + public IBinder asBinder() { // pass for now. return null; } } - + /** * Implementation of {@link IWindow} to pass to the {@link AttachInfo}. */ diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java index d0896b5829fe..69f3d9c9ca99 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java @@ -29,6 +29,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Configuration; @@ -960,6 +961,12 @@ public final class BridgeContext extends Context { } @Override + public ApplicationInfo getApplicationInfo() { + // TODO Auto-generated method stub + return null; + } + + @Override public String getPackageResourcePath() { // TODO Auto-generated method stub return null; |