diff options
63 files changed, 4758 insertions, 858 deletions
@@ -75,6 +75,7 @@ LOCAL_SRC_FILES += \ core/java/android/app/IActivityWatcher.aidl \ core/java/android/app/IAlarmManager.aidl \ core/java/android/app/IBackupAgent.aidl \ + core/java/android/app/IDevicePolicyManager.aidl \ core/java/android/app/IInstrumentationWatcher.aidl \ core/java/android/app/INotificationManager.aidl \ core/java/android/app/ISearchManager.aidl \ diff --git a/api/current.xml b/api/current.xml index 96e7fd6..ee7d330 100644 --- a/api/current.xml +++ b/api/current.xml @@ -177,6 +177,17 @@ visibility="public" > </field> +<field name="BIND_DEVICE_ADMIN" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.permission.BIND_DEVICE_ADMIN"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="BIND_INPUT_METHOD" type="java.lang.String" transient="false" @@ -188,6 +199,17 @@ visibility="public" > </field> +<field name="BIND_WALLPAPER" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.permission.BIND_WALLPAPER"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="BLUETOOTH" type="java.lang.String" transient="false" @@ -14581,6 +14603,25 @@ <parameter name="key" type="java.lang.String"> </parameter> </method> +<method name="hasFeatures" + return="android.accounts.AccountManagerFuture<java.lang.Boolean>" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="account" type="android.accounts.Account"> +</parameter> +<parameter name="features" type="java.lang.String[]"> +</parameter> +<parameter name="callback" type="android.accounts.AccountManagerCallback<java.lang.Boolean>"> +</parameter> +<parameter name="handler" type="android.os.Handler"> +</parameter> +</method> <method name="invalidateAuthToken" return="void" abstract="false" @@ -14690,25 +14731,6 @@ <parameter name="value" type="java.lang.String"> </parameter> </method> -<method name="testHasFeatures" - return="android.accounts.AccountManagerFuture<java.lang.Boolean>" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="account" type="android.accounts.Account"> -</parameter> -<parameter name="features" type="java.lang.String[]"> -</parameter> -<parameter name="callback" type="android.accounts.AccountManagerCallback<java.lang.Boolean>"> -</parameter> -<parameter name="handler" type="android.os.Handler"> -</parameter> -</method> <method name="updateCredentials" return="android.accounts.AccountManagerFuture<android.os.Bundle>" abstract="false" @@ -19831,6 +19853,600 @@ </parameter> </method> </interface> +<class name="DeviceAdmin" + extends="android.content.BroadcastReceiver" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<constructor name="DeviceAdmin" + type="android.app.DeviceAdmin" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</constructor> +<method name="getManager" + return="android.app.DevicePolicyManager" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</method> +<method name="getWho" + return="android.content.ComponentName" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</method> +<method name="onDisabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> +<method name="onEnabled" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> +<method name="onPasswordChanged" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> +<method name="onPasswordFailed" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> +<method name="onPasswordSucceeded" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> +<method name="onReceive" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="intent" type="android.content.Intent"> +</parameter> +</method> +<field name="ACTION_DEVICE_ADMIN_DISABLED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.DEVICE_ADMIN_DISABLED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_DEVICE_ADMIN_ENABLED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.DEVICE_ADMIN_ENABLED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_PASSWORD_CHANGED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.ACTION_PASSWORD_CHANGED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_PASSWORD_FAILED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.ACTION_PASSWORD_FAILED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_PASSWORD_SUCCEEDED" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.ACTION_PASSWORD_SUCCEEDED"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="DEVICE_ADMIN_META_DATA" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.device_admin"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="DeviceAdminInfo" + extends="java.lang.Object" + abstract="false" + static="false" + final="true" + deprecated="not deprecated" + visibility="public" +> +<implements name="android.os.Parcelable"> +</implements> +<constructor name="DeviceAdminInfo" + type="android.app.DeviceAdminInfo" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="receiver" type="android.content.pm.ResolveInfo"> +</parameter> +<exception name="IOException" type="java.io.IOException"> +</exception> +<exception name="XmlPullParserException" type="org.xmlpull.v1.XmlPullParserException"> +</exception> +</constructor> +<method name="describeContents" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="dump" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="pw" type="android.util.Printer"> +</parameter> +<parameter name="prefix" type="java.lang.String"> +</parameter> +</method> +<method name="getActivityInfo" + return="android.content.pm.ActivityInfo" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getComponent" + return="android.content.ComponentName" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getPackageName" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getReceiverName" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="loadIcon" + return="android.graphics.drawable.Drawable" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="pm" type="android.content.pm.PackageManager"> +</parameter> +</method> +<method name="loadLabel" + return="java.lang.CharSequence" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="pm" type="android.content.pm.PackageManager"> +</parameter> +</method> +<method name="writeToParcel" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dest" type="android.os.Parcel"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="CREATOR" + type="android.os.Parcelable.Creator" + transient="false" + volatile="false" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> +<class name="DevicePolicyManager" + extends="java.lang.Object" + abstract="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="getActiveMinimumPasswordLength" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getActivePasswordMode" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getCurrentFailedPasswordAttempts" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getMaximumTimeToLock" + return="long" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getMinimumPasswordLength" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="getPasswordMode" + return="int" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +</method> +<method name="isAdminActive" + return="boolean" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="who" type="android.content.ComponentName"> +</parameter> +</method> +<method name="removeActiveAdmin" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="who" type="android.content.ComponentName"> +</parameter> +</method> +<method name="setMaximumTimeToLock" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="admin" type="android.content.ComponentName"> +</parameter> +<parameter name="timeMs" type="long"> +</parameter> +</method> +<method name="setMinimumPasswordLength" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="admin" type="android.content.ComponentName"> +</parameter> +<parameter name="length" type="int"> +</parameter> +</method> +<method name="setPasswordMode" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="admin" type="android.content.ComponentName"> +</parameter> +<parameter name="mode" type="int"> +</parameter> +</method> +<method name="wipeData" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="flags" type="int"> +</parameter> +</method> +<field name="ACTION_ADD_DEVICE_ADMIN" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.ADD_DEVICE_ADMIN"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="ACTION_SET_NEW_PASSWORD" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.action.SET_NEW_PASSWORD"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="EXTRA_DEVICE_ADMIN" + type="java.lang.String" + transient="false" + volatile="false" + value=""android.app.extra.DEVICE_ADMIN"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="PASSWORD_MODE_ALPHANUMERIC" + type="int" + transient="false" + volatile="false" + value="2000" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="PASSWORD_MODE_NUMERIC" + type="int" + transient="false" + volatile="false" + value="1000" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="PASSWORD_MODE_UNSPECIFIED" + type="int" + transient="false" + volatile="false" + value="0" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="WIPE_EXTERNAL_STORAGE" + type="int" + transient="false" + volatile="false" + value="2" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +<field name="WIPE_LOW_LEVEL_FORMAT" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> +</class> <class name="Dialog" extends="java.lang.Object" abstract="false" @@ -20739,6 +21355,19 @@ <parameter name="onKeyListener" type="android.content.DialogInterface.OnKeyListener"> </parameter> </method> +<method name="setOnShowListener" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="listener" type="android.content.DialogInterface.OnShowListener"> +</parameter> +</method> <method name="setOwnerActivity" return="void" abstract="false" @@ -32563,6 +33192,17 @@ visibility="public" > </field> +<field name="DEVICE_POLICY_SERVICE" + type="java.lang.String" + transient="false" + volatile="false" + value=""device_policy"" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="DROPBOX_SERVICE" type="java.lang.String" transient="false" @@ -34002,6 +34642,27 @@ </parameter> </method> </interface> +<interface name="DialogInterface.OnShowListener" + abstract="true" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<method name="onShow" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="dialog" type="android.content.DialogInterface"> +</parameter> +</method> +</interface> <class name="Entity" extends="java.lang.Object" abstract="false" @@ -51735,17 +52396,6 @@ <exception name="SQLException" type="android.database.SQLException"> </exception> </method> -<method name="resetCompiledSqlCache" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -</method> <method name="setLocale" return="void" abstract="false" @@ -51772,19 +52422,6 @@ <parameter name="lockingEnabled" type="boolean"> </parameter> </method> -<method name="setMaxSqlCacheSize" - return="void" - abstract="false" - native="false" - synchronized="false" - static="false" - final="false" - deprecated="not deprecated" - visibility="public" -> -<parameter name="cacheSize" type="int"> -</parameter> -</method> <method name="setMaximumSize" return="long" abstract="false" @@ -119426,6 +120063,19 @@ visibility="public" > </constructor> +<method name="getLastOutgoingCall" + return="java.lang.String" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +</method> <field name="CACHED_NAME" type="java.lang.String" transient="false" diff --git a/cmds/stagefright/stagefright.cpp b/cmds/stagefright/stagefright.cpp index ad6540a..e65cdf1 100644 --- a/cmds/stagefright/stagefright.cpp +++ b/cmds/stagefright/stagefright.cpp @@ -34,12 +34,14 @@ #include <media/stagefright/MetaData.h> #include <media/stagefright/OMXClient.h> #include <media/stagefright/OMXCodec.h> +#include <media/mediametadataretriever.h> using namespace android; static long gNumRepetitions; static long gMaxNumFrames; // 0 means decode all available. static long gReproduceBug; // if not -1. +static bool gPreferSoftwareCodec; static int64_t getNowUs() { struct timeval tv; @@ -59,7 +61,9 @@ static void playSource(OMXClient *client, const sp<MediaSource> &source) { rawSource = source; } else { rawSource = OMXCodec::Create( - client->interface(), meta, false /* createEncoder */, source); + client->interface(), meta, false /* createEncoder */, source, + NULL /* matchComponentName */, + gPreferSoftwareCodec ? OMXCodec::kPreferSoftwareCodecs : 0); if (rawSource == NULL) { fprintf(stderr, "Failed to instantiate decoder for '%s'.\n", mime); @@ -219,6 +223,8 @@ static void usage(const char *me) { fprintf(stderr, " -m max-number-of-frames-to-decode in each pass\n"); fprintf(stderr, " -b bug to reproduce\n"); fprintf(stderr, " -p(rofiles) dump decoder profiles supported\n"); + fprintf(stderr, " -t(humbnail) extract video thumbnail\n"); + fprintf(stderr, " -s(oftware) prefer software codec\n"); } int main(int argc, char **argv) { @@ -227,12 +233,14 @@ int main(int argc, char **argv) { bool audioOnly = false; bool listComponents = false; bool dumpProfiles = false; + bool extractThumbnail = false; gNumRepetitions = 1; gMaxNumFrames = 0; gReproduceBug = -1; + gPreferSoftwareCodec = false; int res; - while ((res = getopt(argc, argv, "han:lm:b:p")) >= 0) { + while ((res = getopt(argc, argv, "han:lm:b:pts")) >= 0) { switch (res) { case 'a': { @@ -274,6 +282,18 @@ int main(int argc, char **argv) { break; } + case 't': + { + extractThumbnail = true; + break; + } + + case 's': + { + gPreferSoftwareCodec = true; + break; + } + case '?': case 'h': default: @@ -288,6 +308,34 @@ int main(int argc, char **argv) { argc -= optind; argv += optind; + if (extractThumbnail) { + sp<IServiceManager> sm = defaultServiceManager(); + sp<IBinder> binder = sm->getService(String16("media.player")); + sp<IMediaPlayerService> service = + interface_cast<IMediaPlayerService>(binder); + + CHECK(service.get() != NULL); + + sp<IMediaMetadataRetriever> retriever = + service->createMetadataRetriever(getpid()); + + CHECK(retriever != NULL); + + for (int k = 0; k < argc; ++k) { + const char *filename = argv[k]; + + CHECK_EQ(retriever->setDataSource(filename), OK); + CHECK_EQ(retriever->setMode(METADATA_MODE_FRAME_CAPTURE_ONLY), OK); + + sp<IMemory> mem = retriever->captureFrame(); + + printf("captureFrame(%s) => %s\n", + filename, mem != NULL ? "OK" : "FAILED"); + } + + return 0; + } + if (dumpProfiles) { sp<IServiceManager> sm = defaultServiceManager(); sp<IBinder> binder = sm->getService(String16("media.player")); @@ -389,7 +437,8 @@ int main(int argc, char **argv) { sp<MetaData> meta; size_t i; for (i = 0; i < numTracks; ++i) { - meta = extractor->getTrackMetaData(i); + meta = extractor->getTrackMetaData( + i, MediaExtractor::kIncludeExtensiveMetaData); const char *mime; meta->findCString(kKeyMIMEType, &mime); @@ -403,6 +452,12 @@ int main(int argc, char **argv) { } } + int64_t thumbTimeUs; + if (meta->findInt64(kKeyThumbnailTime, &thumbTimeUs)) { + printf("thumbnailTime: %lld us (%.2f secs)\n", + thumbTimeUs, thumbTimeUs / 1E6); + } + mediaSource = extractor->getTrack(i); } diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java index 3bbfce8..414d963 100644 --- a/core/java/android/accounts/AccountManager.java +++ b/core/java/android/accounts/AccountManager.java @@ -254,12 +254,12 @@ public class AccountManager { * The future result is a {@link Boolean} that is true if the account exists and has the * specified features. */ - public AccountManagerFuture<Boolean> testHasFeatures(final Account account, + public AccountManagerFuture<Boolean> hasFeatures(final Account account, final String[] features, AccountManagerCallback<Boolean> callback, Handler handler) { return new Future2Task<Boolean>(handler, callback) { public void doWork() throws RemoteException { - mService.testHasFeatures(mResponse, account, features); + mService.hasFeatures(mResponse, account, features); } public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException { if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) { diff --git a/core/java/android/accounts/AccountManagerService.java b/core/java/android/accounts/AccountManagerService.java index f5166c2..ee26d3c 100644 --- a/core/java/android/accounts/AccountManagerService.java +++ b/core/java/android/accounts/AccountManagerService.java @@ -449,7 +449,7 @@ public class AccountManagerService return db.insert(TABLE_EXTRAS, EXTRAS_KEY, values); } - public void testHasFeatures(IAccountManagerResponse response, + public void hasFeatures(IAccountManagerResponse response, Account account, String[] features) { checkReadAccountsPermission(); long identityToken = clearCallingIdentity(); @@ -501,7 +501,7 @@ public class AccountManagerService } protected String toDebugString(long now) { - return super.toDebugString(now) + ", testHasFeatures" + return super.toDebugString(now) + ", hasFeatures" + ", " + mAccount + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); } diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl index cbd26ee..36a5653 100644 --- a/core/java/android/accounts/IAccountManager.aidl +++ b/core/java/android/accounts/IAccountManager.aidl @@ -31,8 +31,7 @@ interface IAccountManager { String getUserData(in Account account, String key); AuthenticatorDescription[] getAuthenticatorTypes(); Account[] getAccounts(String accountType); - void testHasFeatures(in IAccountManagerResponse response, in Account account, - in String[] features); + void hasFeatures(in IAccountManagerResponse response, in Account account, in String[] features); void getAccountsByFeatures(in IAccountManagerResponse response, String accountType, in String[] features); boolean addAccount(in Account account, String password, in Bundle extras); void removeAccount(in IAccountManagerResponse response, in Account account); @@ -47,7 +46,7 @@ interface IAccountManager { String authTokenType, boolean notifyOnAuthFailure, boolean expectActivityLaunch, in Bundle options); void addAcount(in IAccountManagerResponse response, String accountType, - String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch, + String authTokenType, in String[] requiredFeatures, boolean expectActivityLaunch, in Bundle options); void updateCredentials(in IAccountManagerResponse response, in Account account, String authTokenType, boolean expectActivityLaunch, in Bundle options); diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index d89b877..fe05393 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -186,6 +186,7 @@ class ApplicationContext extends Context { private boolean mRestricted; private AccountManager mAccountManager; // protected by mSync private DropBoxManager mDropBoxManager = null; + private DevicePolicyManager mDevicePolicyManager = null; private final Object mSync = new Object(); @@ -895,6 +896,8 @@ class ApplicationContext extends Context { return getWallpaperManager(); } else if (DROPBOX_SERVICE.equals(name)) { return getDropBoxManager(); + } else if (DEVICE_POLICY_SERVICE.equals(name)) { + return getDevicePolicyManager(); } return null; @@ -1064,6 +1067,16 @@ class ApplicationContext extends Context { return mDropBoxManager; } + private DevicePolicyManager getDevicePolicyManager() { + synchronized (mSync) { + if (mDevicePolicyManager == null) { + mDevicePolicyManager = new DevicePolicyManager(this, + mMainThread.getHandler()); + } + } + return mDevicePolicyManager; + } + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { diff --git a/core/java/android/app/DeviceAdmin.java b/core/java/android/app/DeviceAdmin.java new file mode 100644 index 0000000..4da3fee --- /dev/null +++ b/core/java/android/app/DeviceAdmin.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2010 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.app; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; + +/** + * Base class for implementing a device administration component. This + * class provides a convenience for interpreting the raw intent actions + * that are sent by the system. + * + * <p>When publishing your DeviceAdmin subclass as a receiver, it must + * handle {@link #ACTION_DEVICE_ADMIN_ENABLED} and require the + * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission. A typical + * manifest entry would look like:</p> + * + * <pre>{@include development/samples/ApiDemos/AndroidManifest.xml + * device_admin_declaration}</pre> + * + * <p>The meta-data referenced here provides addition information specific + * to the device administrator, as parsed by the {@link DeviceAdminInfo} class. + * A typical file would be:</p> + * + * <pre>{@include development/samples/ApiDemos/res/xml/sample_device_admin.xml + * meta_data}</pre> + */ +public class DeviceAdmin extends BroadcastReceiver { + private static String TAG = "DevicePolicy"; + private static boolean DEBUG = false; + private static boolean localLOGV = DEBUG || android.util.Config.LOGV; + + /** + * This is the primary action that a device administrator must implement to be + * allowed to manage a device. This will be set to the receiver + * when the user enables it for administration. You will generally + * handle this in {@link DeviceAdmin#onEnabled(Context, Intent)}. To be + * supported, the receiver must also require the + * {@link android.Manifest.permission#BIND_DEVICE_ADMIN} permission so + * that other applications can not abuse it. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_ADMIN_ENABLED + = "android.app.action.DEVICE_ADMIN_ENABLED"; + + /** + * Action sent to a device administrator when the user has disabled + * it. Upon return, the application no longer has access to the + * protected device policy manager APIs. You will generally + * handle this in {@link DeviceAdmin#onDisabled(Context, Intent)}. Note + * that this action will be + * sent the receiver regardless of whether it is explicitly listed in + * its intent filter. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_DEVICE_ADMIN_DISABLED + = "android.app.action.DEVICE_ADMIN_DISABLED"; + + /** + * Action sent to a device administrator when the user has changed the + * password of their device. You can at this point check the characteristics + * of the new password with {@link DevicePolicyManager#getActivePasswordMode() + * DevicePolicyManager.getActivePasswordMode()} and + * {@link DevicePolicyManager#getActiveMinimumPasswordLength() + * DevicePolicyManager.getActiveMinimumPasswordLength()}. You will generally + * handle this in {@link DeviceAdmin#onPasswordChanged(Context, Intent)}. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PASSWORD_CHANGED + = "android.app.action.ACTION_PASSWORD_CHANGED"; + + /** + * Action sent to a device administrator when the user has failed at + * attempted to enter the password. You can at this point check the + * number of failed password attempts there have been with + * {@link DevicePolicyManager#getCurrentFailedPasswordAttempts() + * DevicePolicyManager.getCurrentFailedPasswordAttempts()}. You will generally + * handle this in {@link DeviceAdmin#onPasswordFailed(Context, Intent)}. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PASSWORD_FAILED + = "android.app.action.ACTION_PASSWORD_FAILED"; + + /** + * Action sent to a device administrator when the user has successfully + * entered their password, after failing one or more times. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_PASSWORD_SUCCEEDED + = "android.app.action.ACTION_PASSWORD_SUCCEEDED"; + + /** + * Name under which an DevicePolicy component publishes information + * about itself. This meta-data must reference an XML resource containing + * a device-admin tag. XXX TO DO: describe syntax. + */ + public static final String DEVICE_ADMIN_META_DATA = "android.app.device_admin"; + + private DevicePolicyManager mManager; + private ComponentName mWho; + + /** + * Retrieve the DevicePolicyManager interface for this administrator to work + * with the system. + */ + public DevicePolicyManager getManager(Context context) { + if (mManager != null) { + return mManager; + } + mManager = (DevicePolicyManager)context.getSystemService( + Context.DEVICE_POLICY_SERVICE); + return mManager; + } + + /** + * Retrieve the ComponentName describing who this device administrator is, for + * use in {@link DevicePolicyManager} APIs that require the administrator to + * identify itself. + */ + public ComponentName getWho(Context context) { + if (mWho != null) { + return mWho; + } + mWho = new ComponentName(context, getClass()); + return mWho; + } + + /** + * Called after the administrator is first enabled, as a result of + * receiving {@link #ACTION_DEVICE_ADMIN_ENABLED}. At this point you + * can use {@link DevicePolicyManager} to set your desired policies. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + */ + public void onEnabled(Context context, Intent intent) { + } + + /** + * Called prior to the administrator being disabled, as a result of + * receiving {@link #ACTION_DEVICE_ADMIN_DISABLED}. Upon return, you + * can no longer use the protected parts of the {@link DevicePolicyManager} + * API. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + */ + public void onDisabled(Context context, Intent intent) { + } + + /** + * Called after the user has changed their password, as a result of + * receiving {@link #ACTION_PASSWORD_CHANGED}. At this point you + * can use {@link DevicePolicyManager#getCurrentFailedPasswordAttempts() + * DevicePolicyManager.getCurrentFailedPasswordAttempts()} + * to retrieve the active password characteristics. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + */ + public void onPasswordChanged(Context context, Intent intent) { + } + + /** + * Called after the user has failed at entering their current password, as a result of + * receiving {@link #ACTION_PASSWORD_FAILED}. At this point you + * can use {@link DevicePolicyManager} to retrieve the number of failed + * password attempts. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + */ + public void onPasswordFailed(Context context, Intent intent) { + } + + /** + * Called after the user has succeeded at entering their current password, + * as a result of receiving {@link #ACTION_PASSWORD_SUCCEEDED}. This will + * only be received the first time they succeed after having previously + * failed. + * @param context The running context as per {@link #onReceive}. + * @param intent The received intent as per {@link #onReceive}. + */ + public void onPasswordSucceeded(Context context, Intent intent) { + } + + /** + * Intercept standard device administrator broadcasts. Implementations + * should not override this method; it is better to implement the + * convenience callbacks for each action. + */ + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (ACTION_PASSWORD_CHANGED.equals(action)) { + onPasswordChanged(context, intent); + } else if (ACTION_PASSWORD_FAILED.equals(action)) { + onPasswordFailed(context, intent); + } else if (ACTION_PASSWORD_SUCCEEDED.equals(action)) { + onPasswordSucceeded(context, intent); + } else if (ACTION_DEVICE_ADMIN_ENABLED.equals(action)) { + onEnabled(context, intent); + } else if (ACTION_DEVICE_ADMIN_DISABLED.equals(action)) { + onDisabled(context, intent); + } + } +} diff --git a/core/java/android/app/DeviceAdminInfo.java b/core/java/android/app/DeviceAdminInfo.java new file mode 100644 index 0000000..eac6e46 --- /dev/null +++ b/core/java/android/app/DeviceAdminInfo.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2010 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.app; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.Printer; +import android.util.Xml; + +import java.io.IOException; + +/** + * This class is used to specify meta information of a device administrator + * component. + */ +public final class DeviceAdminInfo implements Parcelable { + static final String TAG = "DeviceAdminInfo"; + + /** + * The BroadcastReceiver that implements this device admin component. + */ + final ResolveInfo mReceiver; + + /** + * Constructor. + * + * @param context The Context in which we are parsing the device admin. + * @param receiver The ResolveInfo returned from the package manager about + * this device admin's component. + */ + public DeviceAdminInfo(Context context, ResolveInfo receiver) + throws XmlPullParserException, IOException { + mReceiver = receiver; + ActivityInfo ai = receiver.activityInfo; + + PackageManager pm = context.getPackageManager(); + + XmlResourceParser parser = null; + try { + parser = ai.loadXmlMetaData(pm, DeviceAdmin.DEVICE_ADMIN_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + + DeviceAdmin.DEVICE_ADMIN_META_DATA + " meta-data"); + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"device-admin".equals(nodeName)) { + throw new XmlPullParserException( + "Meta-data does not start with device-admin tag"); + } + + TypedArray sa = context.getResources().obtainAttributes(attrs, + com.android.internal.R.styleable.Wallpaper); + + sa.recycle(); + } finally { + if (parser != null) parser.close(); + } + } + + DeviceAdminInfo(Parcel source) { + mReceiver = ResolveInfo.CREATOR.createFromParcel(source); + } + + /** + * Return the .apk package that implements this device admin. + */ + public String getPackageName() { + return mReceiver.activityInfo.packageName; + } + + /** + * Return the class name of the receiver component that implements + * this device admin. + */ + public String getReceiverName() { + return mReceiver.activityInfo.name; + } + + /** + * Return the raw information about the receiver implementing this + * device admin. Do not modify the returned object. + */ + public ActivityInfo getActivityInfo() { + return mReceiver.activityInfo; + } + + /** + * Return the component of the receiver that implements this device admin. + */ + public ComponentName getComponent() { + return new ComponentName(mReceiver.activityInfo.packageName, + mReceiver.activityInfo.name); + } + + /** + * Load the user-displayed label for this device admin. + * + * @param pm Supply a PackageManager used to load the device admin's + * resources. + */ + public CharSequence loadLabel(PackageManager pm) { + return mReceiver.loadLabel(pm); + } + + /** + * Load the user-displayed icon for this device admin. + * + * @param pm Supply a PackageManager used to load the device admin's + * resources. + */ + public Drawable loadIcon(PackageManager pm) { + return mReceiver.loadIcon(pm); + } + + public void dump(Printer pw, String prefix) { + pw.println(prefix + "Receiver:"); + mReceiver.dump(pw, prefix + " "); + } + + @Override + public String toString() { + return "DeviceAdminInfo{" + mReceiver.activityInfo.name + "}"; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + mReceiver.writeToParcel(dest, flags); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<DeviceAdminInfo> CREATOR = + new Parcelable.Creator<DeviceAdminInfo>() { + public DeviceAdminInfo createFromParcel(Parcel source) { + return new DeviceAdminInfo(source); + } + + public DeviceAdminInfo[] newArray(int size) { + return new DeviceAdminInfo[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/DevicePolicyManager.java b/core/java/android/app/DevicePolicyManager.java new file mode 100644 index 0000000..4fdfe0a --- /dev/null +++ b/core/java/android/app/DevicePolicyManager.java @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2010 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.app; + +import org.xmlpull.v1.XmlPullParserException; + +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Handler; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.io.IOException; + +/** + * Public interface for managing policies enforced on a device. Most clients + * of this class must have published a {@link DeviceAdmin} that the user + * has currently enabled. + */ +public class DevicePolicyManager { + private static String TAG = "DevicePolicyManager"; + private static boolean DEBUG = false; + private static boolean localLOGV = DEBUG || android.util.Config.LOGV; + + private final Context mContext; + private final Handler mHandler; + private final IDevicePolicyManager mService; + + /*package*/ DevicePolicyManager(Context context, Handler handler) { + mContext = context; + mHandler = handler; + mService = IDevicePolicyManager.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); + } + + /** + * Activity action: ask the user to add a new device administrator to the system. + * The desired policy is the ComponentName of the policy in the + * {@link #EXTRA_DEVICE_ADMIN} extra field. This will invoke a UI to + * bring the user through adding the device administrator to the system (or + * allowing them to reject it). + * + * <p>Note: the current platform can only have one device administrator + * active at a time. If you make this request while there is already + * an active administrator, this new request will be canceled automatically. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_ADD_DEVICE_ADMIN + = "android.app.action.ADD_DEVICE_ADMIN"; + + /** + * The ComponentName of the administrator component. + * + * @see #ACTION_ADD_DEVICE_ADMIN + */ + public static final String EXTRA_DEVICE_ADMIN = "android.app.extra.DEVICE_ADMIN"; + + /** + * Activity action: have the user enter a new password. This activity + * should be launched after using {@link #setPasswordMode(ComponentName, int)} + * or {@link #setMinimumPasswordLength(ComponentName, int)} to have the + * user enter a new password that meets the current requirements. If the + * current password is sufficient, the activity will exit immediately without + * being displayed to the user. Upon receiving a result from this activity, + * you can check the new password characteristics to see if they are + * sufficient. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_SET_NEW_PASSWORD + = "android.app.action.SET_NEW_PASSWORD"; + + /** + * Return true if the given administrator component is currently + * active (enabled) in the system. + */ + public boolean isAdminActive(ComponentName who) { + if (mService != null) { + try { + return who.equals(mService.getActiveAdmin()); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return false; + } + + /** + * Remove a current administration component. This can only be called + * by the application that owns the administration component; if you + * try to remove someone else's component, a security exception will be + * thrown. + */ + public void removeActiveAdmin(ComponentName who) { + if (mService != null) { + try { + mService.removeActiveAdmin(who); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Constant for {@link #setPasswordMode}: the policy has no requirements + * for the password. + */ + public static final int PASSWORD_MODE_UNSPECIFIED = 0; + + /** + * Constant for {@link #setPasswordMode}: the user must have at least a + * numeric password. + */ + public static final int PASSWORD_MODE_NUMERIC = 1000; + + /** + * Constant for {@link #setPasswordMode}: the user must have at least an + * alphanumeric password. + */ + public static final int PASSWORD_MODE_ALPHANUMERIC = 2000; + + /** + * Called by an application that is administering the device to set the + * password restrictions it is imposing. After setting this, the user + * will not be able to enter a new password that is not at least as + * restrictive as what has been set. Note that the current password + * will remain until the user has set a new one, so the change does not + * take place immediately. To prompt the user for a new password, use + * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. + * + * @param admin Which {@link DeviceAdmin} this request is associated with. + * @param mode The new desired mode. One of + * {@link #PASSWORD_MODE_UNSPECIFIED}, {@link #PASSWORD_MODE_NUMERIC}, + * or {@link #PASSWORD_MODE_ALPHANUMERIC}. + */ + public void setPasswordMode(ComponentName admin, int mode) { + if (mService != null) { + try { + mService.setPasswordMode(admin, mode); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Retrieve the current password mode that is in effect due to all + * device admins. + */ + public int getPasswordMode() { + if (mService != null) { + try { + return mService.getPasswordMode(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return PASSWORD_MODE_UNSPECIFIED; + } + + /** + * Retrieve the password mode associated with the last password the + * user selected. + */ + public int getActivePasswordMode() { + if (mService != null) { + try { + return mService.getActivePasswordMode(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return PASSWORD_MODE_UNSPECIFIED; + } + + /** + * Called by an application that is administering the device to set the + * minimum allowed password length. After setting this, the user + * will not be able to enter a new password that is not at least as + * restrictive as what has been set. Note that the current password + * will remain until the user has set a new one, so the change does not + * take place immediately. To prompt the user for a new password, use + * {@link #ACTION_SET_NEW_PASSWORD} after setting this value. This + * constraint is only imposed if the administrator has also requested either + * {@link #PASSWORD_MODE_NUMERIC} or {@link #PASSWORD_MODE_ALPHANUMERIC} + * with {@link #setPasswordMode}. + * + * @param admin Which {@link DeviceAdmin} this request is associated with. + * @param length The new desired minimum password length. A value of 0 + * means there is no restriction. + */ + public void setMinimumPasswordLength(ComponentName admin, int length) { + if (mService != null) { + try { + mService.setMinimumPasswordLength(admin, length); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Retrieve the current minimum password length that is in effect due to all + * device admins. + */ + public int getMinimumPasswordLength() { + if (mService != null) { + try { + return mService.getMinimumPasswordLength(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return 0; + } + + /** + * Retrieve the password length associated with the last password the + * user selected. + */ + public int getActiveMinimumPasswordLength() { + if (mService != null) { + try { + return mService.getActiveMinimumPasswordLength(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return 0; + } + + /** + * Retrieve the number of times the user has failed at entering a + * password since that last successful password entry. + */ + public int getCurrentFailedPasswordAttempts() { + if (mService != null) { + try { + return mService.getCurrentFailedPasswordAttempts(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return -1; + } + + /** + * Called by an application that is administering the device to set the + * maximum time for user activity until the device will lock. This limits + * the length that the user can set. It takes effect immediately. + * + * @param admin Which {@link DeviceAdmin} this request is associated with. + * @param timeMs The new desired maximum time to lock in milliseconds. + * A value of 0 means there is no restriction. + */ + public void setMaximumTimeToLock(ComponentName admin, long timeMs) { + if (mService != null) { + try { + mService.setMaximumTimeToLock(admin, timeMs); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * Retrieve the current maximum time to lock that is in effect due to all + * device admins. Returns 0 if no maximum is set. + */ + public long getMaximumTimeToLock() { + if (mService != null) { + try { + return mService.getMaximumTimeToLock(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return 0; + } + + /** + * Constant for {@link #wipeData}: perform a low-level format of data + * storage. + */ + public static final int WIPE_LOW_LEVEL_FORMAT = 0x0001; + + /** + * Constant for {@link #wipeData}: also wipe any external storage. + */ + public static final int WIPE_EXTERNAL_STORAGE = 0x0002; + + /** + * Ask the user date be wiped. This will cause the device to reboot, + * erasing all user data while next booting up. + * + * @param flags Bit mask of additional options: currently + * {@link #WIPE_LOW_LEVEL_FORMAT} and {@link #WIPE_EXTERNAL_STORAGE}. + */ + public void wipeData(int flags) { + if (mService != null) { + try { + mService.wipeData(flags); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * @hide + */ + public void setActiveAdmin(ComponentName policyReceiver) { + if (mService != null) { + try { + mService.setActiveAdmin(policyReceiver); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * @hide + */ + public ComponentName getActiveAdmin() { + if (mService != null) { + try { + return mService.getActiveAdmin(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + return null; + } + + /** + * @hide + */ + public DeviceAdminInfo getActiveAdminInfo() { + ComponentName cn = getActiveAdmin(); + if (cn == null) { + return null; + } + + ActivityInfo ai; + try { + ai = mContext.getPackageManager().getReceiverInfo(cn, + PackageManager.GET_META_DATA); + } catch (PackageManager.NameNotFoundException e) { + Log.w(TAG, "Unable to retrieve device policy " + cn, e); + return null; + } + + ResolveInfo ri = new ResolveInfo(); + ri.activityInfo = ai; + + try { + return new DeviceAdminInfo(mContext, ri); + } catch (XmlPullParserException e) { + Log.w(TAG, "Unable to parse device policy " + cn, e); + return null; + } catch (IOException e) { + Log.w(TAG, "Unable to parse device policy " + cn, e); + return null; + } + } + + /** + * @hide + */ + public void setActivePasswordState(int mode, int length) { + if (mService != null) { + try { + mService.setActivePasswordState(mode, length); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * @hide + */ + public void reportFailedPasswordAttempt() { + if (mService != null) { + try { + mService.reportFailedPasswordAttempt(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } + + /** + * @hide + */ + public void reportSuccessfulPasswordAttempt() { + if (mService != null) { + try { + mService.reportSuccessfulPasswordAttempt(); + } catch (RemoteException e) { + Log.w(TAG, "Failed talking with device policy service", e); + } + } + } +} diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index fa5d4a8..ed38240 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -995,8 +995,7 @@ public class Dialog implements DialogInterface, Window.Callback, /** * Sets a listener to be invoked when the dialog is shown. - * - * @hide Pending API council approval + * @param listener The {@link DialogInterface.OnShowListener} to use. */ public void setOnShowListener(OnShowListener listener) { if (listener != null) { diff --git a/core/java/android/app/IDevicePolicyManager.aidl b/core/java/android/app/IDevicePolicyManager.aidl new file mode 100644 index 0000000..f62647f --- /dev/null +++ b/core/java/android/app/IDevicePolicyManager.aidl @@ -0,0 +1,49 @@ +/* +** +** Copyright 2010, 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.app; + +import android.content.ComponentName; + +/** + * Internal IPC interface to the device policy service. + * {@hide} + */ +interface IDevicePolicyManager { + void setPasswordMode(in ComponentName who, int mode); + int getPasswordMode(); + int getActivePasswordMode(); + + void setMinimumPasswordLength(in ComponentName who, int length); + int getMinimumPasswordLength(); + int getActiveMinimumPasswordLength(); + + int getCurrentFailedPasswordAttempts(); + + void setMaximumTimeToLock(in ComponentName who, long timeMs); + long getMaximumTimeToLock(); + + void wipeData(int flags); + + void setActiveAdmin(in ComponentName policyReceiver); + ComponentName getActiveAdmin(); + void removeActiveAdmin(in ComponentName policyReceiver); + + void setActivePasswordState(int mode, int length); + void reportFailedPasswordAttempt(); + void reportSuccessfulPasswordAttempt(); +} diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 981145b..d25d670 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -1838,8 +1838,10 @@ public class SearchManager * * TODO: Doing this every time we start global search is inefficient. Will fix that once * we have settled on the right mechanism for finding the global search activity. + * + * @hide */ - private ComponentName getGlobalSearchActivity() { + public ComponentName getGlobalSearchActivity() { Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH); PackageManager pm = mContext.getPackageManager(); List<ResolveInfo> activities = diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java index 1034fab..1612ac9 100644 --- a/core/java/android/app/WallpaperInfo.java +++ b/core/java/android/app/WallpaperInfo.java @@ -1,3 +1,19 @@ +/* + * 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.app; import org.xmlpull.v1.XmlPullParser; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 0fafe5d..0b83f03 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -1132,6 +1132,7 @@ public abstract class Context { * you're running long tasks. */ public static final String POWER_SERVICE = "power"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.view.WindowManager} for accessing the system's window @@ -1141,6 +1142,7 @@ public abstract class Context { * @see android.view.WindowManager */ public static final String WINDOW_SERVICE = "window"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.view.LayoutInflater} for inflating layout resources in this @@ -1150,6 +1152,7 @@ public abstract class Context { * @see android.view.LayoutInflater */ public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.accounts.AccountManager} for receiving intents at a @@ -1159,6 +1162,7 @@ public abstract class Context { * @see android.accounts.AccountManager */ public static final String ACCOUNT_SERVICE = "account"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.app.ActivityManager} for interacting with the global @@ -1168,6 +1172,7 @@ public abstract class Context { * @see android.app.ActivityManager */ public static final String ACTIVITY_SERVICE = "activity"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.app.AlarmManager} for receiving intents at a @@ -1177,6 +1182,7 @@ public abstract class Context { * @see android.app.AlarmManager */ public static final String ALARM_SERVICE = "alarm"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.app.NotificationManager} for informing the user of @@ -1186,6 +1192,7 @@ public abstract class Context { * @see android.app.NotificationManager */ public static final String NOTIFICATION_SERVICE = "notification"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.view.accessibility.AccessibilityManager} for giving the user @@ -1195,6 +1202,7 @@ public abstract class Context { * @see android.view.accessibility.AccessibilityManager */ public static final String ACCESSIBILITY_SERVICE = "accessibility"; + /** * Use with {@link #getSystemService} to retrieve a * {@link android.app.NotificationManager} for controlling keyguard. @@ -1203,6 +1211,7 @@ public abstract class Context { * @see android.app.KeyguardManager */ public static final String KEYGUARD_SERVICE = "keyguard"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.location.LocationManager} for controlling location @@ -1212,6 +1221,7 @@ public abstract class Context { * @see android.location.LocationManager */ public static final String LOCATION_SERVICE = "location"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.app.SearchManager} for handling searches. @@ -1220,6 +1230,7 @@ public abstract class Context { * @see android.app.SearchManager */ public static final String SEARCH_SERVICE = "search"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.hardware.SensorManager} for accessing sensors. @@ -1228,6 +1239,7 @@ public abstract class Context { * @see android.hardware.SensorManager */ public static final String SENSOR_SERVICE = "sensor"; + /** * Use with {@link #getSystemService} to retrieve a * com.android.server.WallpaperService for accessing wallpapers. @@ -1235,6 +1247,7 @@ public abstract class Context { * @see #getSystemService */ public static final String WALLPAPER_SERVICE = "wallpaper"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.os.Vibrator} for interacting with the vibration hardware. @@ -1243,6 +1256,7 @@ public abstract class Context { * @see android.os.Vibrator */ public static final String VIBRATOR_SERVICE = "vibrator"; + /** * Use with {@link #getSystemService} to retrieve a {@link * android.app.StatusBarManager} for interacting with the status bar. @@ -1340,6 +1354,15 @@ public abstract class Context { public static final String DROPBOX_SERVICE = "dropbox"; /** + * Use with {@link #getSystemService} to retrieve a + * {@link android.app.DevicePolicyManager} for working with global + * device policy management. + * + * @see #getSystemService + */ + public static final String DEVICE_POLICY_SERVICE = "device_policy"; + + /** * Determine whether the given permission is allowed for a particular * process and user ID running in the system. * diff --git a/core/java/android/content/DialogInterface.java b/core/java/android/content/DialogInterface.java index 9f1036e..947eac6 100644 --- a/core/java/android/content/DialogInterface.java +++ b/core/java/android/content/DialogInterface.java @@ -94,7 +94,6 @@ public interface DialogInterface { /** * Interface used to allow the creator of a dialog to run some code when the * dialog is shown. - * @hide Pending API council approval */ interface OnShowListener { /** diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index b27cd6c..3e8a55f 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -242,9 +242,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS}, - * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES}. - * {@link #FLAG_FWD_LOCKED}, - * {@link #FLAG_ON_SDCARD} + * {@link #FLAG_SUPPORTS_SCREEN_DENSITIES} */ public int flags = 0; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index f310586..fb44a62 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -243,9 +243,12 @@ public class SQLiteDatabase extends SQLiteClosable { * (@link setMaxCacheSize(int)}). its default is 0 - i.e., no caching by default because * most of the apps don't use "?" syntax in their sql, caching is not useful for them. */ - private Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap(); - private int mMaxSqlCacheSize = 0; // no caching by default - private static final int MAX_SQL_CACHE_SIZE = 1000; + /* package */ Map<String, SQLiteCompiledSql> mCompiledQueries = Maps.newHashMap(); + /** + * @hide + */ + public static final int MAX_SQL_CACHE_SIZE = 250; + private int mMaxSqlCacheSize = MAX_SQL_CACHE_SIZE; // max cache size per Database instance /** maintain stats about number of cache hits and misses */ private int mNumCacheHits; @@ -828,6 +831,15 @@ public class SQLiteDatabase extends SQLiteClosable { } private void closeClosable() { + /* deallocate all compiled sql statement objects from mCompiledQueries cache. + * this should be done before de-referencing all {@link SQLiteClosable} objects + * from this database object because calling + * {@link SQLiteClosable#onAllReferencesReleasedFromContainer()} could cause the database + * to be closed. sqlite doesn't let a database close if there are + * any unfinalized statements - such as the compiled-sql objects in mCompiledQueries. + */ + deallocCachedSqlStatements(); + Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator(); while (iter.hasNext()) { Map.Entry<SQLiteClosable, Object> entry = iter.next(); @@ -836,13 +848,6 @@ public class SQLiteDatabase extends SQLiteClosable { program.onAllReferencesReleasedFromContainer(); } } - - // finalize all compiled sql statement objects in compiledQueries cache - synchronized (mCompiledQueries) { - for (SQLiteCompiledSql compiledStatement : mCompiledQueries.values()) { - compiledStatement.releaseSqlStatement(); - } - } } /** @@ -1781,30 +1786,61 @@ public class SQLiteDatabase extends SQLiteClosable { return mPath; } - /** - * set the max size of the compiled sql cache for this database after purging the cache. - * (size of the cache = number of compiled-sql-statements stored in the cache) - * - * synchronized because we don't want t threads to change cache size at the same time. - * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) - */ - public void setMaxSqlCacheSize(int cacheSize) { - synchronized(mCompiledQueries) { - resetCompiledSqlCache(); - mMaxSqlCacheSize = (cacheSize > MAX_SQL_CACHE_SIZE) ? MAX_SQL_CACHE_SIZE - : (cacheSize < 0) ? 0 : cacheSize; + + + /* package */ void logTimeStat(String sql, long beginNanos) { + // Sample fast queries in proportion to the time taken. + // Quantize the % first, so the logged sampling probability + // exactly equals the actual sampling rate for this query. + + int samplePercent; + long nanos = Debug.threadCpuTimeNanos() - beginNanos; + if (nanos >= QUERY_LOG_TIME_IN_NANOS) { + samplePercent = 100; + } else { + samplePercent = (int) (100 * nanos / QUERY_LOG_TIME_IN_NANOS) + 1; + if (mRandom.nextInt(100) >= samplePercent) return; } + + if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH); + + // ActivityThread.currentPackageName() only returns non-null if the + // current thread is an application main thread. This parameter tells + // us whether an event loop is blocked, and if so, which app it is. + // + // Sadly, there's no fast way to determine app name if this is *not* a + // main thread, or when we are invoked via Binder (e.g. ContentProvider). + // Hopefully the full path to the database will be informative enough. + + String blockingPackage = ActivityThread.currentPackageName(); + if (blockingPackage == null) blockingPackage = ""; + + int millis = (int) (nanos / 1000000); + EventLog.writeEvent(EVENT_DB_OPERATION, mPath, sql, millis, blockingPackage, samplePercent); } /** - * remove everything from the compiled sql cache + * Sets the locale for this database. Does nothing if this database has + * the NO_LOCALIZED_COLLATORS flag set or was opened read only. + * @throws SQLException if the locale could not be set. The most common reason + * for this is that there is no collator available for the locale you requested. + * In this case the database remains unchanged. */ - public void resetCompiledSqlCache() { - synchronized(mCompiledQueries) { - mCompiledQueries.clear(); + public void setLocale(Locale locale) { + lock(); + try { + native_setLocale(locale.toString(), mFlags); + } finally { + unlock(); } } + /* + * ============================================================================ + * + * The following methods deal with compiled-sql cache + * ============================================================================ + */ /** * adds the given sql and its compiled-statement-id-returned-by-sqlite to the * cache of compiledQueries attached to 'this'. @@ -1812,16 +1848,14 @@ public class SQLiteDatabase extends SQLiteClosable { * if there is already a {@link SQLiteCompiledSql} in compiledQueries for the given sql, * the new {@link SQLiteCompiledSql} object is NOT inserted into the cache (i.e.,the current * mapping is NOT replaced with the new mapping). - * - * @return true if the given obj is added to cache. false otherwise. */ - /* package */ boolean addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { + /* package */ void addToCompiledQueries(String sql, SQLiteCompiledSql compiledStatement) { if (mMaxSqlCacheSize == 0) { // for this database, there is no cache of compiled sql. if (SQLiteDebug.DEBUG_SQL_CACHE) { Log.v(TAG, "|NOT adding_sql_to_cache|" + getPath() + "|" + sql); } - return false; + return; } SQLiteCompiledSql compiledSql = null; @@ -1829,30 +1863,42 @@ public class SQLiteDatabase extends SQLiteClosable { // don't insert the new mapping if a mapping already exists compiledSql = mCompiledQueries.get(sql); if (compiledSql != null) { - return false; + return; } // add this <sql, compiledStatement> to the cache if (mCompiledQueries.size() == mMaxSqlCacheSize) { /* reached max cachesize. before adding new entry, remove an entry from the * cache. we don't want to wipe out the entire cache because of this: * GCing {@link SQLiteCompiledSql} requires call to sqlite3_finalize - * JNI method. If entire cache is wiped out, it could be cause a big GC activity + * JNI method. If entire cache is wiped out, it could cause a big GC activity * just because a (rogue) process is using the cache incorrectly. */ + Log.w(TAG, "Too many sql statements in database cache. Make sure your sql " + + "statements are using prepared-sql-statement syntax with '?' for " + + "bindargs, instead of using actual values"); Set<String> keySet = mCompiledQueries.keySet(); for (String s : keySet) { mCompiledQueries.remove(s); break; } } - compiledSql = new SQLiteCompiledSql(this, sql); - mCompiledQueries.put(sql, compiledSql); + mCompiledQueries.put(sql, compiledStatement); } if (SQLiteDebug.DEBUG_SQL_CACHE) { Log.v(TAG, "|adding_sql_to_cache|" + getPath() + "|" + mCompiledQueries.size() + "|" + sql); } - return true; + return; + } + + + private void deallocCachedSqlStatements() { + synchronized (mCompiledQueries) { + for (SQLiteCompiledSql compiledSql : mCompiledQueries.values()) { + compiledSql.releaseSqlStatement(); + } + mCompiledQueries.clear(); + } } /** @@ -1887,51 +1933,67 @@ public class SQLiteDatabase extends SQLiteClosable { return compiledStatement; } - /* package */ void logTimeStat(String sql, long beginNanos) { - // Sample fast queries in proportion to the time taken. - // Quantize the % first, so the logged sampling probability - // exactly equals the actual sampling rate for this query. - - int samplePercent; - long nanos = Debug.threadCpuTimeNanos() - beginNanos; - if (nanos >= QUERY_LOG_TIME_IN_NANOS) { - samplePercent = 100; - } else { - samplePercent = (int) (100 * nanos / QUERY_LOG_TIME_IN_NANOS) + 1; - if (mRandom.nextInt(100) >= samplePercent) return; + /** + * returns true if the given sql is cached in compiled-sql cache. + * @hide + */ + public boolean isInCompiledSqlCache(String sql) { + synchronized(mCompiledQueries) { + return mCompiledQueries.containsKey(sql); } + } - if (sql.length() > QUERY_LOG_SQL_LENGTH) sql = sql.substring(0, QUERY_LOG_SQL_LENGTH); - - // ActivityThread.currentPackageName() only returns non-null if the - // current thread is an application main thread. This parameter tells - // us whether an event loop is blocked, and if so, which app it is. - // - // Sadly, there's no fast way to determine app name if this is *not* a - // main thread, or when we are invoked via Binder (e.g. ContentProvider). - // Hopefully the full path to the database will be informative enough. + /** + * purges the given sql from the compiled-sql cache. + * @hide + */ + public void purgeFromCompiledSqlCache(String sql) { + synchronized(mCompiledQueries) { + mCompiledQueries.remove(sql); + } + } - String blockingPackage = ActivityThread.currentPackageName(); - if (blockingPackage == null) blockingPackage = ""; + /** + * remove everything from the compiled sql cache + * @hide + */ + public void resetCompiledSqlCache() { + synchronized(mCompiledQueries) { + mCompiledQueries.clear(); + } + } - int millis = (int) (nanos / 1000000); - EventLog.writeEvent(EVENT_DB_OPERATION, mPath, sql, millis, blockingPackage, samplePercent); + /** + * return the current maxCacheSqlCacheSize + * @hide + */ + public synchronized int getMaxSqlCacheSize() { + return mMaxSqlCacheSize; } /** - * Sets the locale for this database. Does nothing if this database has - * the NO_LOCALIZED_COLLATORS flag set or was opened read only. - * @throws SQLException if the locale could not be set. The most common reason - * for this is that there is no collator available for the locale you requested. - * In this case the database remains unchanged. + * set the max size of the compiled sql cache for this database after purging the cache. + * (size of the cache = number of compiled-sql-statements stored in the cache). + * + * max cache size can ONLY be increased from its current size (default = 0). + * if this method is called with smaller size than the current value of mMaxSqlCacheSize, + * then IllegalStateException is thrown + * + * synchronized because we don't want t threads to change cache size at the same time. + * @param cacheSize the size of the cache. can be (0 to MAX_SQL_CACHE_SIZE) + * @throws IllegalStateException if input cacheSize > MAX_SQL_CACHE_SIZE or < 0 or + * < the value set with previous setMaxSqlCacheSize() call. + * + * @hide */ - public void setLocale(Locale locale) { - lock(); - try { - native_setLocale(locale.toString(), mFlags); - } finally { - unlock(); + public synchronized void setMaxSqlCacheSize(int cacheSize) { + if (cacheSize > MAX_SQL_CACHE_SIZE || cacheSize < 0) { + throw new IllegalStateException("expected value between 0 and " + MAX_SQL_CACHE_SIZE); + } else if (cacheSize < mMaxSqlCacheSize) { + throw new IllegalStateException("cannot set cacheSize to a value less than the value " + + "set with previous setMaxSqlCacheSize() call."); } + mMaxSqlCacheSize = cacheSize; } /** diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index edc15cb..00b0a86 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -37,15 +37,13 @@ public abstract class SQLiteProgram extends SQLiteClosable { protected int nHandle = 0; /** - * the compiledSql object for the given sql statement. + * the SQLiteCompiledSql object for the given sql statement. */ - private SQLiteCompiledSql compiledSql; - private boolean myCompiledSqlIsInCache; + private SQLiteCompiledSql mCompiledSql; /** - * compiledSql statement id is populated with the corresponding object from the above - * member compiledSql. - * this member is used by the native_bind_* methods + * SQLiteCompiledSql statement id is populated with the corresponding object from the above + * member. This member is used by the native_bind_* methods */ protected int nStatement = 0; @@ -60,47 +58,50 @@ public abstract class SQLiteProgram extends SQLiteClosable { db.addSQLiteClosable(this); this.nHandle = db.mNativeHandle; - compiledSql = db.getCompiledStatementForSql(sql); - if (compiledSql == null) { + mCompiledSql = db.getCompiledStatementForSql(sql); + if (mCompiledSql == null) { // create a new compiled-sql obj - compiledSql = new SQLiteCompiledSql(db, sql); + mCompiledSql = new SQLiteCompiledSql(db, sql); // add it to the cache of compiled-sqls - myCompiledSqlIsInCache = db.addToCompiledQueries(sql, compiledSql); - } else { - myCompiledSqlIsInCache = true; + db.addToCompiledQueries(sql, mCompiledSql); } - nStatement = compiledSql.nStatement; + nStatement = mCompiledSql.nStatement; } @Override protected void onAllReferencesReleased() { - // release the compiled sql statement used by me if it is NOT in cache - if (!myCompiledSqlIsInCache && compiledSql != null) { - compiledSql.releaseSqlStatement(); - compiledSql = null; // so that GC doesn't call finalize() on it - } + releaseCompiledSqlIfInCache(); mDatabase.releaseReference(); mDatabase.removeSQLiteClosable(this); } @Override protected void onAllReferencesReleasedFromContainer() { - // release the compiled sql statement used by me if it is NOT in cache - if (!myCompiledSqlIsInCache && compiledSql != null) { - compiledSql.releaseSqlStatement(); - compiledSql = null; // so that GC doesn't call finalize() on it - } + releaseCompiledSqlIfInCache(); mDatabase.releaseReference(); } + private void releaseCompiledSqlIfInCache() { + if (mCompiledSql == null) { + return; + } + synchronized(mDatabase.mCompiledQueries) { + if (!mDatabase.mCompiledQueries.containsValue(mCompiledSql)) { + mCompiledSql.releaseSqlStatement(); + mCompiledSql = null; // so that GC doesn't call finalize() on it + nStatement = 0; + } + } + } + /** * Returns a unique identifier for this program. * * @return a unique identifier for this program */ public final int getUniqueId() { - return compiledSql.nStatement; + return nStatement; } /* package */ String getSqlString() { diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java index 62330e1..d71344c 100755 --- a/core/java/android/gesture/Gesture.java +++ b/core/java/android/gesture/Gesture.java @@ -34,7 +34,9 @@ import java.util.ArrayList; import java.util.concurrent.atomic.AtomicInteger; /** - * A gesture can have a single or multiple strokes + * A gesture is a hand-drawn shape on a touch screen. It can have one or multiple strokes. + * Each stroke is a sequence of timed points. A user-defined gesture can be recognized by + * a GestureLibrary and a built-in alphabet gesture can be recognized by a LetterRecognizer. */ public class Gesture implements Parcelable { @@ -58,6 +60,19 @@ public class Gesture implements Parcelable { mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet(); } + @Override + public Object clone() { + Gesture gesture = new Gesture(); + gesture.mBoundingBox.set(mBoundingBox.left, mBoundingBox.top, + mBoundingBox.right, mBoundingBox.bottom); + final int count = mStrokes.size(); + for (int i = 0; i < count; i++) { + GestureStroke stroke = mStrokes.get(i); + gesture.mStrokes.add((GestureStroke)stroke.clone()); + } + return gesture; + } + /** * @return all the strokes of the gesture */ @@ -73,7 +88,7 @@ public class Gesture implements Parcelable { } /** - * Add a stroke to the gesture + * Adds a stroke to the gesture. * * @param stroke */ @@ -83,8 +98,8 @@ public class Gesture implements Parcelable { } /** - * Get the total length of the gesture. When there are multiple strokes in - * the gesture, this returns the sum of the lengths of all the strokes + * Calculates the total length of the gesture. When there are multiple strokes in + * the gesture, this returns the sum of the lengths of all the strokes. * * @return the length of the gesture */ @@ -142,7 +157,7 @@ public class Gesture implements Parcelable { } /** - * Set the id of the gesture + * Sets the id of the gesture. * * @param id */ @@ -158,7 +173,7 @@ public class Gesture implements Parcelable { } /** - * Create a bitmap of the gesture with a transparent background + * Creates a bitmap of the gesture with a transparent background. * * @param width width of the target bitmap * @param height height of the target bitmap @@ -194,7 +209,7 @@ public class Gesture implements Parcelable { } /** - * Create a bitmap of the gesture with a transparent background + * Creates a bitmap of the gesture with a transparent background. * * @param width * @param height diff --git a/core/java/android/gesture/GesturePoint.java b/core/java/android/gesture/GesturePoint.java index 3698011..4cb7707 100644 --- a/core/java/android/gesture/GesturePoint.java +++ b/core/java/android/gesture/GesturePoint.java @@ -20,7 +20,7 @@ import java.io.DataInputStream; import java.io.IOException; /** - * A timed point of a gesture stroke + * A timed point of a gesture stroke. Multiple points form a stroke. */ public class GesturePoint { @@ -43,4 +43,9 @@ public class GesturePoint { final long timeStamp = in.readLong(); return new GesturePoint(x, y, timeStamp); } + + @Override + public Object clone() { + return new GesturePoint(x, y, timestamp); + } } diff --git a/core/java/android/gesture/GestureStroke.java b/core/java/android/gesture/GestureStroke.java index 598eb85..68dc5a6 100644 --- a/core/java/android/gesture/GestureStroke.java +++ b/core/java/android/gesture/GestureStroke.java @@ -27,7 +27,8 @@ import java.io.DataInputStream; import java.util.ArrayList; /** - * A gesture stroke started on a touch down and ended on a touch up. + * A gesture stroke started on a touch down and ended on a touch up. A stroke + * consists of a sequence of timed points. One or multiple strokes form a gesture. */ public class GestureStroke { static final float TOUCH_TOLERANCE = 8; @@ -41,7 +42,7 @@ public class GestureStroke { private Path mCachedPath; /** - * Construct a gesture stroke from a list of gesture points + * A constructor that constructs a gesture stroke from a list of gesture points. * * @param points */ @@ -82,7 +83,22 @@ public class GestureStroke { } /** - * Draw the gesture with a given canvas and paint + * A faster constructor specially for cloning a stroke. + */ + private GestureStroke(RectF bbx, float len, float[] pts, long[] times) { + boundingBox = new RectF(bbx.left, bbx.top, bbx.right, bbx.bottom); + length = len; + points = pts.clone(); + timestamps = times.clone(); + } + + @Override + public Object clone() { + return new GestureStroke(boundingBox, length, points, timestamps); + } + + /** + * Draws the stroke with a given canvas and paint. * * @param canvas */ @@ -134,7 +150,7 @@ public class GestureStroke { } /** - * Convert the stroke to a Path based on the number of points + * Converts the stroke to a Path of a given number of points. * * @param width the width of the bounding box of the target path * @param height the height of the bounding box of the target path @@ -213,14 +229,15 @@ public class GestureStroke { } /** - * Invalidate the cached path that is used to render the stroke + * Invalidates the cached path that is used to render the stroke. */ public void clearPath() { if (mCachedPath != null) mCachedPath.rewind(); } /** - * Compute an oriented bounding box of the stroke + * Computes an oriented bounding box of the stroke. + * * @return OrientedBoundingBox */ public OrientedBoundingBox computeOrientedBoundingBox() { diff --git a/core/java/android/gesture/GestureUtilities.java b/core/java/android/gesture/GestureUtilities.java index f1dcd89..dfe1d00 100755 --- a/core/java/android/gesture/GestureUtilities.java +++ b/core/java/android/gesture/GestureUtilities.java @@ -27,8 +27,10 @@ import java.io.IOException; import static android.gesture.GestureConstants.*; final class GestureUtilities { - private static final int TEMPORAL_SAMPLING_RATE = 16; - + + private static final float SCALING_THRESHOLD = 0.26f; + private static final float NONUNIFORM_SCALE = (float) Math.sqrt(2); + private GestureUtilities() { } @@ -46,64 +48,87 @@ final class GestureUtilities { } } } - + static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension) { + return spatialSampling(gesture, sampleMatrixDimension, false); + } + + static float[] spatialSampling(Gesture gesture, int sampleMatrixDimension, + boolean uniformScaling) { final float targetPatchSize = sampleMatrixDimension - 1; // edge inclusive float[] sample = new float[sampleMatrixDimension * sampleMatrixDimension]; Arrays.fill(sample, 0); - + RectF rect = gesture.getBoundingBox(); - float sx = targetPatchSize / rect.width(); - float sy = targetPatchSize / rect.height(); - float scale = sx < sy ? sx : sy; + final float gestureWidth = rect.width(); + final float gestureHeight = rect.height(); + float sx = targetPatchSize / gestureWidth; + float sy = targetPatchSize / gestureHeight; + + if (uniformScaling) { + float scale = sx < sy ? sx : sy; + sx = scale; + sy = scale; + } else { + float aspectRatio = gestureWidth / gestureHeight; + if (aspectRatio > 1) { + aspectRatio = 1 / aspectRatio; + } + if (aspectRatio < SCALING_THRESHOLD) { + float scale = sx < sy ? sx : sy; + sx = scale; + sy = scale; + } else { + if (sx > sy) { + float scale = sy * NONUNIFORM_SCALE; + if (scale < sx) { + sx = scale; + } + } else { + float scale = sx * NONUNIFORM_SCALE; + if (scale < sy) { + sy = scale; + } + } + } + } float preDx = -rect.centerX(); float preDy = -rect.centerY(); float postDx = targetPatchSize / 2; float postDy = targetPatchSize / 2; - final ArrayList<GestureStroke> strokes = gesture.getStrokes(); final int count = strokes.size(); - int size; float xpos; float ypos; - for (int index = 0; index < count; index++) { final GestureStroke stroke = strokes.get(index); float[] strokepoints = stroke.points; size = strokepoints.length; - final float[] pts = new float[size]; - for (int i = 0; i < size; i += 2) { - pts[i] = (strokepoints[i] + preDx) * scale + postDx; - pts[i + 1] = (strokepoints[i + 1] + preDy) * scale + postDy; + pts[i] = (strokepoints[i] + preDx) * sx + postDx; + pts[i + 1] = (strokepoints[i + 1] + preDy) * sy + postDy; } - float segmentEndX = -1; float segmentEndY = -1; - for (int i = 0; i < size; i += 2) { - float segmentStartX = pts[i] < 0 ? 0 : pts[i]; float segmentStartY = pts[i + 1] < 0 ? 0 : pts[i + 1]; - if (segmentStartX > targetPatchSize) { segmentStartX = targetPatchSize; } - if (segmentStartY > targetPatchSize) { segmentStartY = targetPatchSize; } - plot(segmentStartX, segmentStartY, sample, sampleMatrixDimension); - if (segmentEndX != -1) { // evaluate horizontally if (segmentEndX > segmentStartX) { xpos = (float) Math.ceil(segmentStartX); - float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); + float slope = (segmentEndY - segmentStartY) / + (segmentEndX - segmentStartX); while (xpos < segmentEndX) { ypos = slope * (xpos - segmentStartX) + segmentStartY; plot(xpos, ypos, sample, sampleMatrixDimension); @@ -111,18 +136,19 @@ final class GestureUtilities { } } else if (segmentEndX < segmentStartX){ xpos = (float) Math.ceil(segmentEndX); - float slope = (segmentEndY - segmentStartY) / (segmentEndX - segmentStartX); + float slope = (segmentEndY - segmentStartY) / + (segmentEndX - segmentStartX); while (xpos < segmentStartX) { ypos = slope * (xpos - segmentStartX) + segmentStartY; plot(xpos, ypos, sample, sampleMatrixDimension); xpos++; } } - // evaluating vertically if (segmentEndY > segmentStartY) { ypos = (float) Math.ceil(segmentStartY); - float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); + float invertSlope = (segmentEndX - segmentStartX) / + (segmentEndY - segmentStartY); while (ypos < segmentEndY) { xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; plot(xpos, ypos, sample, sampleMatrixDimension); @@ -130,7 +156,8 @@ final class GestureUtilities { } } else if (segmentEndY < segmentStartY) { ypos = (float) Math.ceil(segmentEndY); - float invertSlope = (segmentEndX - segmentStartX) / (segmentEndY - segmentStartY); + float invertSlope = (segmentEndX - segmentStartX) / + (segmentEndY - segmentStartY); while (ypos < segmentStartY) { xpos = invertSlope * (ypos - segmentStartY) + segmentStartX; plot(xpos, ypos, sample, sampleMatrixDimension); @@ -138,16 +165,13 @@ final class GestureUtilities { } } } - segmentEndX = segmentStartX; segmentEndY = segmentStartY; } } - - return sample; } - + private static void plot(float x, float y, float[] sample, int sampleSize) { x = x < 0 ? 0 : x; y = y < 0 ? 0 : y; @@ -163,40 +187,44 @@ final class GestureUtilities { sample[index] = 1; } } else { - double topLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yFloor - y, 2)); - double topRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yFloor - y, 2)); - double btmLeft = Math.sqrt(Math.pow(xFloor - x, 2) + Math.pow(yCeiling - y, 2)); - double btmRight = Math.sqrt(Math.pow(xCeiling - x, 2) + Math.pow(yCeiling - y, 2)); - double sum = topLeft + topRight + btmLeft + btmRight; + final double xFloorSq = Math.pow(xFloor - x, 2); + final double yFloorSq = Math.pow(yFloor - y, 2); + final double xCeilingSq = Math.pow(xCeiling - x, 2); + final double yCeilingSq = Math.pow(yCeiling - y, 2); + float topLeft = (float) Math.sqrt(xFloorSq + yFloorSq); + float topRight = (float) Math.sqrt(xCeilingSq + yFloorSq); + float btmLeft = (float) Math.sqrt(xFloorSq + yCeilingSq); + float btmRight = (float) Math.sqrt(xCeilingSq + yCeilingSq); + float sum = topLeft + topRight + btmLeft + btmRight; - double value = topLeft / sum; + float value = topLeft / sum; int index = yFloor * sampleSize + xFloor; if (value > sample[index]){ - sample[index] = (float) value; + sample[index] = value; } value = topRight / sum; index = yFloor * sampleSize + xCeiling; if (value > sample[index]){ - sample[index] = (float) value; + sample[index] = value; } value = btmLeft / sum; index = yCeiling * sampleSize + xFloor; if (value > sample[index]){ - sample[index] = (float) value; + sample[index] = value; } value = btmRight / sum; index = yCeiling * sampleSize + xCeiling; if (value > sample[index]){ - sample[index] = (float) value; + sample[index] = value; } } } - + /** - * Featurize a stroke into a vector of a given number of elements + * Featurizes a stroke into a vector of a given number of elements * * @param stroke * @param sampleSize @@ -286,8 +314,8 @@ final class GestureUtilities { * @param points * @return the covariance matrix */ - private static double[][] computeCoVariance(float[] points) { - double[][] array = new double[2][2]; + private static float[][] computeCoVariance(float[] points) { + float[][] array = new float[2][2]; array[0][0] = 0; array[0][1] = 0; array[1][0] = 0; @@ -321,17 +349,17 @@ final class GestureUtilities { return sum; } - static double computeStraightness(float[] points) { + static float computeStraightness(float[] points) { float totalLen = computeTotalLength(points); float dx = points[2] - points[0]; float dy = points[3] - points[1]; - return Math.sqrt(dx * dx + dy * dy) / totalLen; + return (float) Math.sqrt(dx * dx + dy * dy) / totalLen; } - static double computeStraightness(float[] points, float totalLen) { + static float computeStraightness(float[] points, float totalLen) { float dx = points[2] - points[0]; float dy = points[3] - points[1]; - return Math.sqrt(dx * dx + dy * dy) / totalLen; + return (float) Math.sqrt(dx * dx + dy * dy) / totalLen; } /** @@ -341,8 +369,8 @@ final class GestureUtilities { * @param vector2 * @return the distance */ - static double squaredEuclideanDistance(float[] vector1, float[] vector2) { - double squaredDistance = 0; + static float squaredEuclideanDistance(float[] vector1, float[] vector2) { + float squaredDistance = 0; int size = vector1.length; for (int i = 0; i < size; i++) { float difference = vector1[i] - vector2[i]; @@ -358,13 +386,13 @@ final class GestureUtilities { * @param vector2 * @return the distance between 0 and Math.PI */ - static double cosineDistance(float[] vector1, float[] vector2) { + static float cosineDistance(float[] vector1, float[] vector2) { float sum = 0; int len = vector1.length; for (int i = 0; i < len; i++) { sum += vector1[i] * vector2[i]; } - return Math.acos(sum); + return (float) Math.acos(sum); } /** @@ -375,46 +403,58 @@ final class GestureUtilities { * @param numOrientations the maximum number of orientation allowed * @return the distance between the two instances (between 0 and Math.PI) */ - static double minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) { + static float minimumCosineDistance(float[] vector1, float[] vector2, int numOrientations) { final int len = vector1.length; - double a = 0; - double b = 0; + float a = 0; + float b = 0; for (int i = 0; i < len; i += 2) { a += vector1[i] * vector2[i] + vector1[i + 1] * vector2[i + 1]; b += vector1[i] * vector2[i + 1] - vector1[i + 1] * vector2[i]; } if (a != 0) { - final double tan = b/a; + final float tan = b/a; final double angle = Math.atan(tan); if (numOrientations > 2 && Math.abs(angle) >= Math.PI / numOrientations) { - return Math.acos(a); + return (float) Math.acos(a); } else { final double cosine = Math.cos(angle); final double sine = cosine * tan; - return Math.acos(a * cosine + b * sine); + return (float) Math.acos(a * cosine + b * sine); } } else { - return Math.PI / 2; + return (float) Math.PI / 2; } } - static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> pts) { - GestureStroke stroke = new GestureStroke(pts); - float[] points = temporalSampling(stroke, TEMPORAL_SAMPLING_RATE); - return computeOrientedBoundingBox(points); + static OrientedBoundingBox computeOrientedBoundingBox(ArrayList<GesturePoint> originalPoints) { + final int count = originalPoints.size(); + float[] points = new float[count * 2]; + for (int i = 0; i < count; i++) { + GesturePoint point = originalPoints.get(i); + int index = i * 2; + points[index] = point.x; + points[index + 1] = point.y; + } + float[] meanVector = computeCentroid(points); + return computeOrientedBoundingBox(points, meanVector); } - static OrientedBoundingBox computeOrientedBoundingBox(float[] points) { + static OrientedBoundingBox computeOrientedBoundingBox(float[] originalPoints) { + int size = originalPoints.length; + float[] points = new float[size]; + for (int i = 0; i < size; i++) { + points[i] = originalPoints[i]; + } float[] meanVector = computeCentroid(points); return computeOrientedBoundingBox(points, meanVector); } - static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) { + private static OrientedBoundingBox computeOrientedBoundingBox(float[] points, float[] centroid) { translate(points, -centroid[0], -centroid[1]); - double[][] array = computeCoVariance(points); - double[] targetVector = computeOrientation(array); + float[][] array = computeCoVariance(points); + float[] targetVector = computeOrientation(array); float angle; if (targetVector[0] == 0 && targetVector[1] == 0) { @@ -448,25 +488,25 @@ final class GestureUtilities { return new OrientedBoundingBox((float) (angle * 180 / Math.PI), centroid[0], centroid[1], maxx - minx, maxy - miny); } - private static double[] computeOrientation(double[][] covarianceMatrix) { - double[] targetVector = new double[2]; + private static float[] computeOrientation(float[][] covarianceMatrix) { + float[] targetVector = new float[2]; if (covarianceMatrix[0][1] == 0 || covarianceMatrix[1][0] == 0) { targetVector[0] = 1; targetVector[1] = 0; } - double a = -covarianceMatrix[0][0] - covarianceMatrix[1][1]; - double b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1] + float a = -covarianceMatrix[0][0] - covarianceMatrix[1][1]; + float b = covarianceMatrix[0][0] * covarianceMatrix[1][1] - covarianceMatrix[0][1] * covarianceMatrix[1][0]; - double value = a / 2; - double rightside = Math.sqrt(Math.pow(value, 2) - b); - double lambda1 = -value + rightside; - double lambda2 = -value - rightside; + float value = a / 2; + float rightside = (float) Math.sqrt(Math.pow(value, 2) - b); + float lambda1 = -value + rightside; + float lambda2 = -value - rightside; if (lambda1 == lambda2) { targetVector[0] = 0; targetVector[1] = 0; } else { - double lambda = lambda1 > lambda2 ? lambda1 : lambda2; + float lambda = lambda1 > lambda2 ? lambda1 : lambda2; targetVector[0] = 1; targetVector[1] = (lambda - covarianceMatrix[0][0]) / covarianceMatrix[0][1]; } @@ -474,13 +514,13 @@ final class GestureUtilities { } - static float[] rotate(float[] points, double angle) { - double cos = Math.cos(angle); - double sin = Math.sin(angle); + static float[] rotate(float[] points, float angle) { + float cos = (float) Math.cos(angle); + float sin = (float) Math.sin(angle); int size = points.length; for (int i = 0; i < size; i += 2) { - float x = (float) (points[i] * cos - points[i + 1] * sin); - float y = (float) (points[i] * sin + points[i + 1] * cos); + float x = points[i] * cos - points[i + 1] * sin; + float y = points[i] * sin + points[i + 1] * cos; points[i] = x; points[i + 1] = y; } diff --git a/core/java/android/gesture/Instance.java b/core/java/android/gesture/Instance.java index 68a2985..bb0b340 100755 --- a/core/java/android/gesture/Instance.java +++ b/core/java/android/gesture/Instance.java @@ -84,7 +84,7 @@ class Instance { } private static float[] spatialSampler(Gesture gesture) { - return GestureUtilities.spatialSampling(gesture, PATCH_SAMPLE_SIZE); + return GestureUtilities.spatialSampling(gesture, PATCH_SAMPLE_SIZE, false); } private static float[] temporalSampler(int orientationType, Gesture gesture) { diff --git a/core/java/android/os/IMountService.aidl b/core/java/android/os/IMountService.aidl index e73569a..c0c2d03 100644 --- a/core/java/android/os/IMountService.aidl +++ b/core/java/android/os/IMountService.aidl @@ -94,6 +94,11 @@ interface IMountService String mountSecureContainer(String id, String key, int ownerUid); /* + * Unount a secure container. + */ + void unmountSecureContainer(String id); + + /* * Returns the filesystem path of a mounted secure container. */ String getSecureContainerPath(String id); diff --git a/core/java/android/pim/vcard/VCardParser_V21.java b/core/java/android/pim/vcard/VCardParser_V21.java index c2928cb..fe8cfb0 100644 --- a/core/java/android/pim/vcard/VCardParser_V21.java +++ b/core/java/android/pim/vcard/VCardParser_V21.java @@ -522,7 +522,7 @@ public class VCardParser_V21 extends VCardParser { protected void handleParams(String params) throws VCardException { String[] strArray = params.split("=", 2); if (strArray.length == 2) { - String paramName = strArray[0].trim(); + final String paramName = strArray[0].trim().toUpperCase(); String paramValue = strArray[1].trim(); if (paramName.equals("TYPE")) { handleType(paramValue); diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 7854423..d52632b 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -22,6 +22,7 @@ import com.android.internal.telephony.Connection; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.database.Cursor; import android.net.Uri; import android.text.TextUtils; @@ -111,25 +112,25 @@ public class CallLog { * <P>Type: TEXT</P> */ public static final String CACHED_NAME = "name"; - + /** * The cached number type (Home, Work, etc) associated with the * phone number, if it exists. * This value is not guaranteed to be current, if the contact information * associated with this number has changed. - * <P>Type: INTEGER</P> + * <P>Type: INTEGER</P> */ public static final String CACHED_NUMBER_TYPE = "numbertype"; - + /** * The cached number label, for a custom number type, associated with the * phone number, if it exists. * This value is not guaranteed to be current, if the contact information * associated with this number has changed. - * <P>Type: TEXT</P> + * <P>Type: TEXT</P> */ public static final String CACHED_NUMBER_LABEL = "numberlabel"; - + /** * Adds a call to the call log. * @@ -137,15 +138,15 @@ public class CallLog { * if the contact is unknown. * @param context the context used to get the ContentResolver * @param number the phone number to be added to the calls db - * @param presentation the number presenting rules set by the network for + * @param presentation the number presenting rules set by the network for * "allowed", "payphone", "restricted" or "unknown" * @param callType enumerated values for "incoming", "outgoing", or "missed" * @param start time stamp for the call in milliseconds * @param duration call duration in seconds - * + * * {@hide} */ - public static Uri addCall(CallerInfo ci, Context context, String number, + public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, long start, int duration) { final ContentResolver resolver = context.getContentResolver(); @@ -175,22 +176,47 @@ public class CallLog { values.put(CACHED_NUMBER_TYPE, ci.numberType); values.put(CACHED_NUMBER_LABEL, ci.numberLabel); } - + if ((ci != null) && (ci.person_id > 0)) { ContactsContract.Contacts.markAsContacted(resolver, ci.person_id); } - + Uri result = resolver.insert(CONTENT_URI, values); - + removeExpiredEntries(context); - + return result; } - + + /** + * Query the call log database for the last dialed number. + * @param context Used to get the content resolver. + * @return The last phone number dialed (outgoing) or an empty + * string if none exist yet. + */ + public static String getLastOutgoingCall(Context context) { + final ContentResolver resolver = context.getContentResolver(); + Cursor c = null; + try { + c = resolver.query( + CONTENT_URI, + new String[] {NUMBER}, + TYPE + " = " + OUTGOING_TYPE, + null, + DEFAULT_SORT_ORDER + " LIMIT 1"); + if (c == null || !c.moveToFirst()) { + return ""; + } + return c.getString(0); + } finally { + if (c != null) c.close(); + } + } + private static void removeExpiredEntries(Context context) { final ContentResolver resolver = context.getContentResolver(); resolver.delete(CONTENT_URI, "_id IN " + - "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER + " LIMIT -1 OFFSET 500)", null); } } diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 7fb9daf..93b5b4d 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -55,19 +55,21 @@ import java.io.InputStream; * </p> * <ul> * <li> - * The {@link Data} table contains all kinds of personal data: phone numbers, - * email addresses etc. The list of data kinds that can be stored in this table - * is open-ended. There is a predefined set of common kinds, but any application - * can add its own data kinds. + * A row in the {@link Data} table can store any kind of personal data, such + * as a phone number or email addresses. The set of data kinds that can be + * stored in this table is open-ended. There is a predefined set of common + * kinds, but any application can add its own data kinds. * </li> * <li> - * A row in the {@link RawContacts} table represents a set of Data describing a - * person and associated with a single account (for example, a single Gmail - * account). + * A row in the {@link RawContacts} table represents a set of data describing a + * person and associated with a single account (for example, one of the user's + * Gmail accounts). * </li> * <li> * A row in the {@link Contacts} table represents an aggregate of one or more - * RawContacts presumably describing the same person. + * RawContacts presumably describing the same person. When data in or associated with + * the RawContacts table is changed, the affected aggregate contacts are updated as + * necessary. * </li> * </ul> * <p> @@ -75,7 +77,8 @@ import java.io.InputStream; * </p> * <ul> * <li> - * {@link Groups}, which contains information about raw contact groups - the + * {@link Groups}, which contains information about raw contact groups + * such as Gmail contact groups. The * current API does not support the notion of groups spanning multiple accounts. * </li> * <li> @@ -106,11 +109,15 @@ public final class ContactsContract { public static final Uri AUTHORITY_URI = Uri.parse("content://" + AUTHORITY); /** - * An optional insert, update or delete URI parameter that allows the caller + * An optional URI parameter for insert, update, or delete queries + * that allows the caller * to specify that it is a sync adapter. The default value is false. If true - * the dirty flag is not automatically set and the "syncToNetwork" parameter - * is set to false when calling - * {@link ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}. + * {@link RawContacts#DIRTY} is not automatically set and the + * "syncToNetwork" parameter is set to false when calling + * {@link + * ContentResolver#notifyChange(android.net.Uri, android.database.ContentObserver, boolean)}. + * This prevents an unnecessary extra synchronization, see the discussion of + * the delete operation in {@link RawContacts}. */ public static final String CALLER_IS_SYNCADAPTER = "caller_is_syncadapter"; @@ -459,19 +466,23 @@ public final class ContactsContract { protected interface ContactNameColumns { /** - * The kind of data that is used as the display name for the contact, see - * DisplayNameSources. + * The kind of data that is used as the display name for the contact, such as + * structured name or email address. See DisplayNameSources. + * + * TODO: convert DisplayNameSources to a link after it is un-hidden */ public static final String DISPLAY_NAME_SOURCE = "display_name_source"; /** * The default text shown as the contact's display name. It is based on * available data, see {@link #DISPLAY_NAME_SOURCE}. + * + * @see ContactsContract.ContactNameColumns#DISPLAY_NAME_ALTERNATIVE */ public static final String DISPLAY_NAME_PRIMARY = "display_name"; /** - * Alternative representation of the display name. If display name is + * An alternative representation of the display name. If display name is * based on the structured name and the structured name follows * the Western full name style, then this field contains the "family name first" * version of the full name. Otherwise, it is the same as DISPLAY_NAME_PRIMARY. @@ -479,27 +490,42 @@ public final class ContactsContract { public static final String DISPLAY_NAME_ALTERNATIVE = "display_name_alt"; /** - * The type of alphabet used to capture the phonetic name. See + * The phonetic alphabet used to represent the {@link #PHONETIC_NAME}. See * PhoneticNameStyle. + * + * TODO: convert PhoneticNameStyle to a link after it is un-hidden */ public static final String PHONETIC_NAME_STYLE = "phonetic_name_style"; /** - * Pronunciation of the full name. See PhoneticNameStyle. + * <p> + * Pronunciation of the full name in the phonetic alphabet specified by + * {@link #PHONETIC_NAME_STYLE}. + * </p> + * <p> + * The value may be set manually by the user. + * This capability is is of interest only in countries + * with commonly used phonetic + * alphabets, such as Japan and Korea. See PhoneticNameStyle. + * </p> + * + * TODO: convert PhoneticNameStyle to a link after it is un-hidden */ public static final String PHONETIC_NAME = "phonetic_name"; /** * Sort key that takes into account locale-based traditions for sorting - * names in address books. More specifically, for Chinese names - * the sort key is the name's Pinyin spelling; for Japanese names + * names in address books. The default + * sort key is {@link #DISPLAY_NAME_PRIMARY}. For Chinese names + * the sort key is the name's Pinyin spelling, and for Japanese names * it is the Hiragana version of the phonetic name. */ public static final String SORT_KEY_PRIMARY = "sort_key"; /** * Sort key based on the alternative representation of the full name, - * specifically the one using the 'family name first' format for + * {@link #DISPLAY_NAME_ALTERNATIVE}. Thus for Western names, + * it is the one using the "family name first" format for * Western names. */ public static final String SORT_KEY_ALTERNATIVE = "sort_key_alt"; @@ -808,7 +834,10 @@ public final class ContactsContract { } /** - * Mark a contact as having been contacted. + * Mark a contact as having been contacted. This updates the + * {@link #TIMES_CONTACTED} and {@link #LAST_TIME_CONTACTED} for the + * contact, plus the corresponding values of any associated raw + * contacts. * * @param resolver the ContentResolver to use * @param contactId the person who was contacted @@ -1050,15 +1079,37 @@ public final class ContactsContract { } /** - * Constants for the raw contacts table, which contains the base contact - * information per sync source. Sync adapters and contact management apps + * Constants for the raw contacts table, which contains one row of contact + * information for each person in each synced account. Sync adapters and + * contact management apps * are the primary consumers of this API. + * + * <h3>Aggregation</h3> + * <p> + * As soon as a raw contact is inserted or whenever its constituent data + * changes, the provider will check if the raw contact matches other + * existing raw contacts and if so will aggregate it with those. The + * aggregation is reflected in the {@link RawContacts} table by the change of the + * {@link #CONTACT_ID} field, which is the reference to the aggregate contact. + * </p> + * <p> + * Changes to the structured name, organization, phone number, email address, + * or nickname trigger a re-aggregation. + * </p> + * <p> + * See also {@link AggregationExceptions} for a mechanism to control + * aggregation programmatically. + * </p> + * * <h3>Operations</h3> * <dl> * <dt><b>Insert</b></dt> - * <dd>There are two mechanisms that can be used to insert a raw contact: incremental and - * batch. The incremental method is more traditional but less efficient. It should be used - * only if the constituent data rows are unavailable at the time the raw contact is created: + * <dd> + * <p> + * Raw contacts can be inserted incrementally or in a batch. + * The incremental method is more traditional but less efficient. + * It should be used + * only if no {@link Data} values are available at the time the raw contact is created: * <pre> * ContentValues values = new ContentValues(); * values.put(RawContacts.ACCOUNT_TYPE, accountType); @@ -1066,9 +1117,10 @@ public final class ContactsContract { * Uri rawContactUri = getContentResolver().insert(RawContacts.CONTENT_URI, values); * long rawContactId = ContentUris.parseId(rawContactUri); * </pre> + * </p> * <p> - * Once data rows are available, insert those. For example, here's how you would insert - * a name: + * Once {@link Data} values become available, insert those. + * For example, here's how you would insert a name: * * <pre> * values.clear(); @@ -1084,6 +1136,7 @@ public final class ContactsContract { * and causes at most one aggregation pass. * <pre> * ArrayList<ContentProviderOperation> ops = Lists.newArrayList(); + * ... * int rawContactInsertIndex = ops.size(); * ops.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI) * .withValue(RawContacts.ACCOUNT_TYPE, accountType) @@ -1100,21 +1153,27 @@ public final class ContactsContract { * </pre> * </p> * <p> - * Please note the use of back reference in the construction of the - * {@link ContentProviderOperation}. It allows an operation to use the result of - * a previous operation by referring to it by its index in the batch. + * Note the use of {@link ContentProviderOperation.Builder#withValueBackReference(String, int)} + * to refer to the as-yet-unknown index value of the raw contact inserted in the + * first operation. * </p> + * * <dt><b>Update</b></dt> - * <dd><p>Just as with insert, the update can be done incrementally or as a batch, the - * batch mode being the preferred method.</p></dd> + * <dd><p> + * Raw contacts can be updated incrementally or in a batch. + * Batch mode should be used whenever possible. + * The procedures and considerations are analogous to those documented above for inserts. + * </p></dd> * <dt><b>Delete</b></dt> * <dd><p>When a raw contact is deleted, all of its Data rows as well as StatusUpdates, * AggregationExceptions, PhoneLookup rows are deleted automatically. When all raw - * contacts in a Contact are deleted, the Contact itself is also deleted automatically. + * contacts associated with a {@link Contacts} row are deleted, the {@link Contacts} row + * itself is also deleted automatically. * </p> * <p> - * The invocation of {@code resolver.delete(...)}, does not physically delete - * a raw contacts row. It sets the {@link #DELETED} flag on the raw contact and + * The invocation of {@code resolver.delete(...)}, does not immediately delete + * a raw contacts row. + * Instead, it sets the {@link #DELETED} flag on the raw contact and * removes the raw contact from its aggregate contact. * The sync adapter then deletes the raw contact from the server and * finalizes phone-side deletion by calling {@code resolver.delete(...)} @@ -1124,10 +1183,11 @@ public final class ContactsContract { * is marked for deletion, it will remain on the phone. However it will be * effectively invisible, because it will not be part of any aggregate contact. * </dd> + * * <dt><b>Query</b></dt> * <dd> * <p> - * Finding all raw contacts in a Contact is easy: + * It is easy to find all raw contacts in a Contact: * <pre> * Cursor c = getContentResolver().query(RawContacts.CONTENT_URI, * new String[]{RawContacts._ID}, @@ -1136,7 +1196,7 @@ public final class ContactsContract { * </pre> * </p> * <p> - * There are two ways to find raw contacts within a specific account, + * To find raw contacts within a specific account, * you can either put the account name and type in the selection or pass them as query * parameters. The latter approach is preferable, especially when you can reuse the * URI: @@ -1178,19 +1238,11 @@ public final class ContactsContract { * </p> * </dd> * </dl> - * <h3>Aggregation</h3> - * <p> - * As soon as a raw contact is inserted or whenever its constituent data - * changes, the provider will check if the raw contact matches other - * existing raw contacts and if so will aggregate it with those. From the - * data standpoint, aggregation is reflected in the change of the - * {@link #CONTACT_ID} field, which is the reference to the aggregate contact. - * </p> - * <p> - * See also {@link AggregationExceptions} for a mechanism to control - * aggregation programmatically. - * </p> * <h2>Columns</h2> + * TODO: include {@link #DISPLAY_NAME_PRIMARY}, {@link #DISPLAY_NAME_ALTERNATIVE}, + * {@link #DISPLAY_NAME_SOURCE}, {@link #PHONETIC_NAME}, {@link #PHONETIC_NAME_STYLE}, + * {@link #SORT_KEY_PRIMARY}, {@link #SORT_KEY_ALTERNATIVE}? + * * <table class="jd-sumtable"> * <tr> * <th colspan='4'>RawContacts</th> @@ -1199,15 +1251,16 @@ public final class ContactsContract { * <td>long</td> * <td>{@link #_ID}</td> * <td>read-only</td> - * <td>Row ID. Sync adapter should try to preserve row IDs during updates. In other words, - * it would be a really bad idea to delete and reinsert a raw contact. A sync adapter should - * always do an update instead.</td> + * <td>Row ID. Sync adapters should try to preserve row IDs during updates. In other words, + * it is much better for a sync adapter to update a raw contact rather than to delete and + * re-insert it.</td> * </tr> * <tr> * <td>long</td> * <td>{@link #CONTACT_ID}</td> * <td>read-only</td> - * <td>A reference to the {@link ContactsContract.Contacts#_ID} that this raw contact belongs + * <td>The ID of the row in the {@link ContactsContract.Contacts} table + * that this raw contact belongs * to. Raw contacts are linked to contacts by the aggregation process, which can be controlled * by the {@link #AGGREGATION_MODE} field and {@link AggregationExceptions}.</td> * </tr> @@ -1238,7 +1291,8 @@ public final class ContactsContract { * <td>The number of times the contact has been contacted. To have an effect * on the corresponding value of the aggregate contact, this field * should be set at the time the raw contact is inserted. - * See {@link ContactsContract.Contacts#markAsContacted}.</td> + * After that, this value is typically updated via + * {@link ContactsContract.Contacts#markAsContacted}.</td> * </tr> * <tr> * <td>long</td> @@ -1247,14 +1301,16 @@ public final class ContactsContract { * <td>The timestamp of the last time the contact was contacted. To have an effect * on the corresponding value of the aggregate contact, this field * should be set at the time the raw contact is inserted. - * See {@link ContactsContract.Contacts#markAsContacted}.</td> + * After that, this value is typically updated via + * {@link ContactsContract.Contacts#markAsContacted}. + * </td> * </tr> * <tr> * <td>int</td> * <td>{@link #STARRED}</td> * <td>read/write</td> * <td>An indicator for favorite contacts: '1' if favorite, '0' otherwise. - * Changing this field immediately effects the corresponding aggregate contact: + * Changing this field immediately affects the corresponding aggregate contact: * if any raw contacts in that aggregate contact are starred, then the contact * itself is marked as starred.</td> * </tr> @@ -1267,7 +1323,8 @@ public final class ContactsContract { * {@link android.media.RingtoneManager#ACTION_RINGTONE_PICKER} intent. * To have an effect on the corresponding value of the aggregate contact, this field * should be set at the time the raw contact is inserted. To set a custom - * ringtone on a contact, use the field {@link ContactsContract.Contacts#CUSTOM_RINGTONE} + * ringtone on a contact, use the field {@link ContactsContract.Contacts#CUSTOM_RINGTONE + * Contacts.CUSTOM_RINGTONE} * instead.</td> * </tr> * <tr> @@ -1284,16 +1341,27 @@ public final class ContactsContract { * <td>{@link #ACCOUNT_NAME}</td> * <td>read/write-once</td> * <td>The name of the account instance to which this row belongs, which when paired with - * {@link #ACCOUNT_TYPE} identifies a specific account. It should be set at the time + * {@link #ACCOUNT_TYPE} identifies a specific account. + * For example, this will be the Gmail address if it is a Google account. + * It should be set at the time * the raw contact is inserted and never changed afterwards.</td> * </tr> * <tr> * <td>String</td> * <td>{@link #ACCOUNT_TYPE}</td> * <td>read/write-once</td> - * <td>The type of account to which this row belongs, which when paired with - * {@link #ACCOUNT_NAME} identifies a specific account. It should be set at the time - * the raw contact is inserted and never changed afterwards.</td> + * <td> + * <p> + * The type of account to which this row belongs, which when paired with + * {@link #ACCOUNT_NAME} identifies a specific account. + * It should be set at the time + * the raw contact is inserted and never changed afterwards. + * </p> + * <p> + * To ensure uniqueness, new account types should be chosen according to the + * Java package naming convention. Thus a Google account is of type "com.google". + * </p> + * </td> * </tr> * <tr> * <td>String</td> @@ -1302,8 +1370,8 @@ public final class ContactsContract { * <td>String that uniquely identifies this row to its source account. * Typically it is set at the time the raw contact is inserted and never * changed afterwards. The one notable exception is a new raw contact: it - * will have an account name and type, but no source id. This should - * indicated to the sync adapter that a new contact needs to be created + * will have an account name and type, but no source id. This + * indicates to the sync adapter that a new contact needs to be created * server-side and its ID stored in the corresponding SOURCE_ID field on * the phone. * </td> @@ -1335,7 +1403,8 @@ public final class ContactsContract { * <td>String</td> * <td>{@link #SYNC1}</td> * <td>read/write</td> - * <td>Generic column for use by sync adapters. Content provider + * <td>Generic column provided for arbitrary use by sync adapters. + * The content provider * stores this information on behalf of the sync adapter but does not * interpret it in any way. * </td> @@ -1372,46 +1441,69 @@ public final class ContactsContract { } /** - * The content:// style URI for this table + * The content:// style URI for this table, which requests a directory of + * raw contact rows matching the selection criteria. */ public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "raw_contacts"); /** - * The MIME type of {@link #CONTENT_URI} providing a directory of - * people. + * The MIME type of the results from {@link #CONTENT_URI} when a specific + * ID value is not provided, and multiple raw contacts may be returned. */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/raw_contact"; /** - * The MIME type of a {@link #CONTENT_URI} subdirectory of a single - * person. + * The MIME type of the results when a raw contact ID is appended to {@link #CONTENT_URI}, + * yielding a subdirectory of a single person. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/raw_contact"; /** - * Aggregation mode: aggregate asynchronously. + * Aggregation mode: aggregate immediately after insert or update operation(s) are complete. */ public static final int AGGREGATION_MODE_DEFAULT = 0; /** - * Aggregation mode: aggregate at the time the raw contact is inserted/updated. - * TODO: deprecate. Aggregation is now synchronous, this value is a no-op + * Do not use. + * + * TODO: deprecate in favor of {@link #AGGREGATION_MODE_DEFAULT} */ public static final int AGGREGATION_MODE_IMMEDIATE = 1; /** - * If {@link #AGGREGATION_MODE} is {@link #AGGREGATION_MODE_SUSPENDED}, changes - * to the raw contact do not cause its aggregation to be revisited. Note that changing + * <p> + * Aggregation mode: aggregation suspended temporarily, and is likely to be resumed later. + * Changes to the raw contact will update the associated aggregate contact but will not + * result in any change in how the contact is aggregated. Similar to + * {@link #AGGREGATION_MODE_DISABLED}, but maintains a link to the corresponding + * {@link Contacts} aggregate. + * </p> + * <p> + * This can be used to postpone aggregation until after a series of updates, for better + * performance and/or user experience. + * </p> + * <p> + * Note that changing * {@link #AGGREGATION_MODE} from {@link #AGGREGATION_MODE_SUSPENDED} to - * {@link #AGGREGATION_MODE_DEFAULT} does not trigger an aggregation pass. Any subsequent + * {@link #AGGREGATION_MODE_DEFAULT} does not trigger an aggregation pass, but any + * subsequent * change to the raw contact's data will. + * </p> */ public static final int AGGREGATION_MODE_SUSPENDED = 2; /** - * Aggregation mode: never aggregate this raw contact (note that the raw contact will not - * have a corresponding Aggregate and therefore will not be included in Aggregates - * query results.) + * <p> + * Aggregation mode: never aggregate this raw contact. The raw contact will not + * have a corresponding {@link Contacts} aggregate and therefore will not be included in + * {@link Contacts} query results. + * </p> + * <p> + * For example, this mode can be used for a raw contact that is marked for deletion while + * waiting for the deletion to occur on the server side. + * </p> + * + * @see #AGGREGATION_MODE_SUSPENDED */ public static final int AGGREGATION_MODE_DISABLED = 3; @@ -1441,9 +1533,11 @@ public final class ContactsContract { } /** - * A sub-directory of a single raw contact that contains all of their + * A sub-directory of a single raw contact that contains all of its * {@link ContactsContract.Data} rows. To access this directory * append {@link Data#CONTENT_DIRECTORY} to the contact URI. + * + * TODO: deprecate in favor of {@link RawContacts.Entity}. */ public static final class Data implements BaseColumns, DataColumns { /** @@ -1460,26 +1554,24 @@ public final class ContactsContract { /** * <p> - * A sub-directory of a single raw contact that contains all of their + * A sub-directory of a single raw contact that contains all of its * {@link ContactsContract.Data} rows. To access this directory append * {@link #CONTENT_DIRECTORY} to the contact URI. See * {@link RawContactsEntity} for a stand-alone table containing the same * data. * </p> * <p> - * The Entity directory is similar to the {@link RawContacts.Data} - * directory but with two important differences: - * <ul> - * <li>Entity has different ID fields: {@link #_ID} for the raw contact - * and {@link #DATA_ID} for the data rows.</li> - * <li>Entity always contains at least one row, even if there are no + * Entity has two ID fields: {@link #_ID} for the raw contact + * and {@link #DATA_ID} for the data rows. + * Entity always contains at least one row, even if there are no * actual data rows. In this case the {@link #DATA_ID} field will be - * null.</li> - * </ul> - * Using Entity should preferred to using two separate queries: - * RawContacts followed by Data. The reason is that Entity reads all - * data for a raw contact in one transaction, so there is no possibility - * of the data changing between the two queries. + * null. + * </p> + * <p> + * Entity reads all + * data for a raw contact in one transaction, to guarantee + * consistency. + * </p> */ public static final class Entity implements BaseColumns, DataColumns { /** @@ -1501,6 +1593,11 @@ public final class ContactsContract { public static final String DATA_ID = "data_id"; } + /** + * TODO: javadoc + * @param cursor + * @return + */ public static EntityIterator newEntityIterator(Cursor cursor) { return new EntityIteratorImpl(cursor); } @@ -1839,7 +1936,12 @@ public final class ContactsContract { * By convention, {@link #DATA15} is used for storing BLOBs (binary data). * </p> * <p> - * Typically you should refrain from introducing new kinds of data for an other + * The sync adapter for a given account type must correctly handle every data type + * used in the corresponding raw contacts. Otherwise it could result in lost or + * corrupted data. + * </p> + * <p> + * Similarly, you should refrain from introducing new kinds of data for an other * party's account types. For example, if you add a data row for * "favorite song" to a raw contact owned by a Google account, it will not * get synced to the server, because the Google sync adapter does not know diff --git a/core/java/android/provider/Downloads.java b/core/java/android/provider/Downloads.java index 790fe5c..eb863ef 100644 --- a/core/java/android/provider/Downloads.java +++ b/core/java/android/provider/Downloads.java @@ -19,41 +19,45 @@ package android.provider; import android.net.Uri; /** - * Exposes constants used to interact with the download manager's - * content provider. - * The constants URI ... STATUS are the names of columns in the downloads table. + * The Download Manager * - * @hide + * @pending */ -// For 1.0 the download manager can't deal with abuse from untrusted apps, so -// this API is hidden. -public final class Downloads implements BaseColumns { +public final class Downloads { + /** + * @hide + */ private Downloads() {} /** * The permission to access the download manager + * @hide */ public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER"; /** * The permission to access the download manager's advanced functions + * @hide */ public static final String PERMISSION_ACCESS_ADVANCED = "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"; /** * The permission to directly access the download manager's cache directory + * @hide */ public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM"; /** * The permission to send broadcasts on download completion + * @hide */ public static final String PERMISSION_SEND_INTENTS = "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"; /** * The content:// URI for the data table in the provider + * @hide */ public static final Uri CONTENT_URI = Uri.parse("content://downloads/download"); @@ -62,6 +66,7 @@ public final class Downloads implements BaseColumns { * Broadcast Action: this is sent by the download manager to the app * that had initiated a download when that download completes. The * download's content: uri is specified in the intent's data. + * @hide */ public static final String ACTION_DOWNLOAD_COMPLETED = "android.intent.action.DOWNLOAD_COMPLETED"; @@ -75,6 +80,7 @@ public final class Downloads implements BaseColumns { * multiple downloads. * Note: this is not currently sent for downloads that have completed * successfully. + * @hide */ public static final String ACTION_NOTIFICATION_CLICKED = "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; @@ -83,6 +89,7 @@ public final class Downloads implements BaseColumns { * The name of the column containing the URI of the data being downloaded. * <P>Type: TEXT</P> * <P>Owner can Init/Read</P> + * @hide */ public static final String COLUMN_URI = "uri"; @@ -90,6 +97,7 @@ public final class Downloads implements BaseColumns { * The name of the column containing application-specific data. * <P>Type: TEXT</P> * <P>Owner can Init/Read/Write</P> + * @hide */ public static final String COLUMN_APP_DATA = "entity"; @@ -103,6 +111,7 @@ public final class Downloads implements BaseColumns { * whether a download fully completed). * <P>Type: BOOLEAN</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_NO_INTEGRITY = "no_integrity"; @@ -112,6 +121,7 @@ public final class Downloads implements BaseColumns { * to use this filename, or a variation, as the actual name for the file. * <P>Type: TEXT</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_FILE_NAME_HINT = "hint"; @@ -120,6 +130,7 @@ public final class Downloads implements BaseColumns { * was actually stored. * <P>Type: TEXT</P> * <P>Owner can Read</P> + * @hide */ public static final String _DATA = "_data"; @@ -127,6 +138,7 @@ public final class Downloads implements BaseColumns { * The name of the column containing the MIME type of the downloaded data. * <P>Type: TEXT</P> * <P>Owner can Init/Read</P> + * @hide */ public static final String COLUMN_MIME_TYPE = "mimetype"; @@ -135,6 +147,7 @@ public final class Downloads implements BaseColumns { * of the download. See the DESTINATION_* constants for a list of legal values. * <P>Type: INTEGER</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_DESTINATION = "destination"; @@ -144,6 +157,7 @@ public final class Downloads implements BaseColumns { * a list of legal values. * <P>Type: INTEGER</P> * <P>Owner can Init/Read/Write</P> + * @hide */ public static final String COLUMN_VISIBILITY = "visibility"; @@ -153,6 +167,7 @@ public final class Downloads implements BaseColumns { * the CONTROL_* constants for a list of legal values. * <P>Type: INTEGER</P> * <P>Owner can Read</P> + * @hide */ public static final String COLUMN_CONTROL = "control"; @@ -162,6 +177,7 @@ public final class Downloads implements BaseColumns { * the STATUS_* constants for a list of legal values. * <P>Type: INTEGER</P> * <P>Owner can Read</P> + * @hide */ public static final String COLUMN_STATUS = "status"; @@ -171,6 +187,7 @@ public final class Downloads implements BaseColumns { * value. * <P>Type: BIGINT</P> * <P>Owner can Read</P> + * @hide */ public static final String COLUMN_LAST_MODIFICATION = "lastmod"; @@ -180,6 +197,7 @@ public final class Downloads implements BaseColumns { * notifications to a component in this package when the download completes. * <P>Type: TEXT</P> * <P>Owner can Init/Read</P> + * @hide */ public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage"; @@ -190,6 +208,7 @@ public final class Downloads implements BaseColumns { * Intent.setClassName(String,String). * <P>Type: TEXT</P> * <P>Owner can Init/Read</P> + * @hide */ public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass"; @@ -198,6 +217,7 @@ public final class Downloads implements BaseColumns { * is sent to the specified class and package when a download has finished. * <P>Type: TEXT</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras"; @@ -207,6 +227,7 @@ public final class Downloads implements BaseColumns { * header that gets sent with the request. * <P>Type: TEXT</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_COOKIE_DATA = "cookiedata"; @@ -215,6 +236,7 @@ public final class Downloads implements BaseColumns { * application wants the download manager to use for this download. * <P>Type: TEXT</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_USER_AGENT = "useragent"; @@ -223,6 +245,7 @@ public final class Downloads implements BaseColumns { * application wants the download manager to use for this download. * <P>Type: TEXT</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_REFERER = "referer"; @@ -231,6 +254,7 @@ public final class Downloads implements BaseColumns { * downloaded. * <P>Type: INTEGER</P> * <P>Owner can Read</P> + * @hide */ public static final String COLUMN_TOTAL_BYTES = "total_bytes"; @@ -239,6 +263,7 @@ public final class Downloads implements BaseColumns { * has been downloaded so far. * <P>Type: INTEGER</P> * <P>Owner can Read</P> + * @hide */ public static final String COLUMN_CURRENT_BYTES = "current_bytes"; @@ -251,6 +276,7 @@ public final class Downloads implements BaseColumns { * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED. * <P>Type: INTEGER</P> * <P>Owner can Init</P> + * @hide */ public static final String COLUMN_OTHER_UID = "otheruid"; @@ -260,6 +286,7 @@ public final class Downloads implements BaseColumns { * list of downloads. * <P>Type: TEXT</P> * <P>Owner can Init/Read/Write</P> + * @hide */ public static final String COLUMN_TITLE = "title"; @@ -269,6 +296,7 @@ public final class Downloads implements BaseColumns { * user in the list of downloads. * <P>Type: TEXT</P> * <P>Owner can Init/Read/Write</P> + * @hide */ public static final String COLUMN_DESCRIPTION = "description"; @@ -284,6 +312,7 @@ public final class Downloads implements BaseColumns { * Downloads to the external destination only write files for which * there is a registered handler. The resulting files are accessible * by filename to all applications. + * @hide */ public static final int DESTINATION_EXTERNAL = 0; @@ -295,6 +324,7 @@ public final class Downloads implements BaseColumns { * application can access the file (indirectly through a content * provider). This requires the * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission. + * @hide */ public static final int DESTINATION_CACHE_PARTITION = 1; @@ -304,6 +334,7 @@ public final class Downloads implements BaseColumns { * for private files (similar to CACHE_PARTITION) that aren't deleted * immediately after they are used, and are kept around by the download * manager as long as space is available. + * @hide */ public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2; @@ -311,16 +342,19 @@ public final class Downloads implements BaseColumns { * This download will be saved to the download manager's private * partition, as with DESTINATION_CACHE_PARTITION, but the download * will not proceed if the user is on a roaming data connection. + * @hide */ public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3; /** * This download is allowed to run. + * @hide */ public static final int CONTROL_RUN = 0; /** * This download must pause at the first opportunity. + * @hide */ public static final int CONTROL_PAUSED = 1; @@ -337,6 +371,7 @@ public final class Downloads implements BaseColumns { /** * Returns whether the status is informational (i.e. 1xx). + * @hide */ public static boolean isStatusInformational(int status) { return (status >= 100 && status < 200); @@ -346,6 +381,7 @@ public final class Downloads implements BaseColumns { * Returns whether the download is suspended. (i.e. whether the download * won't complete without some action from outside the download * manager). + * @hide */ public static boolean isStatusSuspended(int status) { return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED); @@ -353,6 +389,7 @@ public final class Downloads implements BaseColumns { /** * Returns whether the status is a success (i.e. 2xx). + * @hide */ public static boolean isStatusSuccess(int status) { return (status >= 200 && status < 300); @@ -360,6 +397,7 @@ public final class Downloads implements BaseColumns { /** * Returns whether the status is an error (i.e. 4xx or 5xx). + * @hide */ public static boolean isStatusError(int status) { return (status >= 400 && status < 600); @@ -367,6 +405,7 @@ public final class Downloads implements BaseColumns { /** * Returns whether the status is a client error (i.e. 4xx). + * @hide */ public static boolean isStatusClientError(int status) { return (status >= 400 && status < 500); @@ -374,6 +413,7 @@ public final class Downloads implements BaseColumns { /** * Returns whether the status is a server error (i.e. 5xx). + * @hide */ public static boolean isStatusServerError(int status) { return (status >= 500 && status < 600); @@ -382,6 +422,7 @@ public final class Downloads implements BaseColumns { /** * Returns whether the download has completed (either with success or * error). + * @hide */ public static boolean isStatusCompleted(int status) { return (status >= 200 && status < 300) || (status >= 400 && status < 600); @@ -389,21 +430,25 @@ public final class Downloads implements BaseColumns { /** * This download hasn't stated yet + * @hide */ public static final int STATUS_PENDING = 190; /** * This download hasn't stated yet and is paused + * @hide */ public static final int STATUS_PENDING_PAUSED = 191; /** * This download has started + * @hide */ public static final int STATUS_RUNNING = 192; /** * This download has started and is paused + * @hide */ public static final int STATUS_RUNNING_PAUSED = 193; @@ -412,18 +457,21 @@ public final class Downloads implements BaseColumns { * Warning: there might be other status values that indicate success * in the future. * Use isSucccess() to capture the entire category. + * @hide */ public static final int STATUS_SUCCESS = 200; /** * This request couldn't be parsed. This is also used when processing * requests with unknown/unsupported URI schemes. + * @hide */ public static final int STATUS_BAD_REQUEST = 400; /** * This download can't be performed because the content type cannot be * handled. + * @hide */ public static final int STATUS_NOT_ACCEPTABLE = 406; @@ -435,6 +483,7 @@ public final class Downloads implements BaseColumns { * client when a response is received whose length cannot be determined * accurately (therefore making it impossible to know when a download * completes). + * @hide */ public static final int STATUS_LENGTH_REQUIRED = 411; @@ -442,11 +491,13 @@ public final class Downloads implements BaseColumns { * This download was interrupted and cannot be resumed. * This is the code for the HTTP error "Precondition Failed", and it is * also used in situations where the client doesn't have an ETag at all. + * @hide */ public static final int STATUS_PRECONDITION_FAILED = 412; /** * This download was canceled + * @hide */ public static final int STATUS_CANCELED = 490; @@ -454,12 +505,14 @@ public final class Downloads implements BaseColumns { * This download has completed with an error. * Warning: there will be other status values that indicate errors in * the future. Use isStatusError() to capture the entire category. + * @hide */ public static final int STATUS_UNKNOWN_ERROR = 491; /** * This download couldn't be completed because of a storage issue. * Typically, that's because the filesystem is missing or full. + * @hide */ public static final int STATUS_FILE_ERROR = 492; @@ -467,47 +520,550 @@ public final class Downloads implements BaseColumns { * This download couldn't be completed because of an HTTP * redirect response that the download manager couldn't * handle. + * @hide */ public static final int STATUS_UNHANDLED_REDIRECT = 493; /** * This download couldn't be completed because of an * unspecified unhandled HTTP code. + * @hide */ public static final int STATUS_UNHANDLED_HTTP_CODE = 494; /** * This download couldn't be completed because of an * error receiving or processing data at the HTTP level. + * @hide */ public static final int STATUS_HTTP_DATA_ERROR = 495; /** * This download couldn't be completed because of an * HttpException while setting up the request. + * @hide */ public static final int STATUS_HTTP_EXCEPTION = 496; /** * This download couldn't be completed because there were * too many redirects. + * @hide */ public static final int STATUS_TOO_MANY_REDIRECTS = 497; /** * This download is visible but only shows in the notifications * while it's in progress. + * @hide */ public static final int VISIBILITY_VISIBLE = 0; /** * This download is visible and shows in the notifications while * in progress and after completion. + * @hide */ public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; /** * This download doesn't show in the UI or in the notifications. + * @hide */ public static final int VISIBILITY_HIDDEN = 2; + + /** + * Implementation details + * + * Exposes constants used to interact with the download manager's + * content provider. + * The constants URI ... STATUS are the names of columns in the downloads table. + * + * @hide + */ + public static final class Impl implements BaseColumns { + private Impl() {} + + /** + * The permission to access the download manager + */ + public static final String PERMISSION_ACCESS = "android.permission.ACCESS_DOWNLOAD_MANAGER"; + + /** + * The permission to access the download manager's advanced functions + */ + public static final String PERMISSION_ACCESS_ADVANCED = + "android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED"; + + /** + * The permission to directly access the download manager's cache directory + */ + public static final String PERMISSION_CACHE = "android.permission.ACCESS_CACHE_FILESYSTEM"; + + /** + * The permission to send broadcasts on download completion + */ + public static final String PERMISSION_SEND_INTENTS = + "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS"; + + /** + * The content:// URI for the data table in the provider + */ + public static final Uri CONTENT_URI = + Uri.parse("content://downloads/download"); + + /** + * Broadcast Action: this is sent by the download manager to the app + * that had initiated a download when that download completes. The + * download's content: uri is specified in the intent's data. + */ + public static final String ACTION_DOWNLOAD_COMPLETED = + "android.intent.action.DOWNLOAD_COMPLETED"; + + /** + * Broadcast Action: this is sent by the download manager to the app + * that had initiated a download when the user selects the notification + * associated with that download. The download's content: uri is specified + * in the intent's data if the click is associated with a single download, + * or Downloads.CONTENT_URI if the notification is associated with + * multiple downloads. + * Note: this is not currently sent for downloads that have completed + * successfully. + */ + public static final String ACTION_NOTIFICATION_CLICKED = + "android.intent.action.DOWNLOAD_NOTIFICATION_CLICKED"; + + /** + * The name of the column containing the URI of the data being downloaded. + * <P>Type: TEXT</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_URI = "uri"; + + /** + * The name of the column containing application-specific data. + * <P>Type: TEXT</P> + * <P>Owner can Init/Read/Write</P> + */ + public static final String COLUMN_APP_DATA = "entity"; + + /** + * The name of the column containing the flags that indicates whether + * the initiating application is capable of verifying the integrity of + * the downloaded file. When this flag is set, the download manager + * performs downloads and reports success even in some situations where + * it can't guarantee that the download has completed (e.g. when doing + * a byte-range request without an ETag, or when it can't determine + * whether a download fully completed). + * <P>Type: BOOLEAN</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_NO_INTEGRITY = "no_integrity"; + + /** + * The name of the column containing the filename that the initiating + * application recommends. When possible, the download manager will attempt + * to use this filename, or a variation, as the actual name for the file. + * <P>Type: TEXT</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_FILE_NAME_HINT = "hint"; + + /** + * The name of the column containing the filename where the downloaded data + * was actually stored. + * <P>Type: TEXT</P> + * <P>Owner can Read</P> + */ + public static final String _DATA = "_data"; + + /** + * The name of the column containing the MIME type of the downloaded data. + * <P>Type: TEXT</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_MIME_TYPE = "mimetype"; + + /** + * The name of the column containing the flag that controls the destination + * of the download. See the DESTINATION_* constants for a list of legal values. + * <P>Type: INTEGER</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_DESTINATION = "destination"; + + /** + * The name of the column containing the flags that controls whether the + * download is displayed by the UI. See the VISIBILITY_* constants for + * a list of legal values. + * <P>Type: INTEGER</P> + * <P>Owner can Init/Read/Write</P> + */ + public static final String COLUMN_VISIBILITY = "visibility"; + + /** + * The name of the column containing the current control state of the download. + * Applications can write to this to control (pause/resume) the download. + * the CONTROL_* constants for a list of legal values. + * <P>Type: INTEGER</P> + * <P>Owner can Read</P> + */ + public static final String COLUMN_CONTROL = "control"; + + /** + * The name of the column containing the current status of the download. + * Applications can read this to follow the progress of each download. See + * the STATUS_* constants for a list of legal values. + * <P>Type: INTEGER</P> + * <P>Owner can Read</P> + */ + public static final String COLUMN_STATUS = "status"; + + /** + * The name of the column containing the date at which some interesting + * status changed in the download. Stored as a System.currentTimeMillis() + * value. + * <P>Type: BIGINT</P> + * <P>Owner can Read</P> + */ + public static final String COLUMN_LAST_MODIFICATION = "lastmod"; + + /** + * The name of the column containing the package name of the application + * that initiating the download. The download manager will send + * notifications to a component in this package when the download completes. + * <P>Type: TEXT</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_NOTIFICATION_PACKAGE = "notificationpackage"; + + /** + * The name of the column containing the component name of the class that + * will receive notifications associated with the download. The + * package/class combination is passed to + * Intent.setClassName(String,String). + * <P>Type: TEXT</P> + * <P>Owner can Init/Read</P> + */ + public static final String COLUMN_NOTIFICATION_CLASS = "notificationclass"; + + /** + * If extras are specified when requesting a download they will be provided in the intent that + * is sent to the specified class and package when a download has finished. + * <P>Type: TEXT</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_NOTIFICATION_EXTRAS = "notificationextras"; + + /** + * The name of the column contain the values of the cookie to be used for + * the download. This is used directly as the value for the Cookie: HTTP + * header that gets sent with the request. + * <P>Type: TEXT</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_COOKIE_DATA = "cookiedata"; + + /** + * The name of the column containing the user agent that the initiating + * application wants the download manager to use for this download. + * <P>Type: TEXT</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_USER_AGENT = "useragent"; + + /** + * The name of the column containing the referer (sic) that the initiating + * application wants the download manager to use for this download. + * <P>Type: TEXT</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_REFERER = "referer"; + + /** + * The name of the column containing the total size of the file being + * downloaded. + * <P>Type: INTEGER</P> + * <P>Owner can Read</P> + */ + public static final String COLUMN_TOTAL_BYTES = "total_bytes"; + + /** + * The name of the column containing the size of the part of the file that + * has been downloaded so far. + * <P>Type: INTEGER</P> + * <P>Owner can Read</P> + */ + public static final String COLUMN_CURRENT_BYTES = "current_bytes"; + + /** + * The name of the column where the initiating application can provide the + * UID of another application that is allowed to access this download. If + * multiple applications share the same UID, all those applications will be + * allowed to access this download. This column can be updated after the + * download is initiated. This requires the permission + * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED. + * <P>Type: INTEGER</P> + * <P>Owner can Init</P> + */ + public static final String COLUMN_OTHER_UID = "otheruid"; + + /** + * The name of the column where the initiating application can provided the + * title of this download. The title will be displayed ito the user in the + * list of downloads. + * <P>Type: TEXT</P> + * <P>Owner can Init/Read/Write</P> + */ + public static final String COLUMN_TITLE = "title"; + + /** + * The name of the column where the initiating application can provide the + * description of this download. The description will be displayed to the + * user in the list of downloads. + * <P>Type: TEXT</P> + * <P>Owner can Init/Read/Write</P> + */ + public static final String COLUMN_DESCRIPTION = "description"; + + /* + * Lists the destinations that an application can specify for a download. + */ + + /** + * This download will be saved to the external storage. This is the + * default behavior, and should be used for any file that the user + * can freely access, copy, delete. Even with that destination, + * unencrypted DRM files are saved in secure internal storage. + * Downloads to the external destination only write files for which + * there is a registered handler. The resulting files are accessible + * by filename to all applications. + */ + public static final int DESTINATION_EXTERNAL = 0; + + /** + * This download will be saved to the download manager's private + * partition. This is the behavior used by applications that want to + * download private files that are used and deleted soon after they + * get downloaded. All file types are allowed, and only the initiating + * application can access the file (indirectly through a content + * provider). This requires the + * android.permission.ACCESS_DOWNLOAD_MANAGER_ADVANCED permission. + */ + public static final int DESTINATION_CACHE_PARTITION = 1; + + /** + * This download will be saved to the download manager's private + * partition and will be purged as necessary to make space. This is + * for private files (similar to CACHE_PARTITION) that aren't deleted + * immediately after they are used, and are kept around by the download + * manager as long as space is available. + */ + public static final int DESTINATION_CACHE_PARTITION_PURGEABLE = 2; + + /** + * This download will be saved to the download manager's private + * partition, as with DESTINATION_CACHE_PARTITION, but the download + * will not proceed if the user is on a roaming data connection. + */ + public static final int DESTINATION_CACHE_PARTITION_NOROAMING = 3; + + /** + * This download is allowed to run. + */ + public static final int CONTROL_RUN = 0; + + /** + * This download must pause at the first opportunity. + */ + public static final int CONTROL_PAUSED = 1; + + /* + * Lists the states that the download manager can set on a download + * to notify applications of the download progress. + * The codes follow the HTTP families:<br> + * 1xx: informational<br> + * 2xx: success<br> + * 3xx: redirects (not used by the download manager)<br> + * 4xx: client errors<br> + * 5xx: server errors + */ + + /** + * Returns whether the status is informational (i.e. 1xx). + */ + public static boolean isStatusInformational(int status) { + return (status >= 100 && status < 200); + } + + /** + * Returns whether the download is suspended. (i.e. whether the download + * won't complete without some action from outside the download + * manager). + */ + public static boolean isStatusSuspended(int status) { + return (status == STATUS_PENDING_PAUSED || status == STATUS_RUNNING_PAUSED); + } + + /** + * Returns whether the status is a success (i.e. 2xx). + */ + public static boolean isStatusSuccess(int status) { + return (status >= 200 && status < 300); + } + + /** + * Returns whether the status is an error (i.e. 4xx or 5xx). + */ + public static boolean isStatusError(int status) { + return (status >= 400 && status < 600); + } + + /** + * Returns whether the status is a client error (i.e. 4xx). + */ + public static boolean isStatusClientError(int status) { + return (status >= 400 && status < 500); + } + + /** + * Returns whether the status is a server error (i.e. 5xx). + */ + public static boolean isStatusServerError(int status) { + return (status >= 500 && status < 600); + } + + /** + * Returns whether the download has completed (either with success or + * error). + */ + public static boolean isStatusCompleted(int status) { + return (status >= 200 && status < 300) || (status >= 400 && status < 600); + } + + /** + * This download hasn't stated yet + */ + public static final int STATUS_PENDING = 190; + + /** + * This download hasn't stated yet and is paused + */ + public static final int STATUS_PENDING_PAUSED = 191; + + /** + * This download has started + */ + public static final int STATUS_RUNNING = 192; + + /** + * This download has started and is paused + */ + public static final int STATUS_RUNNING_PAUSED = 193; + + /** + * This download has successfully completed. + * Warning: there might be other status values that indicate success + * in the future. + * Use isSucccess() to capture the entire category. + */ + public static final int STATUS_SUCCESS = 200; + + /** + * This request couldn't be parsed. This is also used when processing + * requests with unknown/unsupported URI schemes. + */ + public static final int STATUS_BAD_REQUEST = 400; + + /** + * This download can't be performed because the content type cannot be + * handled. + */ + public static final int STATUS_NOT_ACCEPTABLE = 406; + + /** + * This download cannot be performed because the length cannot be + * determined accurately. This is the code for the HTTP error "Length + * Required", which is typically used when making requests that require + * a content length but don't have one, and it is also used in the + * client when a response is received whose length cannot be determined + * accurately (therefore making it impossible to know when a download + * completes). + */ + public static final int STATUS_LENGTH_REQUIRED = 411; + + /** + * This download was interrupted and cannot be resumed. + * This is the code for the HTTP error "Precondition Failed", and it is + * also used in situations where the client doesn't have an ETag at all. + */ + public static final int STATUS_PRECONDITION_FAILED = 412; + + /** + * This download was canceled + */ + public static final int STATUS_CANCELED = 490; + + /** + * This download has completed with an error. + * Warning: there will be other status values that indicate errors in + * the future. Use isStatusError() to capture the entire category. + */ + public static final int STATUS_UNKNOWN_ERROR = 491; + + /** + * This download couldn't be completed because of a storage issue. + * Typically, that's because the filesystem is missing or full. + */ + public static final int STATUS_FILE_ERROR = 492; + + /** + * This download couldn't be completed because of an HTTP + * redirect response that the download manager couldn't + * handle. + */ + public static final int STATUS_UNHANDLED_REDIRECT = 493; + + /** + * This download couldn't be completed because of an + * unspecified unhandled HTTP code. + */ + public static final int STATUS_UNHANDLED_HTTP_CODE = 494; + + /** + * This download couldn't be completed because of an + * error receiving or processing data at the HTTP level. + */ + public static final int STATUS_HTTP_DATA_ERROR = 495; + + /** + * This download couldn't be completed because of an + * HttpException while setting up the request. + */ + public static final int STATUS_HTTP_EXCEPTION = 496; + + /** + * This download couldn't be completed because there were + * too many redirects. + */ + public static final int STATUS_TOO_MANY_REDIRECTS = 497; + + /** + * This download is visible but only shows in the notifications + * while it's in progress. + */ + public static final int VISIBILITY_VISIBLE = 0; + + /** + * This download is visible and shows in the notifications while + * in progress and after completion. + */ + public static final int VISIBILITY_VISIBLE_NOTIFY_COMPLETED = 1; + + /** + * This download doesn't show in the UI or in the notifications. + */ + public static final int VISIBILITY_HIDDEN = 2; + + } } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index fe3b149..eb48a0c 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -20,6 +20,8 @@ import com.android.internal.os.HandlerCaller; import com.android.internal.view.BaseIWindow; import com.android.internal.view.BaseSurfaceHolder; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.app.Service; import android.app.WallpaperManager; import android.content.BroadcastReceiver; @@ -58,9 +60,13 @@ import java.util.ArrayList; public abstract class WallpaperService extends Service { /** * The {@link Intent} that must be declared as handled by the service. + * To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_WALLPAPER} permission so + * that other applications can not abuse it. */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = - "android.service.wallpaper.WallpaperService"; + "android.service.wallpaper.WallpaperService"; /** * Name under which a WallpaperService component publishes information diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java new file mode 100644 index 0000000..2da8f14 --- /dev/null +++ b/core/java/android/view/ScaleGestureDetector.java @@ -0,0 +1,360 @@ +/* + * Copyright (C) 2010 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.view; + +import android.content.Context; + +/** + * Detects transformation gestures involving more than one pointer ("multitouch") + * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener} + * callback will notify users when a particular gesture event has occurred. + * This class should only be used with {@link MotionEvent}s reported via touch. + * + * To use this class: + * <ul> + * <li>Create an instance of the {@code ScaleGestureDetector} for your + * {@link View} + * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call + * {@link #onTouchEvent(MotionEvent)}. The methods defined in your + * callback will be executed when the events occur. + * </ul> + * @hide Pending API approval + */ +public class ScaleGestureDetector { + /** + * The listener for receiving notifications when gestures occur. + * If you want to listen for all the different gestures then implement + * this interface. If you only want to listen for a subset it might + * be easier to extend {@link SimpleOnScaleGestureListener}. + * + * An application will receive events in the following order: + * <ul> + * <li>One {@link OnScaleGestureListener#onScaleBegin()} + * <li>Zero or more {@link OnScaleGestureListener#onScale()} + * <li>One {@link OnScaleGestureListener#onTransformEnd()} + * </ul> + */ + public interface OnScaleGestureListener { + /** + * Responds to scaling events for a gesture in progress. + * Reported by pointer motion. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should consider this event + * as handled. If an event was not handled, the detector + * will continue to accumulate movement until an event is + * handled. This can be useful if an application, for example, + * only wants to update scaling factors if the change is + * greater than 0.01. + */ + public boolean onScale(ScaleGestureDetector detector); + + /** + * Responds to the beginning of a scaling gesture. Reported by + * new pointers going down. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + * @return Whether or not the detector should continue recognizing + * this gesture. For example, if a gesture is beginning + * with a focal point outside of a region where it makes + * sense, onScaleBegin() may return false to ignore the + * rest of the gesture. + */ + public boolean onScaleBegin(ScaleGestureDetector detector); + + /** + * Responds to the end of a scale gesture. Reported by existing + * pointers going up. If the end of a gesture would result in a fling, + * {@link onTransformFling()} is called instead. + * + * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()} + * and {@link ScaleGestureDetector#getFocusY()} will return the location + * of the pointer remaining on the screen. + * + * @param detector The detector reporting the event - use this to + * retrieve extended info about event state. + */ + public void onScaleEnd(ScaleGestureDetector detector); + } + + /** + * A convenience class to extend when you only want to listen for a subset + * of scaling-related events. This implements all methods in + * {@link OnScaleGestureListener} but does nothing. + * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} and + * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} return + * {@code true}. + */ + public class SimpleOnScaleGestureListener implements OnScaleGestureListener { + + public boolean onScale(ScaleGestureDetector detector) { + return true; + } + + public boolean onScaleBegin(ScaleGestureDetector detector) { + return true; + } + + public void onScaleEnd(ScaleGestureDetector detector) { + // Intentionally empty + } + } + + private static final float PRESSURE_THRESHOLD = 0.67f; + + private Context mContext; + private OnScaleGestureListener mListener; + private boolean mGestureInProgress; + + private MotionEvent mPrevEvent; + private MotionEvent mCurrEvent; + + private float mFocusX; + private float mFocusY; + private float mPrevFingerDiffX; + private float mPrevFingerDiffY; + private float mCurrFingerDiffX; + private float mCurrFingerDiffY; + private float mCurrLen; + private float mPrevLen; + private float mScaleFactor; + private float mCurrPressure; + private float mPrevPressure; + private long mTimeDelta; + + public ScaleGestureDetector(Context context, OnScaleGestureListener listener) { + mContext = context; + mListener = listener; + } + + public boolean onTouchEvent(MotionEvent event) { + final int action = event.getAction(); + boolean handled = true; + + if (!mGestureInProgress) { + if ((action == MotionEvent.ACTION_POINTER_1_DOWN || + action == MotionEvent.ACTION_POINTER_2_DOWN) && + event.getPointerCount() >= 2) { + // We have a new multi-finger gesture + + // Be paranoid in case we missed an event + reset(); + + mPrevEvent = MotionEvent.obtain(event); + mTimeDelta = 0; + + setContext(event); + mGestureInProgress = mListener.onScaleBegin(this); + } + } else { + // Transform gesture in progress - attempt to handle it + switch (action) { + case MotionEvent.ACTION_POINTER_1_UP: + case MotionEvent.ACTION_POINTER_2_UP: + // Gesture ended + setContext(event); + + // Set focus point to the remaining finger + int id = (((action & MotionEvent.ACTION_POINTER_ID_MASK) + >> MotionEvent.ACTION_POINTER_ID_SHIFT) == 0) ? 1 : 0; + mFocusX = event.getX(id); + mFocusY = event.getY(id); + + mListener.onScaleEnd(this); + mGestureInProgress = false; + + reset(); + break; + + case MotionEvent.ACTION_CANCEL: + mListener.onScaleEnd(this); + mGestureInProgress = false; + + reset(); + break; + + case MotionEvent.ACTION_MOVE: + setContext(event); + + // Only accept the event if our relative pressure is within + // a certain limit - this can help filter shaky data as a + // finger is lifted. + if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) { + final boolean updatePrevious = mListener.onScale(this); + + if (updatePrevious) { + mPrevEvent.recycle(); + mPrevEvent = MotionEvent.obtain(event); + } + } + break; + } + } + return handled; + } + + private void setContext(MotionEvent curr) { + if (mCurrEvent != null) { + mCurrEvent.recycle(); + } + mCurrEvent = MotionEvent.obtain(curr); + + mCurrLen = -1; + mPrevLen = -1; + mScaleFactor = -1; + + final MotionEvent prev = mPrevEvent; + + final float px0 = prev.getX(0); + final float py0 = prev.getY(0); + final float px1 = prev.getX(1); + final float py1 = prev.getY(1); + final float cx0 = curr.getX(0); + final float cy0 = curr.getY(0); + final float cx1 = curr.getX(1); + final float cy1 = curr.getY(1); + + final float pvx = px1 - px0; + final float pvy = py1 - py0; + final float cvx = cx1 - cx0; + final float cvy = cy1 - cy0; + mPrevFingerDiffX = pvx; + mPrevFingerDiffY = pvy; + mCurrFingerDiffX = cvx; + mCurrFingerDiffY = cvy; + + mFocusX = cx0 + cvx * 0.5f; + mFocusY = cy0 + cvy * 0.5f; + mTimeDelta = curr.getEventTime() - prev.getEventTime(); + mCurrPressure = curr.getPressure(0) + curr.getPressure(1); + mPrevPressure = prev.getPressure(0) + prev.getPressure(1); + } + + private void reset() { + if (mPrevEvent != null) { + mPrevEvent.recycle(); + mPrevEvent = null; + } + if (mCurrEvent != null) { + mCurrEvent.recycle(); + mCurrEvent = null; + } + } + + /** + * Returns {@code true} if a two-finger scale gesture is in progress. + * @return {@code true} if a scale gesture is in progress, {@code false} otherwise. + */ + public boolean isInProgress() { + return mGestureInProgress; + } + + /** + * Get the X coordinate of the current gesture's focal point. + * If a gesture is in progress, the focal point is directly between + * the two pointers forming the gesture. + * If a gesture is ending, the focal point is the location of the + * remaining pointer on the screen. + * If {@link isInProgress()} would return false, the result of this + * function is undefined. + * + * @return X coordinate of the focal point in pixels. + */ + public float getFocusX() { + return mFocusX; + } + + /** + * Get the Y coordinate of the current gesture's focal point. + * If a gesture is in progress, the focal point is directly between + * the two pointers forming the gesture. + * If a gesture is ending, the focal point is the location of the + * remaining pointer on the screen. + * If {@link isInProgress()} would return false, the result of this + * function is undefined. + * + * @return Y coordinate of the focal point in pixels. + */ + public float getFocusY() { + return mFocusY; + } + + /** + * Return the current distance between the two pointers forming the + * gesture in progress. + * + * @return Distance between pointers in pixels. + */ + public float getCurrentSpan() { + if (mCurrLen == -1) { + final float cvx = mCurrFingerDiffX; + final float cvy = mCurrFingerDiffY; + mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy); + } + return mCurrLen; + } + + /** + * Return the previous distance between the two pointers forming the + * gesture in progress. + * + * @return Previous distance between pointers in pixels. + */ + public float getPreviousSpan() { + if (mPrevLen == -1) { + final float pvx = mPrevFingerDiffX; + final float pvy = mPrevFingerDiffY; + mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy); + } + return mPrevLen; + } + + /** + * Return the scaling factor from the previous scale event to the current + * event. This value is defined as + * ({@link getCurrentSpan()} / {@link getPreviousSpan()}). + * + * @return The current scaling factor. + */ + public float getScaleFactor() { + if (mScaleFactor == -1) { + mScaleFactor = getCurrentSpan() / getPreviousSpan(); + } + return mScaleFactor; + } + + /** + * Return the time difference in milliseconds between the previous + * accepted scaling event and the current scaling event. + * + * @return Time difference since the last scaling event in milliseconds. + */ + public long getTimeDelta() { + return mTimeDelta; + } + + /** + * Return the event time of the current event being processed. + * + * @return Current event time in milliseconds. + */ + public long getEventTime() { + return mCurrEvent.getEventTime(); + } +} diff --git a/core/java/android/view/TransformGestureDetector.java b/core/java/android/view/TransformGestureDetector.java deleted file mode 100644 index 196716a..0000000 --- a/core/java/android/view/TransformGestureDetector.java +++ /dev/null @@ -1,316 +0,0 @@ -/* - * Copyright (C) 2010 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.view; - -import android.content.Context; -import android.util.Log; -import android.view.GestureDetector.SimpleOnGestureListener; - -/** - * Detects transformation gestures involving more than one pointer ("multitouch") - * using the supplied {@link MotionEvent}s. The {@link OnGestureListener} callback - * will notify users when a particular gesture event has occurred. This class - * should only be used with {@link MotionEvent}s reported via touch. - * - * To use this class: - * <ul> - * <li>Create an instance of the {@code TransformGestureDetector} for your - * {@link View} - * <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call - * {@link #onTouchEvent(MotionEvent)}. The methods defined in your - * callback will be executed when the events occur. - * </ul> - * @hide Pending API approval - */ -public class TransformGestureDetector { - /** - * The listener for receiving notifications when gestures occur. - * If you want to listen for all the different gestures then implement - * this interface. If you only want to listen for a subset it might - * be easier to extend {@link SimpleOnGestureListener}. - * - * An application will receive events in the following order: - * One onTransformBegin() - * Zero or more onTransform() - * One onTransformEnd() or onTransformFling() - */ - public interface OnTransformGestureListener { - /** - * Responds to transformation events for a gesture in progress. - * Reported by pointer motion. - * - * @param detector The detector reporting the event - use this to - * retrieve extended info about event state. - * @return true if the event was handled, false otherwise. - */ - public boolean onTransform(TransformGestureDetector detector); - - /** - * Responds to the beginning of a transformation gesture. Reported by - * new pointers going down. - * - * @param detector The detector reporting the event - use this to - * retrieve extended info about event state. - * @return true if the event was handled, false otherwise. - */ - public boolean onTransformBegin(TransformGestureDetector detector); - - /** - * Responds to the end of a transformation gesture. Reported by existing - * pointers going up. If the end of a gesture would result in a fling, - * onTransformFling is called instead. - * - * @param detector The detector reporting the event - use this to - * retrieve extended info about event state. - * @return true if the event was handled, false otherwise. - */ - public boolean onTransformEnd(TransformGestureDetector detector); - - /** - * Responds to the end of a transformation gesture that begins a fling. - * Reported by existing pointers going up. If the end of a gesture - * would not result in a fling, onTransformEnd is called instead. - * - * @param detector The detector reporting the event - use this to - * retrieve extended info about event state. - * @return true if the event was handled, false otherwise. - */ - public boolean onTransformFling(TransformGestureDetector detector); - } - - private static final boolean DEBUG = false; - - private static final int INITIAL_EVENT_IGNORES = 2; - - private Context mContext; - private float mTouchSizeScale; - private OnTransformGestureListener mListener; - private int mVelocityTimeUnits; - private MotionEvent mInitialEvent; - - private MotionEvent mPrevEvent; - private MotionEvent mCurrEvent; - private VelocityTracker mVelocityTracker; - - private float mCenterX; - private float mCenterY; - private float mTransX; - private float mTransY; - private float mPrevFingerDiffX; - private float mPrevFingerDiffY; - private float mCurrFingerDiffX; - private float mCurrFingerDiffY; - private float mRotateDegrees; - private float mCurrLen; - private float mPrevLen; - private float mScaleFactor; - - // Units in pixels. Current value is pulled out of thin air for debugging only. - private float mPointerJumpLimit = 30; - - private int mEventIgnoreCount; - - public TransformGestureDetector(Context context, OnTransformGestureListener listener, - int velocityTimeUnits) { - mContext = context; - mListener = listener; - mTouchSizeScale = context.getResources().getDisplayMetrics().widthPixels/3; - mVelocityTimeUnits = velocityTimeUnits; - mEventIgnoreCount = INITIAL_EVENT_IGNORES; - } - - public TransformGestureDetector(Context context, OnTransformGestureListener listener) { - this(context, listener, 1000); - } - - public boolean onTouchEvent(MotionEvent event) { - final int action = event.getAction(); - boolean handled = true; - - if (mInitialEvent == null) { - // No transform gesture in progress - if ((action == MotionEvent.ACTION_POINTER_1_DOWN || - action == MotionEvent.ACTION_POINTER_2_DOWN) && - event.getPointerCount() >= 2) { - // We have a new multi-finger gesture - mInitialEvent = MotionEvent.obtain(event); - mPrevEvent = MotionEvent.obtain(event); - mVelocityTracker = VelocityTracker.obtain(); - handled = mListener.onTransformBegin(this); - } - } else { - // Transform gesture in progress - attempt to handle it - switch (action) { - case MotionEvent.ACTION_POINTER_1_UP: - case MotionEvent.ACTION_POINTER_2_UP: - // Gesture ended - handled = mListener.onTransformEnd(this); - - reset(); - break; - - case MotionEvent.ACTION_CANCEL: - handled = mListener.onTransformEnd(this); - - reset(); - break; - - case MotionEvent.ACTION_MOVE: - setContext(event); - - // Our first few events can be crazy from some touchscreens - drop them. - if (mEventIgnoreCount == 0) { - mVelocityTracker.addMovement(event); - handled = mListener.onTransform(this); - } else { - mEventIgnoreCount--; - } - - mPrevEvent.recycle(); - mPrevEvent = MotionEvent.obtain(event); - break; - } - } - return handled; - } - - private void setContext(MotionEvent curr) { - mCurrEvent = MotionEvent.obtain(curr); - - mRotateDegrees = -1; - mCurrLen = -1; - mPrevLen = -1; - mScaleFactor = -1; - - final MotionEvent prev = mPrevEvent; - - float px0 = prev.getX(0); - float py0 = prev.getY(0); - float px1 = prev.getX(1); - float py1 = prev.getY(1); - float cx0 = curr.getX(0); - float cy0 = curr.getY(0); - float cx1 = curr.getX(1); - float cy1 = curr.getY(1); - - // Some touchscreens do weird things with pointer values where points are - // too close along one axis. Try to detect this here and smooth things out. - // The main indicator is that we get the X or Y value from the other pointer. - final float dx0 = cx0 - px0; - final float dy0 = cy0 - py0; - final float dx1 = cx1 - px1; - final float dy1 = cy1 - py1; - - if (cx0 == cx1) { - if (Math.abs(dx0) > mPointerJumpLimit) { - cx0 = px0; - } else if (Math.abs(dx1) > mPointerJumpLimit) { - cx1 = px1; - } - } else if (cy0 == cy1) { - if (Math.abs(dy0) > mPointerJumpLimit) { - cy0 = py0; - } else if (Math.abs(dy1) > mPointerJumpLimit) { - cy1 = py1; - } - } - - final float pvx = px1 - px0; - final float pvy = py1 - py0; - final float cvx = cx1 - cx0; - final float cvy = cy1 - cy0; - mPrevFingerDiffX = pvx; - mPrevFingerDiffY = pvy; - mCurrFingerDiffX = cvx; - mCurrFingerDiffY = cvy; - - final float pmidx = px0 + pvx * 0.5f; - final float pmidy = py0 + pvy * 0.5f; - final float cmidx = cx0 + cvx * 0.5f; - final float cmidy = cy0 + cvy * 0.5f; - - mCenterX = cmidx; - mCenterY = cmidy; - mTransX = cmidx - pmidx; - mTransY = cmidy - pmidy; - } - - private void reset() { - if (mInitialEvent != null) { - mInitialEvent.recycle(); - mInitialEvent = null; - } - if (mPrevEvent != null) { - mPrevEvent.recycle(); - mPrevEvent = null; - } - if (mCurrEvent != null) { - mCurrEvent.recycle(); - mCurrEvent = null; - } - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - mEventIgnoreCount = INITIAL_EVENT_IGNORES; - } - - public float getCenterX() { - return mCenterX; - } - - public float getCenterY() { - return mCenterY; - } - - public float getTranslateX() { - return mTransX; - } - - public float getTranslateY() { - return mTransY; - } - - public float getCurrentSpan() { - if (mCurrLen == -1) { - final float cvx = mCurrFingerDiffX; - final float cvy = mCurrFingerDiffY; - mCurrLen = (float)Math.sqrt(cvx*cvx + cvy*cvy); - } - return mCurrLen; - } - - public float getPreviousSpan() { - if (mPrevLen == -1) { - final float pvx = mPrevFingerDiffX; - final float pvy = mPrevFingerDiffY; - mPrevLen = (float)Math.sqrt(pvx*pvx + pvy*pvy); - } - return mPrevLen; - } - - public float getScaleFactor() { - if (mScaleFactor == -1) { - mScaleFactor = getCurrentSpan() / getPreviousSpan(); - } - return mScaleFactor; - } - - public float getRotation() { - throw new UnsupportedOperationException(); - } -} diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index a5e0e94..2ddf5f8 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -16,6 +16,8 @@ package android.view.inputmethod; +import android.annotation.SdkConstant; +import android.annotation.SdkConstant.SdkConstantType; import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.ResultReceiver; @@ -54,9 +56,12 @@ public interface InputMethod { /** * This is the interface name that a service implementing an input * method should say that it supports -- that is, this is the action it - * uses for its intent filter. (Note: this name is used because this - * interface should be moved to the view package.) + * uses for its intent filter. + * To be supported, the service must also require the + * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission so + * that other applications can not abuse it. */ + @SdkConstant(SdkConstantType.SERVICE_ACTION) public static final String SERVICE_INTERFACE = "android.view.InputMethod"; /** diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java index f350d13..e496d97 100644 --- a/core/java/android/webkit/JWebCoreJavaBridge.java +++ b/core/java/android/webkit/JWebCoreJavaBridge.java @@ -247,4 +247,5 @@ final class JWebCoreJavaBridge extends Handler { private native void nativeUpdatePluginDirectories(String[] directories, boolean reload); public native void setNetworkOnLine(boolean online); + public native void setNetworkType(String type, String subtype); } diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 09ed931..db5641c 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -80,6 +80,7 @@ import java.io.IOException; import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; +import java.util.HashMap; import java.util.Map; import junit.framework.Assert; @@ -1132,6 +1133,16 @@ public class WebView extends AbsoluteLayout } /** + * Inform WebView about the current network type. + * {@hide} + */ + public void setNetworkType(String type, String subtype) { + Map<String, String> map = new HashMap<String, String>(); + map.put("type", type); + map.put("subtype", subtype); + mWebViewCore.sendMessage(EventHub.SET_NETWORK_TYPE, map); + } + /** * Save the state of this WebView used in * {@link android.app.Activity#onSaveInstanceState}. Please note that this * method no longer stores the display data for this WebView. The previous diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 949b318..d509bb4 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -878,6 +878,8 @@ final class WebViewCore { static final int HIDE_FULLSCREEN = 182; + static final int SET_NETWORK_TYPE = 183; + // private message ids private static final int DESTROY = 200; @@ -1110,6 +1112,16 @@ final class WebViewCore { .setNetworkOnLine(msg.arg1 == 1); break; + case SET_NETWORK_TYPE: + if (BrowserFrame.sJavaBridge == null) { + throw new IllegalStateException("No WebView " + + "has been created in this process!"); + } + Map<String, String> map = (Map<String, String>) msg.obj; + BrowserFrame.sJavaBridge + .setNetworkType(map.get("type"), map.get("subtype")); + break; + case CLEAR_CACHE: clearCache(msg.arg1 == 1); break; diff --git a/core/java/android/widget/LinearLayout.java b/core/java/android/widget/LinearLayout.java index 37372c5..0ce70fa 100644 --- a/core/java/android/widget/LinearLayout.java +++ b/core/java/android/widget/LinearLayout.java @@ -50,6 +50,7 @@ public class LinearLayout extends ViewGroup { * Whether the children of this layout are baseline aligned. Only applicable * if {@link #mOrientation} is horizontal. */ + @ViewDebug.ExportedProperty private boolean mBaselineAligned = true; /** @@ -59,6 +60,7 @@ public class LinearLayout extends ViewGroup { * Note: this is orthogonal to {@link #mBaselineAligned}, which is concerned * with whether the children of this layout are baseline aligned. */ + @ViewDebug.ExportedProperty private int mBaselineAlignedChildIndex = -1; /** @@ -66,12 +68,30 @@ public class LinearLayout extends ViewGroup { * We'll calculate the baseline of this layout as we measure vertically; for * horizontal linear layouts, the offset of 0 is appropriate. */ + @ViewDebug.ExportedProperty private int mBaselineChildTop = 0; + @ViewDebug.ExportedProperty private int mOrientation; + @ViewDebug.ExportedProperty(mapping = { + @ViewDebug.IntToString(from = -1, to = "NONE"), + @ViewDebug.IntToString(from = Gravity.NO_GRAVITY, to = "NONE"), + @ViewDebug.IntToString(from = Gravity.TOP, to = "TOP"), + @ViewDebug.IntToString(from = Gravity.BOTTOM, to = "BOTTOM"), + @ViewDebug.IntToString(from = Gravity.LEFT, to = "LEFT"), + @ViewDebug.IntToString(from = Gravity.RIGHT, to = "RIGHT"), + @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL, to = "CENTER_VERTICAL"), + @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL, to = "FILL_VERTICAL"), + @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"), + @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL, to = "FILL_HORIZONTAL"), + @ViewDebug.IntToString(from = Gravity.CENTER, to = "CENTER"), + @ViewDebug.IntToString(from = Gravity.FILL, to = "FILL") + }) private int mGravity = Gravity.LEFT | Gravity.TOP; + @ViewDebug.ExportedProperty private int mTotalLength; + @ViewDebug.ExportedProperty private float mWeightSum; private int[] mMaxAscent; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index c49a86a..d81476a 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -927,21 +927,27 @@ android:description="@string/permdesc_readInputState" android:protectionLevel="signature" /> - <!-- Must be required by input method services, to ensure that only the - system can bind to them. --> + <!-- Must be required by an {@link android.inputmethodservice.InputMethodService}, + to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_INPUT_METHOD" android:label="@string/permlab_bindInputMethod" android:description="@string/permdesc_bindInputMethod" android:protectionLevel="signature" /> - <!-- Must be required by wallpaper services, to ensure that only the - system can bind to them. - @hide Live Wallpaper --> + <!-- Must be required by a {@link android.service.wallpaper.WallpaperService}, + to ensure that only the system can bind to it. --> <permission android:name="android.permission.BIND_WALLPAPER" android:label="@string/permlab_bindWallpaper" android:description="@string/permdesc_bindWallpaper" android:protectionLevel="signatureOrSystem" /> + <!-- Must be required by device administration receiver, to ensure that only the + system can interact with it. --> + <permission android:name="android.permission.BIND_DEVICE_ADMIN" + android:label="@string/permlab_bindDeviceAdmin" + android:description="@string/permdesc_bindDeviceAdmin" + android:protectionLevel="signature" /> + <!-- Allows low-level access to setting the orientation (actually rotation) of the screen. Not for use by normal applications. --> <permission android:name="android.permission.SET_ORIENTATION" diff --git a/core/res/res/values/arrays.xml b/core/res/res/values/arrays.xml index 53bb586..3dbfa25 100644 --- a/core/res/res/values/arrays.xml +++ b/core/res/res/values/arrays.xml @@ -131,7 +131,8 @@ the first component from this list which is found to be installed is set as the preferred activity. --> <string-array name="default_web_search_providers"> - <item>com.android.quicksearchbox/com.android.googlesearch.GoogleSearch</item> + <item>com.google.android.googlequicksearchbox/.google.GoogleSearch</item> + <item>com.android.quicksearchbox/.google.GoogleSearch</item> <item>com.google.android.providers.enhancedgooglesearch/.Launcher</item> <item>com.android.googlesearch/.GoogleSearch</item> <item>com.android.websearch/.Search.1</item> diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 539db83..265dacd 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -617,6 +617,12 @@ interface of a wallpaper. Should never be needed for normal applications.</string> <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permlab_bindDeviceAdmin">interact with a device admin</string> + <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> + <string name="permdesc_bindDeviceAdmin">Allows the holder to send intents to + a device administrator. Should never be needed for normal applications.</string> + + <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permlab_setOrientation">change screen orientation</string> <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. --> <string name="permdesc_setOrientation">Allows an application to change diff --git a/keystore/java/android/security/SystemKeyStore.java b/keystore/java/android/security/SystemKeyStore.java index 452125a..61a4293 100644 --- a/keystore/java/android/security/SystemKeyStore.java +++ b/keystore/java/android/security/SystemKeyStore.java @@ -35,6 +35,7 @@ import javax.crypto.SecretKey; public class SystemKeyStore { private static final String SYSTEM_KEYSTORE_DIRECTORY = "misc/systemkeys"; + private static final String KEY_FILE_EXTENSION = ".sks"; private static SystemKeyStore mInstance = new SystemKeyStore(); private SystemKeyStore() { } @@ -43,6 +44,28 @@ public class SystemKeyStore { return mInstance; } + public static String toHexString(byte[] keyData) { + if (keyData == null) { + return null; + } + int keyLen = keyData.length; + int expectedStringLen = keyData.length * 2; + StringBuilder sb = new StringBuilder(expectedStringLen); + for (int i = 0; i < keyData.length; i++) { + String hexStr = Integer.toString(keyData[i] & 0x00FF, 16); + if (hexStr.length() == 1) { + hexStr = "0" + hexStr; + } + sb.append(hexStr); + } + return sb.toString(); + } + + public String generateNewKeyHexString(int numBits, String algName, String keyName) + throws NoSuchAlgorithmException { + return toHexString(generateNewKey(numBits, algName, keyName)); + } + public byte[] generateNewKey(int numBits, String algName, String keyName) throws NoSuchAlgorithmException { @@ -78,10 +101,14 @@ public class SystemKeyStore { private File getKeyFile(String keyName) { File sysKeystoreDir = new File(Environment.getDataDirectory(), SYSTEM_KEYSTORE_DIRECTORY); - File keyFile = new File(sysKeystoreDir, keyName); + File keyFile = new File(sysKeystoreDir, keyName + KEY_FILE_EXTENSION); return keyFile; } + public String retrieveKeyHexString(String keyName) { + return toHexString(retrieveKey(keyName)); + } + public byte[] retrieveKey(String keyName) { File keyFile = getKeyFile(keyName); diff --git a/keystore/tests/src/android/security/SystemKeyStoreTest.java b/keystore/tests/src/android/security/SystemKeyStoreTest.java index a85f889..a9e2687 100644 --- a/keystore/tests/src/android/security/SystemKeyStoreTest.java +++ b/keystore/tests/src/android/security/SystemKeyStoreTest.java @@ -32,6 +32,7 @@ import android.test.suitebuilder.annotation.MediumTest; public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> { private static final String keyName = "TestKey"; + private static final String keyName2 = "TestKey2"; private SystemKeyStore mSysKeyStore = null; public SystemKeyStoreTest() { @@ -43,6 +44,7 @@ public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> { mSysKeyStore = SystemKeyStore.getInstance(); try { mSysKeyStore.deleteKey(keyName); + mSysKeyStore.deleteKey(keyName2); } catch (Exception e) { } super.setUp(); } @@ -51,13 +53,14 @@ public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> { protected void tearDown() throws Exception { try { mSysKeyStore.deleteKey(keyName); + mSysKeyStore.deleteKey(keyName2); } catch (Exception e) { } super.tearDown(); } public void testBasicAccess() throws Exception { try { - byte[] newKey = mSysKeyStore.generateNewKey(128, "AES", keyName); + byte[] newKey = mSysKeyStore.generateNewKey(128, "Blowfish", keyName); assertNotNull(newKey); byte[] recKey = mSysKeyStore.retrieveKey(keyName); assertEquals(newKey.length, recKey.length); @@ -67,6 +70,14 @@ public class SystemKeyStoreTest extends ActivityUnitTestCase<Activity> { mSysKeyStore.deleteKey(keyName); byte[] nullKey = mSysKeyStore.retrieveKey(keyName); assertNull(nullKey); + + String newKeyStr = mSysKeyStore.generateNewKeyHexString(128, "AES", keyName2); + assertNotNull(newKeyStr); + String recKeyStr = mSysKeyStore.retrieveKeyHexString(keyName2); + assertEquals(newKeyStr, recKeyStr); + mSysKeyStore.deleteKey(keyName2); + String nullKey2 = mSysKeyStore.retrieveKeyHexString(keyName); + assertNull(nullKey2); } catch (Exception e) { fail(); } diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp index 8c3f252..0d617a5 100644 --- a/media/libstagefright/omx/OMX.cpp +++ b/media/libstagefright/omx/OMX.cpp @@ -135,50 +135,6 @@ void OMX::CallbackDispatcher::threadEntry() { //////////////////////////////////////////////////////////////////////////////// -class BufferMeta { -public: - BufferMeta(OMX *owner, const sp<IMemory> &mem, bool is_backup = false) - : mOwner(owner), - mMem(mem), - mIsBackup(is_backup) { - } - - BufferMeta(OMX *owner, size_t size) - : mOwner(owner), - mSize(size), - mIsBackup(false) { - } - - void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) { - if (!mIsBackup) { - return; - } - - memcpy((OMX_U8 *)mMem->pointer() + header->nOffset, - header->pBuffer + header->nOffset, - header->nFilledLen); - } - - void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) { - if (!mIsBackup) { - return; - } - - memcpy(header->pBuffer + header->nOffset, - (const OMX_U8 *)mMem->pointer() + header->nOffset, - header->nFilledLen); - } - -private: - OMX *mOwner; - sp<IMemory> mMem; - size_t mSize; - bool mIsBackup; - - BufferMeta(const BufferMeta &); - BufferMeta &operator=(const BufferMeta &); -}; - OMX::OMX() : mMaster(new OMXMaster), mDispatcher(new CallbackDispatcher(this)), diff --git a/media/libstagefright/omx/tests/OMXHarness.cpp b/media/libstagefright/omx/tests/OMXHarness.cpp index 2e23899..5b45c1c 100644 --- a/media/libstagefright/omx/tests/OMXHarness.cpp +++ b/media/libstagefright/omx/tests/OMXHarness.cpp @@ -586,14 +586,14 @@ status_t Harness::testSeek( double r = uniform_rand(); - if (r < 0.5) { + if (i > 0 && r < 0.5) { // 50% chance of just continuing to decode from last position. requestedSeekTimeUs = -1; LOGI("requesting linear read"); } else { - if (r < 0.55) { + if (i > 0 && r < 0.55) { // 5% chance of seeking beyond end of stream. requestedSeekTimeUs = durationUs; diff --git a/media/sdutils/sdutil.cpp b/media/sdutils/sdutil.cpp index c77424f..322f743 100644 --- a/media/sdutils/sdutil.cpp +++ b/media/sdutils/sdutil.cpp @@ -86,7 +86,7 @@ static void millisecondSleep(int milliseconds) { static int mount(const char* path) { String16 string(path); - gMountService->mountMedia(string); + gMountService->mountVolume(string); for (int i = 0; i < 60; i++) { if (isMounted(path)) { @@ -129,6 +129,11 @@ static int asec_mount(const char *id, const char *key, int ownerUid) { return 0; } +static void asec_unmount(const char *id) { + String16 sId(id); + gMountService->unmountSecureContainer(sId); +} + static int asec_path(const char *id) { String16 sId(id); gMountService->getSecureContainerPath(sId); @@ -137,7 +142,7 @@ static int asec_path(const char *id) { static int unmount(const char* path) { String16 string(path); - gMountService->unmountMedia(string); + gMountService->unmountVolume(string); for (int i = 0; i < 20; i++) { if (!isMounted(path)) { @@ -155,7 +160,7 @@ static int format(const char* path) { if (isMounted(path)) return -EBUSY; - gMountService->formatMedia(string); + gMountService->formatVolume(string); return 0; } @@ -208,6 +213,9 @@ int main(int argc, char **argv) return android::asec_destroy(id); } else if (!strcmp(argument, "mount")) { return android::asec_mount(id, argv[4], atoi(argv[5])); + } else if (!strcmp(argument, "unmount")) { + android::asec_unmount(id); + return 0; } else if (!strcmp(argument, "path")) { return android::asec_path(id); } @@ -224,6 +232,7 @@ usage: " sdutil asec finalize <id>\n" " sdutil asec destroy <id>\n" " sdutil asec mount <id> <key> <ownerUid>\n" + " sdutil asec unmount <id>\n" " sdutil asec path <id>\n" ); return -1; diff --git a/opengl/java/android/opengl/GLSurfaceView.java b/opengl/java/android/opengl/GLSurfaceView.java index 471625e..a186e58 100644 --- a/opengl/java/android/opengl/GLSurfaceView.java +++ b/opengl/java/android/opengl/GLSurfaceView.java @@ -157,6 +157,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * * @see #getRenderMode() * @see #setRenderMode(int) + * @see #requestRender() */ public final static int RENDERMODE_WHEN_DIRTY = 0; /** @@ -165,7 +166,6 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * * @see #getRenderMode() * @see #setRenderMode(int) - * @see #requestRender() */ public final static int RENDERMODE_CONTINUOUSLY = 1; @@ -210,6 +210,9 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback // underlying surface is created and destroyed SurfaceHolder holder = getHolder(); holder.addCallback(this); + // setType is not needed for SDK 2.0 or newer. Uncomment this + // statement if back-porting this code to older SDKs. + // holder.setType(SurfaceHolder.SURFACE_TYPE_GPU); } /** @@ -579,7 +582,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * Called when the surface is created or recreated. * <p> * Called when the rendering thread - * starts and whenever the EGL context is lost. The context will typically + * starts and whenever the EGL context is lost. The EGL context will typically * be lost when the Android device awakes after going to sleep. * <p> * Since this method is called at the beginning of rendering, as well as @@ -871,7 +874,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * Initialize EGL for a given configuration spec. * @param configSpec */ - public void start(){ + public void start() { /* * Get an EGL instance */ @@ -896,8 +899,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mEglConfig = mEGLConfigChooser.chooseConfig(mEgl, mEglDisplay); /* - * Create an OpenGL ES context. This must be done only once, an - * OpenGL context is a somewhat heavy object. + * Create an EGL context. We want to do this as rarely as we can, because an + * EGL context is a somewhat heavy object. */ mEglContext = mEGLContextFactory.createContext(mEgl, mEglDisplay, mEglConfig); if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { @@ -1010,6 +1013,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback EGLSurface mEglSurface; EGLConfig mEglConfig; EGLContext mEglContext; + } /** @@ -1031,6 +1035,7 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback mRenderer = renderer; } + @Override public void run() { setName("GLThread " + getId()); @@ -1051,20 +1056,33 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback * This private method should only be called inside a * synchronized(sGLThreadManager) block. */ - private void stopEglLocked() { - if (mHaveEgl) { - mHaveEgl = false; + private void stopEglSurfaceLocked() { + if (mHaveEglSurface) { + mHaveEglSurface = false; mEglHelper.destroySurface(); - mEglHelper.finish(); - sGLThreadManager.releaseEglSurfaceLocked(this); } } + /* + * This private method should only be called inside a + * synchronized(sGLThreadManager) block. + */ + private void stopEglContextLocked() { + if (mHaveEglContext) { + mEglHelper.finish(); + mHaveEglContext = false; + sGLThreadManager.releaseEglContextLocked(this); + } + } private void guardedRun() throws InterruptedException { mEglHelper = new EglHelper(); + mHaveEglContext = false; + mHaveEglSurface = false; try { GL10 gl = null; + boolean createEglContext = false; boolean createEglSurface = false; + boolean lostEglContext = false; boolean sizeChanged = false; boolean wantRenderNotification = false; boolean doRenderNotification = false; @@ -1084,12 +1102,25 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback break; } + // Have we lost the EGL context? + if (lostEglContext) { + stopEglSurfaceLocked(); + stopEglContextLocked(); + lostEglContext = false; + } + // Do we need to release the EGL surface? - if (mHaveEgl && mPaused) { + if (mHaveEglSurface && mPaused) { if (LOG_SURFACE) { Log.i("GLThread", "releasing EGL surface because paused tid=" + getId()); } - stopEglLocked(); + stopEglSurfaceLocked(); + if (sGLThreadManager.shouldReleaseEGLContextWhenPausing()) { + stopEglContextLocked(); + if (LOG_SURFACE) { + Log.i("GLThread", "releasing EGL context because paused tid=" + getId()); + } + } } // Have we lost the surface view surface? @@ -1097,8 +1128,8 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback if (LOG_SURFACE) { Log.i("GLThread", "noticed surfaceView surface lost tid=" + getId()); } - if (mHaveEgl) { - stopEglLocked(); + if (mHaveEglSurface) { + stopEglSurfaceLocked(); } mWaitingForSurface = true; sGLThreadManager.notifyAll(); @@ -1125,16 +1156,22 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback && (mWidth > 0) && (mHeight > 0) && (mRequestRender || (mRenderMode == RENDERMODE_CONTINUOUSLY))) { - // If we don't have an egl surface, try to acquire one. - if ((! mHaveEgl) && sGLThreadManager.tryAcquireEglSurfaceLocked(this)) { - mHaveEgl = true; + // If we don't have an EGL context, try to acquire one. + if ((! mHaveEglContext) && sGLThreadManager.tryAcquireEglContextLocked(this)) { + mHaveEglContext = true; + createEglContext = true; mEglHelper.start(); + + sGLThreadManager.notifyAll(); + } + + if (mHaveEglContext && !mHaveEglSurface) { + mHaveEglSurface = true; createEglSurface = true; sizeChanged = true; - sGLThreadManager.notifyAll(); } - if (mHaveEgl) { + if (mHaveEglSurface) { if (mSizeChanged) { sizeChanged = true; w = mWidth; @@ -1177,10 +1214,14 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback if (LOG_RENDERER) { Log.w("GLThread", "onSurfaceCreated"); } - mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); createEglSurface = false; } + if (createEglContext) { + mRenderer.onSurfaceCreated(gl, mEglHelper.mEglConfig); + createEglContext = false; + } + if (sizeChanged) { if (LOG_RENDERER) { Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")"); @@ -1193,22 +1234,25 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback Log.w("GLThread", "onDrawFrame"); } mRenderer.onDrawFrame(gl); - if(!mEglHelper.swap()) { + if (!mEglHelper.swap()) { if (LOG_SURFACE) { - Log.i("GLThread", "egl surface lost tid=" + getId()); + Log.i("GLThread", "egl context lost tid=" + getId()); } + lostEglContext = true; } if (wantRenderNotification) { doRenderNotification = true; } } + } finally { /* * clean-up everything... */ synchronized (sGLThreadManager) { - stopEglLocked(); + stopEglSurfaceLocked(); + stopEglContextLocked(); } } } @@ -1338,13 +1382,15 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback private boolean mPaused; private boolean mHasSurface; private boolean mWaitingForSurface; - private boolean mHaveEgl; + private boolean mHaveEglContext; + private boolean mHaveEglSurface; private int mWidth; private int mHeight; private int mRenderMode; private boolean mRequestRender; private boolean mRenderComplete; private ArrayList<Runnable> mEventQueue = new ArrayList<Runnable>(); + // End of member variables protected by the sGLThreadManager monitor. private Renderer mRenderer; @@ -1406,12 +1452,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback /* * Tries once to acquire the right to use an EGL - * surface. Does not block. Requires that we are already + * context. Does not block. Requires that we are already * in the sGLThreadManager monitor when this is called. * - * @return true if the right to use an EGL surface was acquired. + * @return true if the right to use an EGL context was acquired. */ - public boolean tryAcquireEglSurfaceLocked(GLThread thread) { + public boolean tryAcquireEglContextLocked(GLThread thread) { if (mEglOwner == thread || mEglOwner == null) { mEglOwner = thread; notifyAll(); @@ -1423,17 +1469,23 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } return false; } + /* - * Releases the EGL surface. Requires that we are already in the + * Releases the EGL context. Requires that we are already in the * sGLThreadManager monitor when this is called. */ - public void releaseEglSurfaceLocked(GLThread thread) { + public void releaseEglContextLocked(GLThread thread) { if (mEglOwner == thread) { mEglOwner = null; } notifyAll(); } + public synchronized boolean shouldReleaseEGLContextWhenPausing() { + checkGLESVersion(); + return mMultipleGLESContextsAllowed; + } + public synchronized void checkGLDriver(GL10 gl) { if (! mGLESDriverCheckComplete) { checkGLESVersion(); @@ -1457,14 +1509,12 @@ public class GLSurfaceView extends SurfaceView implements SurfaceHolder.Callback } mGLESVersionCheckComplete = true; } - } private boolean mGLESVersionCheckComplete; private int mGLESVersion; private boolean mGLESDriverCheckComplete; private boolean mMultipleGLESContextsAllowed; - private int mGLContextCount; private static final int kGLES_20 = 0x20000; private static final String kMSM7K_RENDERER_PREFIX = "Q3Dimension MSM7500 "; diff --git a/opengl/libagl/Android.mk b/opengl/libagl/Android.mk index 9837845..c2e9f31 100644 --- a/opengl/libagl/Android.mk +++ b/opengl/libagl/Android.mk @@ -39,6 +39,11 @@ endif ifneq ($(TARGET_SIMULATOR),true) # we need to access the private Bionic header <bionic_tls.h> + # on ARM platforms, we need to mirror the ARCH_ARM_HAVE_TLS_REGISTER + # behavior from the bionic Android.mk file + ifeq ($(TARGET_ARCH)-$(ARCH_ARM_HAVE_TLS_REGISTER),arm-true) + LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER + endif LOCAL_C_INCLUDES += bionic/libc/private endif diff --git a/opengl/libs/Android.mk b/opengl/libs/Android.mk index 7353385..6b7020f 100644 --- a/opengl/libs/Android.mk +++ b/opengl/libs/Android.mk @@ -20,6 +20,11 @@ LOCAL_MODULE:= libEGL ifeq ($(TARGET_SIMULATOR),true) else LOCAL_SHARED_LIBRARIES += libdl + # Bionic's private TLS header relies on the ARCH_ARM_HAVE_TLS_REGISTER to + # select the appropriate TLS codepath + ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) + LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER + endif # we need to access the private Bionic header <bionic_tls.h> LOCAL_C_INCLUDES += bionic/libc/private endif @@ -75,6 +80,9 @@ ifeq ($(TARGET_SIMULATOR),true) else LOCAL_SHARED_LIBRARIES += libdl # we need to access the private Bionic header <bionic_tls.h> + ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) + LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER + endif LOCAL_C_INCLUDES += bionic/libc/private endif @@ -108,6 +116,9 @@ ifeq ($(TARGET_SIMULATOR),true) else LOCAL_SHARED_LIBRARIES += libdl # we need to access the private Bionic header <bionic_tls.h> + ifeq ($(ARCH_ARM_HAVE_TLS_REGISTER),true) + LOCAL_CFLAGS += -DHAVE_ARM_TLS_REGISTER + endif LOCAL_C_INCLUDES += bionic/libc/private endif diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 80129d0..5a8d35f 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -180,6 +180,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { private ConnectivityService(Context context) { if (DBG) Log.v(TAG, "ConnectivityService starting up"); + + // setup our unique device name + String id = Settings.Secure.getString(context.getContentResolver(), + Settings.Secure.ANDROID_ID); + if (id != null && id.length() > 0) { + String name = new String("android_").concat(id); + SystemProperties.set("net.hostname", name); + } + mContext = context; mNetTrackers = new NetworkStateTracker[ ConnectivityManager.MAX_NETWORK_TYPE+1]; @@ -1248,11 +1257,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { info = (NetworkInfo) msg.obj; int type = info.getType(); NetworkInfo.State state = info.getState(); - if(mNetAttributes[type].mLastState == state) { + if (mNetAttributes[type].mLastState == state) { if (DBG) { // TODO - remove this after we validate the dropping doesn't break anything Log.d(TAG, "Dropping ConnectivityChange for " + - info.getTypeName() +": " + + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); } return; diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java new file mode 100644 index 0000000..e13ddc8 --- /dev/null +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -0,0 +1,471 @@ +/* + * Copyright (C) 2010 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 com.android.common.FastXmlSerializer; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +import android.app.DeviceAdmin; +import android.app.DeviceAdminInfo; +import android.app.DevicePolicyManager; +import android.app.IDevicePolicyManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.os.Binder; +import android.util.Log; +import android.util.Xml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.List; + +/** + * Implementation of the device policy APIs. + */ +public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { + private static final String TAG = "DevicePolicyManagerService"; + + private final Context mContext; + + int mActivePasswordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED; + int mActivePasswordLength = 0; + int mFailedPasswordAttempts = 0; + + ActiveAdmin mActiveAdmin; + + static class ActiveAdmin { + ActiveAdmin(DeviceAdminInfo _info) { + info = _info; + } + + final DeviceAdminInfo info; + int getUid() { return info.getActivityInfo().applicationInfo.uid; } + + int passwordMode = DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED; + int minimumPasswordLength = 0; + long maximumTimeToUnlock = 0; + } + + /** + * Instantiates the service. + */ + public DevicePolicyManagerService(Context context) { + mContext = context; + } + + ActiveAdmin getActiveAdminForCallerLocked(ComponentName who) throws SecurityException { + if (mActiveAdmin != null && mActiveAdmin.getUid() == Binder.getCallingPid()) { + if (who != null) { + if (!who.getPackageName().equals(mActiveAdmin.info.getActivityInfo().packageName) + || !who.getClassName().equals(mActiveAdmin.info.getActivityInfo().name)) { + throw new SecurityException("Current admin is not " + who); + } + } + return mActiveAdmin; + } + throw new SecurityException("Current admin is not owned by uid " + Binder.getCallingUid()); + } + + + void sendAdminCommandLocked(ActiveAdmin policy, String action) { + Intent intent = new Intent(action); + intent.setComponent(policy.info.getComponent()); + mContext.sendBroadcast(intent); + } + + ComponentName getActiveAdminLocked() { + if (mActiveAdmin != null) { + return mActiveAdmin.info.getComponent(); + } + return null; + } + + void removeActiveAdminLocked(ComponentName adminReceiver) { + ComponentName cur = getActiveAdminLocked(); + if (cur != null && cur.equals(adminReceiver)) { + sendAdminCommandLocked(mActiveAdmin, + DeviceAdmin.ACTION_DEVICE_ADMIN_DISABLED); + // XXX need to wait for it to complete. + mActiveAdmin = null; + } + } + + public DeviceAdminInfo findAdmin(ComponentName adminName) { + Intent resolveIntent = new Intent(); + resolveIntent.setComponent(adminName); + List<ResolveInfo> infos = mContext.getPackageManager().queryBroadcastReceivers( + resolveIntent, PackageManager.GET_META_DATA); + if (infos == null || infos.size() <= 0) { + throw new IllegalArgumentException("Unknown admin: " + adminName); + } + + try { + return new DeviceAdminInfo(mContext, infos.get(0)); + } catch (XmlPullParserException e) { + Log.w(TAG, "Bad device admin requested: " + adminName, e); + return null; + } catch (IOException e) { + Log.w(TAG, "Bad device admin requested: " + adminName, e); + return null; + } + } + + private static JournaledFile makeJournaledFile() { + final String base = "/data/system/device_policies.xml"; + return new JournaledFile(new File(base), new File(base + ".tmp")); + } + + private void saveSettingsLocked() { + JournaledFile journal = makeJournaledFile(); + FileOutputStream stream = null; + try { + stream = new FileOutputStream(journal.chooseForWrite(), false); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(stream, "utf-8"); + out.startDocument(null, true); + + out.startTag(null, "policies"); + + ActiveAdmin ap = mActiveAdmin; + if (ap != null) { + out.startTag(null, "admin"); + out.attribute(null, "name", ap.info.getComponent().flattenToString()); + if (ap.passwordMode != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) { + out.startTag(null, "password-mode"); + out.attribute(null, "value", Integer.toString(ap.passwordMode)); + out.endTag(null, "password-mode"); + if (ap.minimumPasswordLength > 0) { + out.startTag(null, "min-password-length"); + out.attribute(null, "value", Integer.toString(ap.minimumPasswordLength)); + out.endTag(null, "mn-password-length"); + } + } + if (ap.maximumTimeToUnlock != DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED) { + out.startTag(null, "max-time-to-unlock"); + out.attribute(null, "value", Long.toString(ap.maximumTimeToUnlock)); + out.endTag(null, "max-time-to-unlock"); + } + out.endTag(null, "admin"); + } + out.endTag(null, "policies"); + + out.endDocument(); + stream.close(); + journal.commit(); + } catch (IOException e) { + try { + if (stream != null) { + stream.close(); + } + } catch (IOException ex) { + // Ignore + } + journal.rollback(); + } + } + + private void loadSettingsLocked() { + JournaledFile journal = makeJournaledFile(); + FileInputStream stream = null; + File file = journal.chooseForRead(); + boolean success = false; + try { + stream = new FileInputStream(file); + XmlPullParser parser = Xml.newPullParser(); + parser.setInput(stream, null); + + int type = parser.next(); + while (type != XmlPullParser.START_TAG) { + type = parser.next(); + } + String tag = parser.getName(); + if ("policies".equals(tag)) { + ActiveAdmin ap = null; + do { + type = parser.next(); + if (type == XmlPullParser.START_TAG) { + tag = parser.getName(); + if (ap == null) { + if ("admin".equals(tag)) { + DeviceAdminInfo dai = findAdmin( + ComponentName.unflattenFromString( + parser.getAttributeValue(null, "name"))); + if (dai != null) { + ap = new ActiveAdmin(dai); + } + } + } else if ("password-mode".equals(tag)) { + ap.passwordMode = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("min-password-length".equals(tag)) { + ap.minimumPasswordLength = Integer.parseInt( + parser.getAttributeValue(null, "value")); + } else if ("max-time-to-unlock".equals(tag)) { + ap.maximumTimeToUnlock = Long.parseLong( + parser.getAttributeValue(null, "value")); + } + } else if (type == XmlPullParser.END_TAG) { + tag = parser.getName(); + if (ap != null && "admin".equals(tag)) { + mActiveAdmin = ap; + ap = null; + } + } + } while (type != XmlPullParser.END_DOCUMENT); + success = true; + } + } catch (NullPointerException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (NumberFormatException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (XmlPullParserException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (IOException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } catch (IndexOutOfBoundsException e) { + Log.w(TAG, "failed parsing " + file + " " + e); + } + try { + if (stream != null) { + stream.close(); + } + } catch (IOException e) { + // Ignore + } + + if (!success) { + Log.w(TAG, "No valid start tag found in policies file"); + } + } + + public void systemReady() { + synchronized (this) { + loadSettingsLocked(); + } + } + + public void setActiveAdmin(ComponentName adminReceiver) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + DeviceAdminInfo info = findAdmin(adminReceiver); + if (info == null) { + throw new IllegalArgumentException("Bad admin: " + adminReceiver); + } + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + ComponentName cur = getActiveAdminLocked(); + if (cur != null && cur.equals(adminReceiver)) { + throw new IllegalStateException("An admin is already set"); + } + if (cur != null) { + removeActiveAdminLocked(adminReceiver); + } + mActiveAdmin = new ActiveAdmin(info); + saveSettingsLocked(); + sendAdminCommandLocked(mActiveAdmin, + DeviceAdmin.ACTION_DEVICE_ADMIN_ENABLED); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public ComponentName getActiveAdmin() { + synchronized (this) { + return getActiveAdminLocked(); + } + } + + public void removeActiveAdmin(ComponentName adminReceiver) { + synchronized (this) { + if (mActiveAdmin == null || mActiveAdmin.getUid() != Binder.getCallingUid()) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + } + long ident = Binder.clearCallingIdentity(); + try { + removeActiveAdminLocked(adminReceiver); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void setPasswordMode(ComponentName who, int mode) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who); + if (ap.passwordMode != mode) { + ap.passwordMode = mode; + saveSettingsLocked(); + } + } + } + + public int getPasswordMode() { + synchronized (this) { + return mActiveAdmin != null ? mActiveAdmin.passwordMode + : DevicePolicyManager.PASSWORD_MODE_UNSPECIFIED; + } + } + + public int getActivePasswordMode() { + synchronized (this) { + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked(null); + return mActivePasswordMode; + } + } + + public void setMinimumPasswordLength(ComponentName who, int length) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who); + if (ap.minimumPasswordLength != length) { + ap.minimumPasswordLength = length; + saveSettingsLocked(); + } + } + } + + public int getMinimumPasswordLength() { + synchronized (this) { + return mActiveAdmin != null ? mActiveAdmin.minimumPasswordLength : 0; + } + } + + public int getActiveMinimumPasswordLength() { + synchronized (this) { + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked(null); + return mActivePasswordLength; + } + } + + public int getCurrentFailedPasswordAttempts() { + synchronized (this) { + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked(null); + return mFailedPasswordAttempts; + } + } + + public void setMaximumTimeToLock(ComponentName who, long timeMs) { + synchronized (this) { + if (who == null) { + throw new NullPointerException("ComponentName is null"); + } + ActiveAdmin ap = getActiveAdminForCallerLocked(who); + if (ap.maximumTimeToUnlock != timeMs) { + ap.maximumTimeToUnlock = timeMs; + saveSettingsLocked(); + } + } + } + + public long getMaximumTimeToLock() { + synchronized (this) { + return mActiveAdmin != null ? mActiveAdmin.maximumTimeToUnlock : 0; + } + } + + public void wipeData(int flags) { + synchronized (this) { + // This API can only be called by an active device admin, + // so try to retrieve it to check that the caller is one. + getActiveAdminForCallerLocked(null); + long ident = Binder.clearCallingIdentity(); + try { + Log.w(TAG, "*************** WIPE DATA HERE"); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void setActivePasswordState(int mode, int length) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + synchronized (this) { + if (mActivePasswordMode != mode || mActivePasswordLength != length + || mFailedPasswordAttempts != 0) { + long ident = Binder.clearCallingIdentity(); + try { + mActivePasswordMode = mode; + mActivePasswordLength = length; + mFailedPasswordAttempts = 0; + sendAdminCommandLocked(mActiveAdmin, + DeviceAdmin.ACTION_PASSWORD_CHANGED); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } + + public void reportFailedPasswordAttempt() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + mFailedPasswordAttempts++; + sendAdminCommandLocked(mActiveAdmin, + DeviceAdmin.ACTION_PASSWORD_FAILED); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + public void reportSuccessfulPasswordAttempt() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.BIND_DEVICE_ADMIN, null); + + synchronized (this) { + if (mFailedPasswordAttempts != 0) { + long ident = Binder.clearCallingIdentity(); + try { + mFailedPasswordAttempts = 0; + sendAdminCommandLocked(mActiveAdmin, + DeviceAdmin.ACTION_PASSWORD_SUCCEEDED); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + } +} diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index c8a6915..f8f8742 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -127,6 +127,7 @@ class MountService extends IMountService.Stub private boolean mUmsConnected = false; private boolean mUmsEnabled = false; + private boolean mUmsEnabling = false; private String mLegacyState = Environment.MEDIA_REMOVED; @@ -332,13 +333,16 @@ class MountService extends IMountService.Stub String vp = Environment.getExternalStorageDirectory().getPath(); String vs = getVolumeState(vp); + mUmsEnabling = enable; if (enable && vs.equals(Environment.MEDIA_MOUNTED)) { unmountVolume(vp); + mUmsEnabling = false; updateUsbMassStorageNotification(true, false); } setShareMethodEnabled(vp, "ums", enable); mUmsEnabled = enable; + mUmsEnabling = false; if (!enable) { mountVolume(vp); if (mPromptUms) { @@ -594,10 +598,14 @@ class MountService extends IMountService.Stub } else if (newState == VolumeState.NoMedia) { // NoMedia is handled via Disk Remove events } else if (newState == VolumeState.Idle) { - // Don't notify if we're in BAD_REMOVAL, NOFS, or UNMOUNTABLE + /* + * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or + * if we're in the process of enabling UMS + */ if (!vs.equals(Environment.MEDIA_BAD_REMOVAL) && !vs.equals(Environment.MEDIA_NOFS) && - !vs.equals(Environment.MEDIA_UNMOUNTABLE)) { + !vs.equals(Environment.MEDIA_UNMOUNTABLE) && + !mUmsEnabling) { notifyMediaUnmounted(mountPoint); } } else if (newState == VolumeState.Pending) { @@ -1049,6 +1057,11 @@ class MountService extends IMountService.Stub return getSecureContainerPath(id); } + public void unmountSecureContainer(String id) throws IllegalStateException { + String cmd = String.format("unmount_asec %s ", id); + mConnector.doCommand(cmd); + } + public String getSecureContainerPath(String id) throws IllegalStateException { ArrayList<String> rsp = mConnector.doCommand("asec_path " + id); diff --git a/services/java/com/android/server/PackageManagerService.java b/services/java/com/android/server/PackageManagerService.java index 4b9b366..1b6a56a 100644 --- a/services/java/com/android/server/PackageManagerService.java +++ b/services/java/com/android/server/PackageManagerService.java @@ -75,6 +75,7 @@ import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; +import android.security.SystemKeyStore; import android.util.*; import android.view.Display; import android.view.WindowManager; @@ -89,6 +90,7 @@ import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -455,7 +457,9 @@ class PackageManagerService extends IPackageManager.Stub { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, startTime); - int scanMode = SCAN_MONITOR; + // Set flag to monitor and not change apk file paths when + // scanning install directories. + int scanMode = SCAN_MONITOR | SCAN_NO_PATHS; if (mNoDexOpt) { Log.w(TAG, "Running ENG build: no pre-dexopt!"); scanMode |= SCAN_NO_DEX; @@ -573,12 +577,12 @@ class PackageManagerService extends IPackageManager.Stub { mFrameworkDir.getPath(), OBSERVER_EVENTS, true); mFrameworkInstallObserver.startWatching(); scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM, - scanMode | SCAN_NO_DEX | SCAN_NO_PATHS); + scanMode | SCAN_NO_DEX); mSystemAppDir = new File(Environment.getRootDirectory(), "app"); mSystemInstallObserver = new AppDirObserver( mSystemAppDir.getPath(), OBSERVER_EVENTS, true); mSystemInstallObserver.startWatching(); - scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM, scanMode | SCAN_NO_PATHS); + scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM, scanMode); mAppInstallDir = new File(dataDir, "app"); if (mInstaller == null) { // Make sure these dirs exist, when we are running in @@ -3754,7 +3758,7 @@ class PackageManagerService extends IPackageManager.Stub { (mIsRom ? PackageParser.PARSE_IS_SYSTEM : 0) | PackageParser.PARSE_CHATTY | PackageParser.PARSE_MUST_BE_APK, - SCAN_MONITOR); + SCAN_MONITOR | SCAN_NO_PATHS); if (p != null) { synchronized (mPackages) { grantPermissionsLP(p, false); @@ -3986,7 +3990,8 @@ class PackageManagerService extends IPackageManager.Stub { Log.w(TAG, "Package couldn't be installed in " + pkg.mPath); if ((res.returnCode=mLastScanError) == PackageManager.INSTALL_SUCCEEDED) { res.returnCode = PackageManager.INSTALL_FAILED_INVALID_APK; - } + } + } else { updateSettingsLI(newPackage, installerPackageName, res); @@ -7436,7 +7441,8 @@ class PackageManagerService extends IPackageManager.Stub { // ------- apps on sdcard specific code ------- static final boolean DEBUG_SD_INSTALL = false; - final private String mSdEncryptKey = "none"; + final private String mSdEncryptKey = "AppsOnSD"; + final private String mSdEncryptAlg = "AES"; private MountService getMountService() { return (MountService) ServiceManager.getService("mount"); @@ -7454,10 +7460,25 @@ class PackageManagerService extends IPackageManager.Stub { String cachePath = null; // Remove any pending destroy messages mHandler.removeMessages(DESTROY_SD_CONTAINER, pkgName); + String sdEncKey; + try { + sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey); + if (sdEncKey == null) { + sdEncKey = SystemKeyStore.getInstance(). + generateNewKeyHexString(128, mSdEncryptAlg, mSdEncryptKey); + if (sdEncKey == null) { + Log.e(TAG, "Failed to create encryption keys for package: " + pkgName + "."); + return null; + } + } + } catch (NoSuchAlgorithmException nsae) { + Log.e(TAG, "Failed to create encryption keys with exception: " + nsae); + return null; + } try { cachePath = mountService.createSecureContainer(pkgName, mbLen, - "vfat", mSdEncryptKey, Process.SYSTEM_UID); + "vfat", sdEncKey, Process.SYSTEM_UID); if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install " + pkgName + ", cachePath =" + cachePath); return cachePath; } catch(IllegalStateException e) { @@ -7474,7 +7495,7 @@ class PackageManagerService extends IPackageManager.Stub { try { cachePath = mountService.createSecureContainer(pkgName, mbLen, - "vfat", mSdEncryptKey, Process.SYSTEM_UID); + "vfat", sdEncKey, Process.SYSTEM_UID); if (DEBUG_SD_INSTALL) Log.i(TAG, "Trying to install again " + pkgName + ", cachePath =" + cachePath); return cachePath; } catch(IllegalStateException e) { @@ -7484,8 +7505,13 @@ class PackageManagerService extends IPackageManager.Stub { } private String mountSdDir(String pkgName, int ownerUid) { + String sdEncKey = SystemKeyStore.getInstance().retrieveKeyHexString(mSdEncryptKey); + if (sdEncKey == null) { + Log.e(TAG, "Failed to retrieve encryption keys to mount package code: " + pkgName + "."); + return null; + } try { - return getMountService().mountSecureContainer(pkgName, mSdEncryptKey, ownerUid); + return getMountService().mountSecureContainer(pkgName, sdEncKey, ownerUid); } catch (IllegalStateException e) { Log.i(TAG, "Failed to mount container for pkg : " + pkgName + " exception : " + e); } diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 674ade9..6b3f433 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -201,6 +201,7 @@ class ServerThread extends Thread { Log.e("System", "Failure starting core service", e); } + DevicePolicyManagerService devicePolicy = null; StatusBarService statusBar = null; InputMethodManagerService imm = null; AppWidgetService appWidget = null; @@ -209,16 +210,25 @@ class ServerThread extends Thread { if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { + Log.i(TAG, "Device Policy"); + devicePolicy = new DevicePolicyManagerService(context); + ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy); + } catch (Throwable e) { + Log.e(TAG, "Failure starting DevicePolicyService", e); + } + + try { Log.i(TAG, "Status Bar"); statusBar = new StatusBarService(context); - ServiceManager.addService("statusbar", statusBar); + ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); } catch (Throwable e) { Log.e(TAG, "Failure starting StatusBarService", e); } try { Log.i(TAG, "Clipboard Service"); - ServiceManager.addService("clipboard", new ClipboardService(context)); + ServiceManager.addService(Context.CLIPBOARD_SERVICE, + new ClipboardService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Clipboard Service", e); } @@ -280,14 +290,16 @@ class ServerThread extends Thread { try { Log.i(TAG, "Location Manager"); - ServiceManager.addService(Context.LOCATION_SERVICE, new LocationManagerService(context)); + ServiceManager.addService(Context.LOCATION_SERVICE, + new LocationManagerService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Location Manager", e); } try { Log.i(TAG, "Search Service"); - ServiceManager.addService( Context.SEARCH_SERVICE, new SearchManagerService(context) ); + ServiceManager.addService(Context.SEARCH_SERVICE, + new SearchManagerService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Search Service", e); } @@ -351,7 +363,8 @@ class ServerThread extends Thread { try { Log.i(TAG, "Backup Service"); - ServiceManager.addService(Context.BACKUP_SERVICE, new BackupManagerService(context)); + ServiceManager.addService(Context.BACKUP_SERVICE, + new BackupManagerService(context)); } catch (Throwable e) { Log.e(TAG, "Failure starting Backup Service", e); } @@ -391,6 +404,10 @@ class ServerThread extends Thread { // It is now time to start up the app processes... + if (devicePolicy != null) { + devicePolicy.systemReady(); + } + if (notification != null) { notification.systemReady(); } diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 40d194c..843058c 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -8381,7 +8381,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityInfo ai = ris.get(i).activityInfo; intent.setComponent(new ComponentName(ai.packageName, ai.name)); IIntentReceiver finisher = null; - if (i == 0) { + if (i == ris.size()-1) { finisher = new IIntentReceiver.Stub() { public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, @@ -8397,7 +8397,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Log.i(TAG, "Sending system update to: " + intent.getComponent()); broadcastIntentLocked(null, null, intent, null, finisher, 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID); - if (i == 0) { + if (finisher != null) { mWaitingUpdate = true; } } diff --git a/services/java/com/android/server/status/StatusBarPolicy.java b/services/java/com/android/server/status/StatusBarPolicy.java index bee0930..42c0254 100644 --- a/services/java/com/android/server/status/StatusBarPolicy.java +++ b/services/java/com/android/server/status/StatusBarPolicy.java @@ -43,6 +43,9 @@ import android.telephony.ServiceState; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; import android.text.format.DateFormat; +import android.text.style.RelativeSizeSpan; +import android.text.Spannable; +import android.text.SpannableStringBuilder; import android.util.Log; import android.view.View; import android.view.ViewGroup; @@ -61,6 +64,7 @@ import com.android.internal.telephony.cdma.EriInfo; import com.android.internal.telephony.cdma.TtyIntent; import com.android.server.am.BatteryStatsService; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.TimeZone; @@ -532,10 +536,69 @@ public class StatusBarPolicy { sInstance = new StatusBarPolicy(context, service); } + private final CharSequence getSmallTime() { + boolean b24 = DateFormat.is24HourFormat(mContext); + int res; + + if (b24) { + res = R.string.twenty_four_hour_time_format; + } else { + res = R.string.twelve_hour_time_format; + } + + String format = mContext.getString(res); + + /* + * Search for an unquoted "a" in the format string, so we can + * add dummy characters around it to let us find it again after + * formatting and change its size. + */ + int a = -1; + boolean quoted = false; + for (int i = 0; i < format.length(); i++) { + char c = format.charAt(i); + + if (c == '\'') { + quoted = !quoted; + } + + if (!quoted && c == 'a') { + a = i; + break; + } + } + + final char MAGIC1 = '\uEF00'; + final char MAGIC2 = '\uEF01'; + + if (a >= 0) { + format = format.substring(0, a) + MAGIC1 + "a" + MAGIC2 + + format.substring(a + 1); + } + + String result = new SimpleDateFormat(format).format(mCalendar.getTime()); + + int magic1 = result.indexOf(MAGIC1); + int magic2 = result.indexOf(MAGIC2); + + if (magic1 >= 0 && magic2 > magic1) { + SpannableStringBuilder formatted = new SpannableStringBuilder(result); + + formatted.setSpan(new RelativeSizeSpan(0.7f), magic1, magic2, + Spannable.SPAN_EXCLUSIVE_INCLUSIVE); + + formatted.delete(magic2, magic2 + 1); + formatted.delete(magic1, magic1 + 1); + + return formatted; + } else { + return result; + } + } + private final void updateClock() { mCalendar.setTimeInMillis(System.currentTimeMillis()); - mClockData.text = DateFormat.getTimeFormat(mContext) - .format(mCalendar.getTime()); + mClockData.text = getSmallTime(); mService.updateIcon(mClockIcon, mClockData, null); } diff --git a/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java b/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java index 7a4d934..69d55c1 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/DatabaseGeneralTest.java @@ -987,4 +987,18 @@ public class DatabaseGeneralTest extends TestCase implements PerformanceTestCase ih.close(); } + @MediumTest + public void testDbCloseReleasingAllCachedSql() { + mDatabase.execSQL("CREATE TABLE test (_id INTEGER PRIMARY KEY, text1 TEXT, text2 TEXT, " + + "num1 INTEGER, num2 INTEGER, image BLOB);"); + final String statement = "DELETE FROM test WHERE _id=?;"; + SQLiteStatement statementDoNotClose = mDatabase.compileStatement(statement); + assertTrue(statementDoNotClose.getUniqueId() > 0); + int nStatement = statementDoNotClose.getUniqueId(); + assertTrue(statementDoNotClose.getUniqueId() == nStatement); + /* do not close statementDoNotClose object. + * That should leave it in SQLiteDatabase.mPrograms. + * mDatabase.close() in tearDown() should release it. + */ + } } diff --git a/tests/AndroidTests/src/com/android/unit_tests/TimeTest.java b/tests/AndroidTests/src/com/android/unit_tests/TimeTest.java index 3b33a99..724ef6a 100644 --- a/tests/AndroidTests/src/com/android/unit_tests/TimeTest.java +++ b/tests/AndroidTests/src/com/android/unit_tests/TimeTest.java @@ -359,56 +359,58 @@ public class TimeTest extends TestCase { Time t = new Time(Time.TIMEZONE_UTC); t.parse3339("1980-05-23"); - if (!t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23) { + if (!t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23) { fail("Did not parse all-day date correctly"); } t.parse3339("1980-05-23T09:50:50"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || t.hour != 9 || t.minute != 50 || t.second != 50 || t.gmtoff != 0) { fail("Did not parse timezone-offset-less date correctly"); } t.parse3339("1980-05-23T09:50:50Z"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || t.hour != 9 || t.minute != 50 || t.second != 50 || t.gmtoff != 0) { fail("Did not parse UTC date correctly"); } t.parse3339("1980-05-23T09:50:50.0Z"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || t.hour != 9 || t.minute != 50 || t.second != 50 || t.gmtoff != 0) { fail("Did not parse UTC date correctly"); } t.parse3339("1980-05-23T09:50:50.12Z"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || t.hour != 9 || t.minute != 50 || t.second != 50 || t.gmtoff != 0) { fail("Did not parse UTC date correctly"); } t.parse3339("1980-05-23T09:50:50.123Z"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || t.hour != 9 || t.minute != 50 || t.second != 50 || t.gmtoff != 0) { fail("Did not parse UTC date correctly"); } - t.parse3339("1980-05-23T09:50:50-06:00"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || - t.hour != 9 || t.minute != 50 || t.second != 50 || - t.gmtoff != -6*3600) { + // The time should be normalized to UTC + t.parse3339("1980-05-23T09:50:50-01:05"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 10 || t.minute != 55 || t.second != 50 || + t.gmtoff != 0) { fail("Did not parse timezone-offset date correctly"); } - t.parse3339("1980-05-23T09:50:50.123-06:00"); - if (t.allDay || t.year != 1980 || t.month != 05 || t.monthDay != 23 || - t.hour != 9 || t.minute != 50 || t.second != 50 || - t.gmtoff != -6*3600) { + // The time should be normalized to UTC + t.parse3339("1980-05-23T09:50:50.123-01:05"); + if (t.allDay || t.year != 1980 || t.month != 04 || t.monthDay != 23 || + t.hour != 10 || t.minute != 55 || t.second != 50 || + t.gmtoff != 0) { fail("Did not parse timezone-offset date correctly"); } diff --git a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java index 52286d1..31ee120 100644 --- a/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java +++ b/tests/TransformTest/src/com/google/android/test/transform/TransformTestActivity.java @@ -24,9 +24,8 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.util.DisplayMetrics; import android.util.Log; -import android.view.LayoutInflater; import android.view.MotionEvent; -import android.view.TransformGestureDetector; +import android.view.ScaleGestureDetector; import android.view.View; import android.widget.LinearLayout; @@ -48,9 +47,6 @@ public class TransformTestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - - final LayoutInflater li = (LayoutInflater)getSystemService( - LAYOUT_INFLATER_SERVICE); this.setTitle(R.string.act_title); LinearLayout root = new LinearLayout(this); @@ -71,15 +67,19 @@ public class TransformTestActivity extends Activity { private float mPosY; private float mScale = 1.f; private Matrix mMatrix; - private TransformGestureDetector mDetector; + private ScaleGestureDetector mDetector; + + private float mLastX; + private float mLastY; - private class Listener implements TransformGestureDetector.OnTransformGestureListener { + private class Listener implements ScaleGestureDetector.OnScaleGestureListener { - public boolean onTransform(TransformGestureDetector detector) { - Log.d("ttest", "Translation: (" + detector.getTranslateX() + - ", " + detector.getTranslateY() + ")"); + public boolean onScale(ScaleGestureDetector detector) { float scale = detector.getScaleFactor(); + Log.d("ttest", "Scale: " + scale); + + // Limit the scale so our object doesn't get too big or disappear if (mScale * scale > 0.1f) { if (mScale * scale < 10.f) { mScale *= scale; @@ -89,16 +89,13 @@ public class TransformTestActivity extends Activity { } else { mScale = 0.1f; } - - mPosX += detector.getTranslateX(); - mPosY += detector.getTranslateY(); Log.d("ttest", "mScale: " + mScale + " mPos: (" + mPosX + ", " + mPosY + ")"); float sizeX = mDrawable.getIntrinsicWidth()/2; float sizeY = mDrawable.getIntrinsicHeight()/2; - float centerX = detector.getCenterX(); - float centerY = detector.getCenterY(); + float centerX = detector.getFocusX(); + float centerY = detector.getFocusY(); float diffX = centerX - mPosX; float diffY = centerY - mPosY; diffX = diffX*scale - diffX; @@ -115,24 +112,20 @@ public class TransformTestActivity extends Activity { return true; } - public boolean onTransformBegin(TransformGestureDetector detector) { - return true; - } - - public boolean onTransformEnd(TransformGestureDetector detector) { + public boolean onScaleBegin(ScaleGestureDetector detector) { return true; } - public boolean onTransformFling(TransformGestureDetector detector) { - return false; - } - + public void onScaleEnd(ScaleGestureDetector detector) { + mLastX = detector.getFocusX(); + mLastY = detector.getFocusY(); + } } public TransformView(Context context) { super(context); mMatrix = new Matrix(); - mDetector = new TransformGestureDetector(context, new Listener()); + mDetector = new ScaleGestureDetector(context, new Listener()); DisplayMetrics metrics = context.getResources().getDisplayMetrics(); mPosX = metrics.widthPixels/2; mPosY = metrics.heightPixels/2; @@ -151,12 +144,37 @@ public class TransformTestActivity extends Activity { @Override public boolean onTouchEvent(MotionEvent event) { - boolean handled = mDetector.onTouchEvent(event); + mDetector.onTouchEvent(event); - int pointerCount = event.getPointerCount(); - Log.d("ttest", "pointerCount: " + pointerCount); + // Handling single finger pan + if (!mDetector.isInProgress()) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastX = event.getX(); + mLastY = event.getY(); + break; + + case MotionEvent.ACTION_MOVE: + final float x = event.getX(); + final float y = event.getY(); + mPosX += x - mLastX; + mPosY += y - mLastY; + mLastX = x; + mLastY = y; + + float sizeX = mDrawable.getIntrinsicWidth()/2; + float sizeY = mDrawable.getIntrinsicHeight()/2; + + mMatrix.reset(); + mMatrix.postTranslate(-sizeX, -sizeY); + mMatrix.postScale(mScale, mScale); + mMatrix.postTranslate(mPosX, mPosY); + invalidate(); + break; + } + } - return handled; + return true; } @Override diff --git a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java index ff1b295..35f022e 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Bitmap.java +++ b/tools/layoutlib/bridge/src/android/graphics/Bitmap.java @@ -20,6 +20,7 @@ package android.graphics; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; +import java.io.InputStream; import javax.imageio.ImageIO; @@ -33,6 +34,12 @@ public final class Bitmap extends _Original_Bitmap { mImage = ImageIO.read(input); } + public Bitmap(InputStream is) throws IOException { + super(1, true, null, -1); + + mImage = ImageIO.read(is); + } + Bitmap(BufferedImage image) { super(1, true, null, -1); mImage = image; @@ -237,4 +244,35 @@ public final class Bitmap extends _Original_Bitmap { return createBitmap(colors, 0, width, width, height, config); } + public static Bitmap createScaledBitmap(Bitmap src, int dstWidth, + int dstHeight, boolean filter) { + Matrix m; + synchronized (Bitmap.class) { + // small pool of just 1 matrix + m = sScaleMatrix; + sScaleMatrix = null; + } + + if (m == null) { + m = new Matrix(); + } + + final int width = src.getWidth(); + final int height = src.getHeight(); + final float sx = dstWidth / (float)width; + final float sy = dstHeight / (float)height; + m.setScale(sx, sy); + Bitmap b = Bitmap.createBitmap(src, 0, 0, width, height, m, filter); + + synchronized (Bitmap.class) { + // do we need to check for null? why not just assign everytime? + if (sScaleMatrix == null) { + sScaleMatrix = m; + } + } + + return b; + } + + } diff --git a/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java new file mode 100644 index 0000000..e978fe8 --- /dev/null +++ b/tools/layoutlib/bridge/src/android/graphics/BitmapFactory.java @@ -0,0 +1,566 @@ +/* + * Copyright (C) 2010 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.graphics; + +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.util.DisplayMetrics; +import android.util.TypedValue; + +import java.io.BufferedInputStream; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Creates Bitmap objects from various sources, including files, streams, + * and byte-arrays. + */ +public class BitmapFactory { + public static class Options { + /** + * Create a default Options object, which if left unchanged will give + * the same result from the decoder as if null were passed. + */ + public Options() { + inDither = true; + inScaled = true; + } + + /** + * If set to true, the decoder will return null (no bitmap), but + * the out... fields will still be set, allowing the caller to query + * the bitmap without having to allocate the memory for its pixels. + */ + public boolean inJustDecodeBounds; + + /** + * If set to a value > 1, requests the decoder to subsample the original + * image, returning a smaller image to save memory. The sample size is + * the number of pixels in either dimension that correspond to a single + * pixel in the decoded bitmap. For example, inSampleSize == 4 returns + * an image that is 1/4 the width/height of the original, and 1/16 the + * number of pixels. Any value <= 1 is treated the same as 1. Note: the + * decoder will try to fulfill this request, but the resulting bitmap + * may have different dimensions that precisely what has been requested. + * Also, powers of 2 are often faster/easier for the decoder to honor. + */ + public int inSampleSize; + + /** + * If this is non-null, the decoder will try to decode into this + * internal configuration. If it is null, or the request cannot be met, + * the decoder will try to pick the best matching config based on the + * system's screen depth, and characteristics of the original image such + * as if it has per-pixel alpha (requiring a config that also does). + */ + public Bitmap.Config inPreferredConfig; + + /** + * If dither is true, the decoder will attempt to dither the decoded + * image. + */ + public boolean inDither; + + /** + * The pixel density to use for the bitmap. This will always result + * in the returned bitmap having a density set for it (see + * {@link Bitmap#setDensity(int) Bitmap.setDensity(int)). In addition, + * if {@link #inScaled} is set (which it is by default} and this + * density does not match {@link #inTargetDensity}, then the bitmap + * will be scaled to the target density before being returned. + * + * <p>If this is 0, + * {@link BitmapFactory#decodeResource(Resources, int)}, + * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, + * and {@link BitmapFactory#decodeResourceStream} + * will fill in the density associated with the resource. The other + * functions will leave it as-is and no density will be applied. + * + * @see #inTargetDensity + * @see #inScreenDensity + * @see #inScaled + * @see Bitmap#setDensity(int) + * @see android.util.DisplayMetrics#densityDpi + */ + public int inDensity; + + /** + * The pixel density of the destination this bitmap will be drawn to. + * This is used in conjunction with {@link #inDensity} and + * {@link #inScaled} to determine if and how to scale the bitmap before + * returning it. + * + * <p>If this is 0, + * {@link BitmapFactory#decodeResource(Resources, int)}, + * {@link BitmapFactory#decodeResource(Resources, int, android.graphics.BitmapFactory.Options)}, + * and {@link BitmapFactory#decodeResourceStream} + * will fill in the density associated the Resources object's + * DisplayMetrics. The other + * functions will leave it as-is and no scaling for density will be + * performed. + * + * @see #inDensity + * @see #inScreenDensity + * @see #inScaled + * @see android.util.DisplayMetrics#densityDpi + */ + public int inTargetDensity; + + /** + * The pixel density of the actual screen that is being used. This is + * purely for applications running in density compatibility code, where + * {@link #inTargetDensity} is actually the density the application + * sees rather than the real screen density. + * + * <p>By setting this, you + * allow the loading code to avoid scaling a bitmap that is currently + * in the screen density up/down to the compatibility density. Instead, + * if {@link #inDensity} is the same as {@link #inScreenDensity}, the + * bitmap will be left as-is. Anything using the resulting bitmap + * must also used {@link Bitmap#getScaledWidth(int) + * Bitmap.getScaledWidth} and {@link Bitmap#getScaledHeight + * Bitmap.getScaledHeight} to account for any different between the + * bitmap's density and the target's density. + * + * <p>This is never set automatically for the caller by + * {@link BitmapFactory} itself. It must be explicitly set, since the + * caller must deal with the resulting bitmap in a density-aware way. + * + * @see #inDensity + * @see #inTargetDensity + * @see #inScaled + * @see android.util.DisplayMetrics#densityDpi + */ + public int inScreenDensity; + + /** + * When this flag is set, if {@link #inDensity} and + * {@link #inTargetDensity} are not 0, the + * bitmap will be scaled to match {@link #inTargetDensity} when loaded, + * rather than relying on the graphics system scaling it each time it + * is drawn to a Canvas. + * + * <p>This flag is turned on by default and should be turned off if you need + * a non-scaled version of the bitmap. Nine-patch bitmaps ignore this + * flag and are always scaled. + */ + public boolean inScaled; + + /** + * If this is set to true, then the resulting bitmap will allocate its + * pixels such that they can be purged if the system needs to reclaim + * memory. In that instance, when the pixels need to be accessed again + * (e.g. the bitmap is drawn, getPixels() is called), they will be + * automatically re-decoded. + * + * For the re-decode to happen, the bitmap must have access to the + * encoded data, either by sharing a reference to the input + * or by making a copy of it. This distinction is controlled by + * inInputShareable. If this is true, then the bitmap may keep a shallow + * reference to the input. If this is false, then the bitmap will + * explicitly make a copy of the input data, and keep that. Even if + * sharing is allowed, the implementation may still decide to make a + * deep copy of the input data. + */ + public boolean inPurgeable; + + /** + * This field works in conjuction with inPurgeable. If inPurgeable is + * false, then this field is ignored. If inPurgeable is true, then this + * field determines whether the bitmap can share a reference to the + * input data (inputstream, array, etc.) or if it must make a deep copy. + */ + public boolean inInputShareable; + + /** + * Normally bitmap allocations count against the dalvik heap, which + * means they help trigger GCs when a lot have been allocated. However, + * in rare cases, the caller may want to allocate the bitmap outside of + * that heap. To request that, set inNativeAlloc to true. In these + * rare instances, it is solely up to the caller to ensure that OOM is + * managed explicitly by calling bitmap.recycle() as soon as such a + * bitmap is no longer needed. + * + * @hide pending API council approval + */ + public boolean inNativeAlloc; + + /** + * The resulting width of the bitmap, set independent of the state of + * inJustDecodeBounds. However, if there is an error trying to decode, + * outWidth will be set to -1. + */ + public int outWidth; + + /** + * The resulting height of the bitmap, set independent of the state of + * inJustDecodeBounds. However, if there is an error trying to decode, + * outHeight will be set to -1. + */ + public int outHeight; + + /** + * If known, this string is set to the mimetype of the decoded image. + * If not know, or there is an error, it is set to null. + */ + public String outMimeType; + + /** + * Temp storage to use for decoding. Suggest 16K or so. + */ + public byte[] inTempStorage; + + private native void requestCancel(); + + /** + * Flag to indicate that cancel has been called on this object. This + * is useful if there's an intermediary that wants to first decode the + * bounds and then decode the image. In that case the intermediary + * can check, inbetween the bounds decode and the image decode, to see + * if the operation is canceled. + */ + public boolean mCancel; + + /** + * This can be called from another thread while this options object is + * inside a decode... call. Calling this will notify the decoder that + * it should cancel its operation. This is not guaranteed to cancel + * the decode, but if it does, the decoder... operation will return + * null, or if inJustDecodeBounds is true, will set outWidth/outHeight + * to -1 + */ + public void requestCancelDecode() { + mCancel = true; + requestCancel(); + } + } + + /** + * Decode a file path into a bitmap. If the specified file name is null, + * or cannot be decoded into a bitmap, the function returns null. + * + * @param pathName complete path name for the file to be decoded. + * @param opts null-ok; Options that control downsampling and whether the + * image should be completely decoded, or just is size returned. + * @return The decoded bitmap, or null if the image data could not be + * decoded, or, if opts is non-null, if opts requested only the + * size be returned (in opts.outWidth and opts.outHeight) + */ + public static Bitmap decodeFile(String pathName, Options opts) { + Bitmap bm = null; + InputStream stream = null; + try { + stream = new FileInputStream(pathName); + bm = decodeStream(stream, null, opts); + } catch (Exception e) { + /* do nothing. + If the exception happened on open, bm will be null. + */ + } finally { + if (stream != null) { + try { + stream.close(); + } catch (IOException e) { + // do nothing here + } + } + } + return bm; + } + + /** + * Decode a file path into a bitmap. If the specified file name is null, + * or cannot be decoded into a bitmap, the function returns null. + * + * @param pathName complete path name for the file to be decoded. + * @return the resulting decoded bitmap, or null if it could not be decoded. + */ + public static Bitmap decodeFile(String pathName) { + return decodeFile(pathName, null); + } + + /** + * Decode a new Bitmap from an InputStream. This InputStream was obtained from + * resources, which we pass to be able to scale the bitmap accordingly. + */ + public static Bitmap decodeResourceStream(Resources res, TypedValue value, + InputStream is, Rect pad, Options opts) { + + if (opts == null) { + opts = new Options(); + } + + if (opts.inDensity == 0 && value != null) { + final int density = value.density; + if (density == TypedValue.DENSITY_DEFAULT) { + opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; + } else if (density != TypedValue.DENSITY_NONE) { + opts.inDensity = density; + } + } + + if (opts.inTargetDensity == 0 && res != null) { + opts.inTargetDensity = res.getDisplayMetrics().densityDpi; + } + + return decodeStream(is, pad, opts); + } + + /** + * Synonym for opening the given resource and calling + * {@link #decodeResourceStream}. + * + * @param res The resources object containing the image data + * @param id The resource id of the image data + * @param opts null-ok; Options that control downsampling and whether the + * image should be completely decoded, or just is size returned. + * @return The decoded bitmap, or null if the image data could not be + * decoded, or, if opts is non-null, if opts requested only the + * size be returned (in opts.outWidth and opts.outHeight) + */ + public static Bitmap decodeResource(Resources res, int id, Options opts) { + Bitmap bm = null; + InputStream is = null; + + try { + final TypedValue value = new TypedValue(); + is = res.openRawResource(id, value); + + bm = decodeResourceStream(res, value, is, null, opts); + } catch (Exception e) { + /* do nothing. + If the exception happened on open, bm will be null. + If it happened on close, bm is still valid. + */ + } finally { + try { + if (is != null) is.close(); + } catch (IOException e) { + // Ignore + } + } + + return bm; + } + + /** + * Synonym for {@link #decodeResource(Resources, int, android.graphics.BitmapFactory.Options)} + * will null Options. + * + * @param res The resources object containing the image data + * @param id The resource id of the image data + * @return The decoded bitmap, or null if the image could not be decode. + */ + public static Bitmap decodeResource(Resources res, int id) { + return decodeResource(res, id, null); + } + + /** + * Decode an immutable bitmap from the specified byte array. + * + * @param data byte array of compressed image data + * @param offset offset into imageData for where the decoder should begin + * parsing. + * @param length the number of bytes, beginning at offset, to parse + * @param opts null-ok; Options that control downsampling and whether the + * image should be completely decoded, or just is size returned. + * @return The decoded bitmap, or null if the image data could not be + * decoded, or, if opts is non-null, if opts requested only the + * size be returned (in opts.outWidth and opts.outHeight) + */ + public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts) { + if ((offset | length) < 0 || data.length < offset + length) { + throw new ArrayIndexOutOfBoundsException(); + } + + // FIXME: implement as needed, but it's unlikely that this is needed in the context of the bridge. + return null; + //return nativeDecodeByteArray(data, offset, length, opts); + } + + /** + * Decode an immutable bitmap from the specified byte array. + * + * @param data byte array of compressed image data + * @param offset offset into imageData for where the decoder should begin + * parsing. + * @param length the number of bytes, beginning at offset, to parse + * @return The decoded bitmap, or null if the image could not be decode. + */ + public static Bitmap decodeByteArray(byte[] data, int offset, int length) { + return decodeByteArray(data, offset, length, null); + } + + /** + * Decode an input stream into a bitmap. If the input stream is null, or + * cannot be used to decode a bitmap, the function returns null. + * The stream's position will be where ever it was after the encoded data + * was read. + * + * @param is The input stream that holds the raw data to be decoded into a + * bitmap. + * @param outPadding If not null, return the padding rect for the bitmap if + * it exists, otherwise set padding to [-1,-1,-1,-1]. If + * no bitmap is returned (null) then padding is + * unchanged. + * @param opts null-ok; Options that control downsampling and whether the + * image should be completely decoded, or just is size returned. + * @return The decoded bitmap, or null if the image data could not be + * decoded, or, if opts is non-null, if opts requested only the + * size be returned (in opts.outWidth and opts.outHeight) + */ + public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { + // we don't throw in this case, thus allowing the caller to only check + // the cache, and not force the image to be decoded. + if (is == null) { + return null; + } + + // we need mark/reset to work properly + + if (!is.markSupported()) { + is = new BufferedInputStream(is, 16 * 1024); + } + + // so we can call reset() if a given codec gives up after reading up to + // this many bytes. FIXME: need to find out from the codecs what this + // value should be. + is.mark(1024); + + Bitmap bm; + + if (is instanceof AssetManager.AssetInputStream) { + // FIXME: log this. + return null; + } else { + // pass some temp storage down to the native code. 1024 is made up, + // but should be large enough to avoid too many small calls back + // into is.read(...) This number is not related to the value passed + // to mark(...) above. + try { + bm = new Bitmap(is); + } catch (IOException e) { + return null; + } + } + + return finishDecode(bm, outPadding, opts); + } + + private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) { + if (bm == null || opts == null) { + return bm; + } + + final int density = opts.inDensity; + if (density == 0) { + return bm; + } + + bm.setDensity(density); + final int targetDensity = opts.inTargetDensity; + if (targetDensity == 0 || density == targetDensity + || density == opts.inScreenDensity) { + return bm; + } + + byte[] np = bm.getNinePatchChunk(); + final boolean isNinePatch = false; //np != null && NinePatch.isNinePatchChunk(np); + if (opts.inScaled || isNinePatch) { + float scale = targetDensity / (float)density; + // TODO: This is very inefficient and should be done in native by Skia + final Bitmap oldBitmap = bm; + bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f), + (int) (bm.getHeight() * scale + 0.5f), true); + oldBitmap.recycle(); + + if (isNinePatch) { + //np = nativeScaleNinePatch(np, scale, outPadding); + bm.setNinePatchChunk(np); + } + bm.setDensity(targetDensity); + } + + return bm; + } + + /** + * Decode an input stream into a bitmap. If the input stream is null, or + * cannot be used to decode a bitmap, the function returns null. + * The stream's position will be where ever it was after the encoded data + * was read. + * + * @param is The input stream that holds the raw data to be decoded into a + * bitmap. + * @return The decoded bitmap, or null if the image data could not be + * decoded, or, if opts is non-null, if opts requested only the + * size be returned (in opts.outWidth and opts.outHeight) + */ + public static Bitmap decodeStream(InputStream is) { + return decodeStream(is, null, null); + } + + /** + * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded + * return null. The position within the descriptor will not be changed when + * this returns, so the descriptor can be used again as-is. + * + * @param fd The file descriptor containing the bitmap data to decode + * @param outPadding If not null, return the padding rect for the bitmap if + * it exists, otherwise set padding to [-1,-1,-1,-1]. If + * no bitmap is returned (null) then padding is + * unchanged. + * @param opts null-ok; Options that control downsampling and whether the + * image should be completely decoded, or just is size returned. + * @return the decoded bitmap, or null + */ + public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) { + return null; + + /* FIXME: implement as needed + try { + if (MemoryFile.isMemoryFile(fd)) { + int mappedlength = MemoryFile.getMappedSize(fd); + MemoryFile file = new MemoryFile(fd, mappedlength, "r"); + InputStream is = file.getInputStream(); + Bitmap bm = decodeStream(is, outPadding, opts); + return finishDecode(bm, outPadding, opts); + } + } catch (IOException ex) { + // invalid filedescriptor, no need to call nativeDecodeFileDescriptor() + return null; + } + //Bitmap bm = nativeDecodeFileDescriptor(fd, outPadding, opts); + //return finishDecode(bm, outPadding, opts); + */ + } + + /** + * Decode a bitmap from the file descriptor. If the bitmap cannot be decoded + * return null. The position within the descriptor will not be changed when + * this returns, so the descriptor can be used again as is. + * + * @param fd The file descriptor containing the bitmap data to decode + * @return the decoded bitmap, or null + */ + public static Bitmap decodeFileDescriptor(FileDescriptor fd) { + return decodeFileDescriptor(fd, null, null); + } +} + diff --git a/tools/layoutlib/bridge/src/android/graphics/Canvas.java b/tools/layoutlib/bridge/src/android/graphics/Canvas.java index 4986c77..02e3220 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Canvas.java +++ b/tools/layoutlib/bridge/src/android/graphics/Canvas.java @@ -236,10 +236,15 @@ public class Canvas extends _Original_Canvas { */ @Override public int save() { + // get the current save count + int count = mGraphicsStack.size(); + + // create a new graphics and add it to the stack Graphics2D g = (Graphics2D)getGraphics2d().create(); mGraphicsStack.push(g); - return mGraphicsStack.size() - 1; + // return the old save count + return count; } /* (non-Javadoc) @@ -274,10 +279,9 @@ public class Canvas extends _Original_Canvas { */ @Override public int getSaveCount() { - return mGraphicsStack.size() - 1; + return mGraphicsStack.size(); } - /* (non-Javadoc) * @see android.graphics.Canvas#clipRect(float, float, float, float, android.graphics.Region.Op) */ @@ -953,10 +957,6 @@ public class Canvas extends _Original_Canvas { */ @Override public void setMatrix(Matrix matrix) { - // since SetMatrix *replaces* all the other transformation, we have to restore/save - restore(); - save(); - // get the new current graphics Graphics2D g = getGraphics2d(); @@ -968,6 +968,27 @@ public class Canvas extends _Original_Canvas { } } + /* (non-Javadoc) + * @see android.graphics.Canvas#concat(android.graphics.Matrix) + */ + @Override + public void concat(Matrix matrix) { + // get the current top graphics2D object. + Graphics2D g = getGraphics2d(); + + // get its current matrix + AffineTransform currentTx = g.getTransform(); + // get the AffineTransform of the given matrix + AffineTransform matrixTx = matrix.getTransform(); + + // combine them so that the given matrix is applied after. + currentTx.preConcatenate(matrixTx); + + // give it to the graphics2D as a new matrix replacing all previous transform + g.setTransform(currentTx); + } + + // -------------------- /* (non-Javadoc) @@ -1008,15 +1029,6 @@ public class Canvas extends _Original_Canvas { } /* (non-Javadoc) - * @see android.graphics.Canvas#concat(android.graphics.Matrix) - */ - @Override - public void concat(Matrix matrix) { - // TODO Auto-generated method stub - super.concat(matrix); - } - - /* (non-Javadoc) * @see android.graphics.Canvas#drawArc(android.graphics.RectF, float, float, boolean, android.graphics.Paint) */ @Override diff --git a/tools/layoutlib/bridge/src/android/graphics/Matrix.java b/tools/layoutlib/bridge/src/android/graphics/Matrix.java index 3974e08..522415c 100644 --- a/tools/layoutlib/bridge/src/android/graphics/Matrix.java +++ b/tools/layoutlib/bridge/src/android/graphics/Matrix.java @@ -87,8 +87,12 @@ public class Matrix extends _Original_Matrix { } public AffineTransform getTransform() { - return new AffineTransform(mValues[0], mValues[1], mValues[2], - mValues[3], mValues[4], mValues[5]); + // the AffineTransform constructor takes the value in a different order + // for a matrix [ 0 1 2 ] + // [ 3 4 5 ] + // the order is 0, 3, 1, 4, 2, 5... + return new AffineTransform(mValues[0], mValues[3], mValues[1], + mValues[4], mValues[2], mValues[5]); } public boolean hasPerspective() { diff --git a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java index 5a13b0b..2623570 100644 --- a/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java +++ b/tools/layoutlib/create/src/com/android/tools/layoutlib/create/CreateInfo.java @@ -43,6 +43,7 @@ public class CreateInfo { public final static String[] RENAMED_CLASSES = new String[] { "android.graphics.Bitmap", "android.graphics._Original_Bitmap", + "android.graphics.BitmapFactory", "android.graphics._Original_BitmapFactory", "android.graphics.BitmapShader", "android.graphics._Original_BitmapShader", "android.graphics.Canvas", "android.graphics._Original_Canvas", "android.graphics.ComposeShader", "android.graphics._Original_ComposeShader", |
