summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/ActivityManagerNative.java21
-rw-r--r--core/java/android/app/ActivityThread.java366
-rw-r--r--core/java/android/app/Dialog.java31
-rw-r--r--core/java/android/app/Fragment.java42
-rw-r--r--core/java/android/app/FragmentManager.java70
-rw-r--r--core/java/android/app/IActivityManager.java2
-rw-r--r--core/java/android/app/LoadedApk.java7
-rw-r--r--core/java/android/app/LoaderManager.java17
-rw-r--r--core/java/android/app/SearchManager.java22
-rw-r--r--core/java/android/app/WallpaperManager.java36
-rw-r--r--core/java/android/appwidget/AppWidgetHostView.java55
-rw-r--r--core/java/android/bluetooth/BluetoothDevice.java38
-rw-r--r--core/java/android/bluetooth/BluetoothDeviceProfileState.java2
-rw-r--r--core/java/android/bluetooth/IBluetooth.aidl2
-rw-r--r--core/java/android/content/ContentResolver.java3
-rw-r--r--core/java/android/content/Context.java9
-rw-r--r--core/java/android/content/Loader.java26
-rw-r--r--core/java/android/content/SyncManager.java6
-rw-r--r--core/java/android/content/pm/PackageManager.java12
-rw-r--r--core/java/android/content/res/XmlBlock.java6
-rw-r--r--core/java/android/database/AbstractCursor.java46
-rw-r--r--core/java/android/database/AbstractWindowedCursor.java7
-rw-r--r--core/java/android/database/CrossProcessCursor.java52
-rw-r--r--core/java/android/database/CrossProcessCursorWrapper.java75
-rw-r--r--core/java/android/database/CursorToBulkCursorAdaptor.java71
-rw-r--r--core/java/android/database/CursorWindow.java80
-rw-r--r--core/java/android/database/CursorWrapper.java8
-rw-r--r--core/java/android/database/DatabaseUtils.java79
-rw-r--r--core/java/android/database/sqlite/SQLiteCompiledSql.java10
-rw-r--r--core/java/android/database/sqlite/SQLiteCursor.java10
-rw-r--r--core/java/android/database/sqlite/package.html2
-rw-r--r--core/java/android/hardware/Camera.java255
-rw-r--r--core/java/android/inputmethodservice/KeyboardView.java35
-rw-r--r--core/java/android/inputmethodservice/SoftInputWindow.java6
-rw-r--r--core/java/android/net/NetworkStats.java146
-rw-r--r--core/java/android/net/TrafficStats.java16
-rw-r--r--core/java/android/net/wimax/WimaxManagerConstants.java104
-rw-r--r--core/java/android/nfc/INfcAdapter.aidl2
-rw-r--r--core/java/android/nfc/INfcAdapterExtras.aidl12
-rw-r--r--core/java/android/nfc/NfcAdapter.java82
-rw-r--r--core/java/android/nfc/NfcManager.java3
-rw-r--r--core/java/android/os/Build.java7
-rw-r--r--core/java/android/os/CountDownTimer.java2
-rw-r--r--core/java/android/os/RemoteException.java4
-rw-r--r--core/java/android/os/StrictMode.java29
-rw-r--r--core/java/android/os/TransactionTooLargeException.java59
-rw-r--r--core/java/android/provider/CalendarContract.java162
-rw-r--r--core/java/android/provider/ContactsContract.java23
-rw-r--r--core/java/android/provider/Settings.java9
-rwxr-xr-xcore/java/android/provider/Telephony.java3
-rw-r--r--core/java/android/server/BluetoothEventLoop.java3
-rwxr-xr-xcore/java/android/server/BluetoothService.java65
-rw-r--r--core/java/android/service/textservice/SpellCheckerService.java16
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java29
-rw-r--r--core/java/android/speech/tts/AudioMessageParams.java4
-rw-r--r--core/java/android/speech/tts/AudioPlaybackHandler.java10
-rw-r--r--core/java/android/speech/tts/FileSynthesisCallback.java20
-rwxr-xr-xcore/java/android/speech/tts/ITextToSpeechCallback.aidl4
-rw-r--r--core/java/android/speech/tts/ITextToSpeechService.aidl15
-rw-r--r--core/java/android/speech/tts/MessageParams.java8
-rw-r--r--core/java/android/speech/tts/PlaybackSynthesisCallback.java6
-rw-r--r--core/java/android/speech/tts/SilenceMessageParams.java4
-rw-r--r--core/java/android/speech/tts/SynthesisMessageParams.java4
-rwxr-xr-xcore/java/android/speech/tts/TextToSpeech.java121
-rw-r--r--core/java/android/speech/tts/TextToSpeechService.java101
-rw-r--r--core/java/android/speech/tts/UtteranceProgressListener.java68
-rw-r--r--core/java/android/text/DynamicLayout.java6
-rw-r--r--core/java/android/text/StaticLayout.java144
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java12
-rw-r--r--core/java/android/text/style/SuggestionSpan.java1
-rw-r--r--core/java/android/view/GLES20Canvas.java21
-rw-r--r--core/java/android/view/HardwareRenderer.java122
-rw-r--r--core/java/android/view/IApplicationToken.aidl1
-rwxr-xr-xcore/java/android/view/InputDevice.java10
-rw-r--r--core/java/android/view/KeyCharacterMap.java15
-rw-r--r--core/java/android/view/View.java275
-rw-r--r--core/java/android/view/ViewGroup.java17
-rw-r--r--core/java/android/view/ViewRootImpl.java157
-rw-r--r--core/java/android/view/WindowManager.java11
-rw-r--r--core/java/android/view/WindowManagerImpl.java28
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java2
-rw-r--r--core/java/android/view/accessibility/AccessibilityRecord.java8
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java14
-rw-r--r--core/java/android/view/textservice/SpellCheckerSession.java70
-rw-r--r--core/java/android/view/textservice/SuggestionsInfo.java6
-rw-r--r--core/java/android/webkit/BrowserFrame.java20
-rw-r--r--core/java/android/webkit/FindActionModeCallback.java15
-rw-r--r--core/java/android/webkit/HTML5VideoFullScreen.java4
-rw-r--r--core/java/android/webkit/HTML5VideoInline.java10
-rw-r--r--core/java/android/webkit/JWebCoreJavaBridge.java2
-rw-r--r--core/java/android/webkit/JniUtil.java8
-rw-r--r--core/java/android/webkit/LoadListener.java4
-rw-r--r--core/java/android/webkit/PerfChecker.java49
-rw-r--r--core/java/android/webkit/WebChromeClient.java46
-rw-r--r--core/java/android/webkit/WebSettings.java14
-rw-r--r--core/java/android/webkit/WebTextView.java75
-rw-r--r--core/java/android/webkit/WebView.java347
-rw-r--r--core/java/android/webkit/WebViewCore.java37
-rw-r--r--core/java/android/webkit/WebViewDatabase.java147
-rw-r--r--core/java/android/webkit/ZoomManager.java35
-rw-r--r--core/java/android/widget/CalendarView.java26
-rw-r--r--core/java/android/widget/EdgeEffect.java2
-rw-r--r--core/java/android/widget/GridLayout.java14
-rw-r--r--core/java/android/widget/HorizontalScrollView.java5
-rw-r--r--core/java/android/widget/ImageView.java25
-rw-r--r--core/java/android/widget/ListView.java4
-rw-r--r--core/java/android/widget/NumberPicker.java176
-rw-r--r--core/java/android/widget/RemoteViews.java10
-rw-r--r--core/java/android/widget/SearchView.java24
-rw-r--r--core/java/android/widget/SpellChecker.java76
-rw-r--r--core/java/android/widget/Spinner.java18
-rw-r--r--core/java/android/widget/SuggestionsAdapter.java8
-rw-r--r--core/java/android/widget/TextView.java315
-rw-r--r--core/java/android/widget/ZoomButton.java4
-rw-r--r--core/java/com/android/internal/net/NetworkStatsFactory.java66
-rw-r--r--core/java/com/android/internal/textservice/ISpellCheckerSession.aidl1
-rw-r--r--core/java/com/android/internal/util/ProcFileReader.java199
-rw-r--r--core/java/com/android/internal/view/menu/ActionMenuItemView.java26
-rw-r--r--core/java/com/android/internal/view/menu/BaseMenuPresenter.java7
-rw-r--r--core/java/com/android/internal/view/menu/ExpandedMenuView.java5
-rw-r--r--core/java/com/android/internal/view/menu/ListMenuItemView.java21
-rw-r--r--core/java/com/android/internal/widget/ActionBarView.java3
-rw-r--r--core/java/com/android/internal/widget/DigitalClock.java5
-rw-r--r--core/java/com/android/internal/widget/LockPatternUtils.java17
-rw-r--r--core/java/com/android/internal/widget/LockPatternView.java2
-rw-r--r--core/java/com/android/internal/widget/WaveView.java90
126 files changed, 4036 insertions, 1496 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index b4471f0..7994d7c 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1092,6 +1092,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
reply.writeNoException();
return true;
}
+
+ case KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ killAllBackgroundProcesses();
+ reply.writeNoException();
+ return true;
+ }
case FORCE_STOP_PACKAGE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
@@ -2906,7 +2913,7 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
-
+
public void killBackgroundProcesses(String packageName) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
@@ -2917,7 +2924,17 @@ class ActivityManagerProxy implements IActivityManager
data.recycle();
reply.recycle();
}
-
+
+ public void killAllBackgroundProcesses() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ data.recycle();
+ reply.recycle();
+ }
+
public void forceStopPackage(String packageName) throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8afe9bf..303f81b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -45,6 +45,7 @@ import android.graphics.Canvas;
import android.net.IConnectivityManager;
import android.net.Proxy;
import android.net.ProxyProperties;
+import android.opengl.GLUtils;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Debug;
@@ -2733,8 +2734,9 @@ public final class ActivityThread {
CharSequence description;
}
- private class ProviderRefCount {
+ private static final class ProviderRefCount {
public int count;
+
ProviderRefCount(int pCount) {
count = pCount;
}
@@ -2769,7 +2771,9 @@ public final class ActivityThread {
if (info != null) {
try {
// First create a thumbnail for the activity...
- info.thumbnail = createThumbnailBitmap(r);
+ // For now, don't create the thumbnail here; we are
+ // doing that by doing a screen snapshot.
+ info.thumbnail = null; //createThumbnailBitmap(r);
info.description = r.activity.onCreateDescription();
} catch (Exception e) {
if (!mInstrumentation.onException(r.activity, e)) {
@@ -3712,6 +3716,24 @@ public final class ActivityThread {
}
}
+ private void setupGraphicsSupport(LoadedApk info) {
+ try {
+ int uid = Process.myUid();
+ String[] packages = getPackageManager().getPackagesForUid(uid);
+
+ // If there are several packages in this application we won't
+ // initialize the graphics disk caches
+ if (packages.length == 1) {
+ ContextImpl appContext = new ContextImpl();
+ appContext.init(info, null, this);
+
+ HardwareRenderer.setupDiskCache(appContext.getCacheDir());
+ }
+ } catch (RemoteException e) {
+ // Ignore
+ }
+ }
+
private void handleBindApplication(AppBindData data) {
mBoundApplication = data;
mConfiguration = new Configuration(data.config);
@@ -3735,7 +3757,7 @@ public final class ActivityThread {
HardwareRenderer.disable(false);
}
}
-
+
if (mProfiler.profileFd != null) {
mProfiler.startProfiling();
}
@@ -3771,6 +3793,8 @@ public final class ActivityThread {
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+ setupGraphicsSupport(data.info);
+
/**
* For system applications on userdebug/eng builds, log stack
* traces of disk and network access to dropbox for analysis.
@@ -3830,11 +3854,16 @@ public final class ActivityThread {
* Initialize the default http proxy in this process for the reasons we set the time zone.
*/
IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
- IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
- try {
- ProxyProperties proxyProperties = service.getProxy();
- Proxy.setHttpProxySystemProperty(proxyProperties);
- } catch (RemoteException e) {}
+ if (b != null) {
+ // In pre-boot mode (doing initial launch to collect password), not
+ // all system is up. This includes the connectivity service, so don't
+ // crash if we can't get it.
+ IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+ try {
+ ProxyProperties proxyProperties = service.getProxy();
+ Proxy.setHttpProxySystemProperty(proxyProperties);
+ } catch (RemoteException e) {}
+ }
if (data.instrumentationName != null) {
ContextImpl appContext = new ContextImpl();
@@ -3960,16 +3989,14 @@ public final class ActivityThread {
buf.append(": ");
buf.append(cpi.name);
Log.i(TAG, buf.toString());
- IContentProvider cp = installProvider(context, null, cpi, false);
+ IContentProvider cp = installProvider(context, null, cpi,
+ false /*noisy*/, true /*noReleaseNeeded*/);
if (cp != null) {
IActivityManager.ContentProviderHolder cph =
- new IActivityManager.ContentProviderHolder(cpi);
+ new IActivityManager.ContentProviderHolder(cpi);
cph.provider = cp;
+ cph.noReleaseNeeded = true;
results.add(cph);
- // Don't ever unload this provider from the process.
- synchronized(mProviderMap) {
- mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000));
- }
}
}
@@ -3980,26 +4007,22 @@ public final class ActivityThread {
}
}
- private IContentProvider getExistingProvider(Context context, String name) {
- synchronized(mProviderMap) {
- final ProviderClientRecord pr = mProviderMap.get(name);
- if (pr != null) {
- return pr.mProvider;
- }
- return null;
- }
- }
-
- private IContentProvider getProvider(Context context, String name) {
- IContentProvider existing = getExistingProvider(context, name);
- if (existing != null) {
- return existing;
+ public final IContentProvider acquireProvider(Context c, String name) {
+ IContentProvider provider = acquireExistingProvider(c, name);
+ if (provider != null) {
+ return provider;
}
+ // There is a possible race here. Another thread may try to acquire
+ // the same provider at the same time. When this happens, we want to ensure
+ // that the first one wins.
+ // Note that we cannot hold the lock while acquiring and installing the
+ // provider since it might take a long time to run and it could also potentially
+ // be re-entrant in the case where the provider is in the same process.
IActivityManager.ContentProviderHolder holder = null;
try {
holder = ActivityManagerNative.getDefault().getContentProvider(
- getApplicationThread(), name);
+ getApplicationThread(), name);
} catch (RemoteException ex) {
}
if (holder == null) {
@@ -4007,135 +4030,136 @@ public final class ActivityThread {
return null;
}
- IContentProvider prov = installProvider(context, holder.provider,
- holder.info, true);
- //Slog.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded);
- if (holder.noReleaseNeeded || holder.provider == null) {
- // We are not going to release the provider if it is an external
- // provider that doesn't care about being released, or if it is
- // a local provider running in this process.
- //Slog.i(TAG, "*** NO RELEASE NEEDED");
- synchronized(mProviderMap) {
- mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000));
+ // Install provider will increment the reference count for us, and break
+ // any ties in the race.
+ provider = installProvider(c, holder.provider, holder.info,
+ true /*noisy*/, holder.noReleaseNeeded);
+ if (holder.provider != null && provider != holder.provider) {
+ if (localLOGV) {
+ Slog.v(TAG, "acquireProvider: lost the race, releasing extraneous "
+ + "reference to the content provider");
+ }
+ try {
+ ActivityManagerNative.getDefault().removeContentProvider(
+ getApplicationThread(), name);
+ } catch (RemoteException ex) {
}
}
- return prov;
- }
-
- public final IContentProvider acquireProvider(Context c, String name) {
- IContentProvider provider = getProvider(c, name);
- if(provider == null)
- return null;
- IBinder jBinder = provider.asBinder();
- synchronized(mProviderMap) {
- ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
- if(prc == null) {
- mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
- } else {
- prc.count++;
- } //end else
- } //end synchronized
return provider;
}
public final IContentProvider acquireExistingProvider(Context c, String name) {
- IContentProvider provider = getExistingProvider(c, name);
- if(provider == null)
- return null;
- IBinder jBinder = provider.asBinder();
- synchronized(mProviderMap) {
+ synchronized (mProviderMap) {
+ ProviderClientRecord pr = mProviderMap.get(name);
+ if (pr == null) {
+ return null;
+ }
+
+ IContentProvider provider = pr.mProvider;
+ IBinder jBinder = provider.asBinder();
+
+ // Only increment the ref count if we have one. If we don't then the
+ // provider is not reference counted and never needs to be released.
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
- if(prc == null) {
- mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
- } else {
- prc.count++;
- } //end else
- } //end synchronized
- return provider;
+ if (prc != null) {
+ prc.count += 1;
+ if (prc.count == 1) {
+ if (localLOGV) {
+ Slog.v(TAG, "acquireExistingProvider: "
+ + "snatched provider from the jaws of death");
+ }
+ // Because the provider previously had a reference count of zero,
+ // it was scheduled to be removed. Cancel that.
+ mH.removeMessages(H.REMOVE_PROVIDER, provider);
+ }
+ }
+ return provider;
+ }
}
public final boolean releaseProvider(IContentProvider provider) {
if(provider == null) {
return false;
}
+
IBinder jBinder = provider.asBinder();
- synchronized(mProviderMap) {
+ synchronized (mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
- if(prc == null) {
- if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldn't be here");
+ if (prc == null) {
+ // The provider has no ref count, no release is needed.
return false;
- } else {
- prc.count--;
- if(prc.count == 0) {
- // Schedule the actual remove asynchronously, since we
- // don't know the context this will be called in.
- // TODO: it would be nice to post a delayed message, so
- // if we come back and need the same provider quickly
- // we will still have it available.
- Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider);
- mH.sendMessage(msg);
- } //end if
- } //end else
- } //end synchronized
- return true;
+ }
+
+ if (prc.count == 0) {
+ if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?");
+ return false;
+ }
+
+ prc.count -= 1;
+ if (prc.count == 0) {
+ // Schedule the actual remove asynchronously, since we don't know the context
+ // this will be called in.
+ // TODO: it would be nice to post a delayed message, so
+ // if we come back and need the same provider quickly
+ // we will still have it available.
+ Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider);
+ mH.sendMessage(msg);
+ }
+ return true;
+ }
}
final void completeRemoveProvider(IContentProvider provider) {
IBinder jBinder = provider.asBinder();
- String name = null;
+ String remoteProviderName = null;
synchronized(mProviderMap) {
ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
- if(prc != null && prc.count == 0) {
- mProviderRefCountMap.remove(jBinder);
- //invoke removeProvider to dereference provider
- name = removeProviderLocked(provider);
+ if (prc == null) {
+ // Either no release is needed (so we shouldn't be here) or the
+ // provider was already released.
+ if (localLOGV) Slog.v(TAG, "completeRemoveProvider: release not needed");
+ return;
+ }
+
+ if (prc.count != 0) {
+ // There was a race! Some other client managed to acquire
+ // the provider before the removal was completed.
+ // Abort the removal. We will do it later.
+ if (localLOGV) Slog.v(TAG, "completeRemoveProvider: lost the race, "
+ + "provider still in use");
+ return;
+ }
+
+ mProviderRefCountMap.remove(jBinder);
+
+ Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
+ while (iter.hasNext()) {
+ ProviderClientRecord pr = iter.next();
+ IBinder myBinder = pr.mProvider.asBinder();
+ if (myBinder == jBinder) {
+ iter.remove();
+ if (pr.mLocalProvider == null) {
+ myBinder.unlinkToDeath(pr, 0);
+ if (remoteProviderName == null) {
+ remoteProviderName = pr.mName;
+ }
+ }
+ }
}
}
-
- if (name != null) {
+
+ if (remoteProviderName != null) {
try {
- if(localLOGV) Slog.v(TAG, "removeProvider::Invoking " +
- "ActivityManagerNative.removeContentProvider(" + name);
+ if (localLOGV) {
+ Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative."
+ + "removeContentProvider(" + remoteProviderName + ")");
+ }
ActivityManagerNative.getDefault().removeContentProvider(
- getApplicationThread(), name);
+ getApplicationThread(), remoteProviderName);
} catch (RemoteException e) {
//do nothing content provider object is dead any way
- } //end catch
- }
- }
-
- public final String removeProviderLocked(IContentProvider provider) {
- if (provider == null) {
- return null;
+ }
}
- IBinder providerBinder = provider.asBinder();
-
- String name = null;
-
- // remove the provider from mProviderMap
- Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator();
- while (iter.hasNext()) {
- ProviderClientRecord pr = iter.next();
- IBinder myBinder = pr.mProvider.asBinder();
- if (myBinder == providerBinder) {
- //find if its published by this process itself
- if(pr.mLocalProvider != null) {
- if(localLOGV) Slog.i(TAG, "removeProvider::found local provider returning");
- return name;
- }
- if(localLOGV) Slog.v(TAG, "removeProvider::Not local provider Unlinking " +
- "death recipient");
- //content provider is in another process
- myBinder.unlinkToDeath(pr, 0);
- iter.remove();
- //invoke remove only once for the very first name seen
- if(name == null) {
- name = pr.mName;
- }
- } //end if myBinder
- } //end while iter
-
- return name;
}
final void removeDeadProvider(String name, IContentProvider provider) {
@@ -4151,8 +4175,23 @@ public final class ActivityThread {
}
}
+ /**
+ * Installs the provider.
+ *
+ * Providers that are local to the process or that come from the system server
+ * may be installed permanently which is indicated by setting noReleaseNeeded to true.
+ * Other remote providers are reference counted. The initial reference count
+ * for all reference counted providers is one. Providers that are not reference
+ * counted do not have a reference count (at all).
+ *
+ * This method detects when a provider has already been installed. When this happens,
+ * it increments the reference count of the existing provider (if appropriate)
+ * and returns the existing provider. This can happen due to concurrent
+ * attempts to acquire the same provider.
+ */
private IContentProvider installProvider(Context context,
- IContentProvider provider, ProviderInfo info, boolean noisy) {
+ IContentProvider provider, ProviderInfo info,
+ boolean noisy, boolean noReleaseNeeded) {
ContentProvider localProvider = null;
if (provider == null) {
if (noisy) {
@@ -4210,24 +4249,69 @@ public final class ActivityThread {
}
synchronized (mProviderMap) {
- // Cache the pointer for the remote provider.
+ // There is a possibility that this thread raced with another thread to
+ // add the provider. If we find another thread got there first then we
+ // just get out of the way and return the original provider.
+ IBinder jBinder = provider.asBinder();
String names[] = PATTERN_SEMICOLON.split(info.authority);
- for (int i=0; i<names.length; i++) {
- ProviderClientRecord pr = new ProviderClientRecord(names[i], provider,
- localProvider);
- try {
- provider.asBinder().linkToDeath(pr, 0);
+ for (int i = 0; i < names.length; i++) {
+ ProviderClientRecord pr = mProviderMap.get(names[i]);
+ if (pr != null) {
+ if (localLOGV) {
+ Slog.v(TAG, "installProvider: lost the race, "
+ + "using existing named provider");
+ }
+ provider = pr.mProvider;
+ } else {
+ pr = new ProviderClientRecord(names[i], provider, localProvider);
+ if (localProvider == null) {
+ try {
+ jBinder.linkToDeath(pr, 0);
+ } catch (RemoteException e) {
+ // Provider already dead. Bail out of here without making
+ // any changes to the provider map or other data structures.
+ return null;
+ }
+ }
mProviderMap.put(names[i], pr);
- } catch (RemoteException e) {
- return null;
}
}
+
if (localProvider != null) {
- mLocalProviders.put(provider.asBinder(),
- new ProviderClientRecord(null, provider, localProvider));
+ ProviderClientRecord pr = mLocalProviders.get(jBinder);
+ if (pr != null) {
+ if (localLOGV) {
+ Slog.v(TAG, "installProvider: lost the race, "
+ + "using existing local provider");
+ }
+ provider = pr.mProvider;
+ } else {
+ pr = new ProviderClientRecord(null, provider, localProvider);
+ mLocalProviders.put(jBinder, pr);
+ }
}
- }
+ if (!noReleaseNeeded) {
+ ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+ if (prc != null) {
+ if (localLOGV) {
+ Slog.v(TAG, "installProvider: lost the race, incrementing ref count");
+ }
+ prc.count += 1;
+ if (prc.count == 1) {
+ if (localLOGV) {
+ Slog.v(TAG, "installProvider: "
+ + "snatched provider from the jaws of death");
+ }
+ // Because the provider previously had a reference count of zero,
+ // it was scheduled to be removed. Cancel that.
+ mH.removeMessages(H.REMOVE_PROVIDER, provider);
+ }
+ } else {
+ mProviderRefCountMap.put(jBinder, new ProviderRefCount(1));
+ }
+ }
+ }
return provider;
}
diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java
index 7a69419..f1ce2bb 100644
--- a/core/java/android/app/Dialog.java
+++ b/core/java/android/app/Dialog.java
@@ -28,6 +28,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ContextMenu;
@@ -77,6 +78,7 @@ import java.lang.ref.WeakReference;
*/
public class Dialog implements DialogInterface, Window.Callback,
KeyEvent.Callback, OnCreateContextMenuListener {
+ private static final String TAG = "Dialog";
private Activity mOwnerActivity;
final Context mContext;
@@ -110,6 +112,8 @@ public class Dialog implements DialogInterface, Window.Callback,
private Handler mListenersHandler;
+ private ActionMode mActionMode;
+
private final Runnable mDismissAction = new Runnable() {
public void run() {
dismissDialog();
@@ -298,18 +302,27 @@ public class Dialog implements DialogInterface, Window.Callback,
if (Thread.currentThread() != mUiThread) {
mHandler.post(mDismissAction);
} else {
+ mHandler.removeCallbacks(mDismissAction);
mDismissAction.run();
}
}
- private void dismissDialog() {
+ void dismissDialog() {
if (mDecor == null || !mShowing) {
return;
}
+ if (mWindow.isDestroyed()) {
+ Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!");
+ return;
+ }
+
try {
mWindowManager.removeView(mDecor);
} finally {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ }
mDecor = null;
mWindow.closeAllPanels();
onStop();
@@ -952,10 +965,26 @@ public class Dialog implements DialogInterface, Window.Callback,
return null;
}
+ /**
+ * {@inheritDoc}
+ *
+ * Note that if you override this method you should always call through
+ * to the superclass implementation by calling super.onActionModeStarted(mode).
+ */
public void onActionModeStarted(ActionMode mode) {
+ mActionMode = mode;
}
+ /**
+ * {@inheritDoc}
+ *
+ * Note that if you override this method you should always call through
+ * to the superclass implementation by calling super.onActionModeFinished(mode).
+ */
public void onActionModeFinished(ActionMode mode) {
+ if (mode == mActionMode) {
+ mActionMode = null;
+ }
}
/**
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index d423d98..473a2d1 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -339,6 +339,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
private static final HashMap<String, Class<?>> sClassMap =
new HashMap<String, Class<?>>();
+ static final int INVALID_STATE = -1; // Invalid state used as a null value.
static final int INITIALIZING = 0; // Not yet created.
static final int CREATED = 1; // Created.
static final int ACTIVITY_CREATED = 2; // The activity has finished its creation.
@@ -403,7 +404,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
// The fragment manager we are associated with. Set as soon as the
// fragment is used in a transaction; cleared after it has been removed
// from all transactions.
- FragmentManager mFragmentManager;
+ FragmentManagerImpl mFragmentManager;
// Activity this fragment is attached to.
Activity mActivity;
@@ -453,6 +454,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
// The View generated for this fragment.
View mView;
+ // Whether this fragment should defer starting until after other fragments
+ // have been started and their loaders are finished.
+ boolean mDeferStart;
+
+ // Hint provided by the app that this fragment is currently visible to the user.
+ boolean mUserVisibleHint = true;
+
LoaderManagerImpl mLoaderManager;
boolean mLoadersStarted;
boolean mCheckedForLoaderManager;
@@ -910,6 +918,35 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
}
/**
+ * Set a hint to the system about whether this fragment's UI is currently visible
+ * to the user. This hint defaults to true and is persistent across fragment instance
+ * state save and restore.
+ *
+ * <p>An app may set this to false to indicate that the fragment's UI is
+ * scrolled out of visibility or is otherwise not directly visible to the user.
+ * This may be used by the system to prioritize operations such as fragment lifecycle updates
+ * or loader ordering behavior.</p>
+ *
+ * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
+ * false if it is not.
+ */
+ public void setUserVisibleHint(boolean isVisibleToUser) {
+ if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
+ mFragmentManager.performPendingDeferredStart(this);
+ }
+ mUserVisibleHint = isVisibleToUser;
+ mDeferStart = !isVisibleToUser;
+ }
+
+ /**
+ * @return The current value of the user-visible hint on this fragment.
+ * @see #setUserVisibleHint(boolean)
+ */
+ public boolean getUserVisibleHint() {
+ return mUserVisibleHint;
+ }
+
+ /**
* Return the LoaderManager for this fragment, creating it if needed.
*/
public LoaderManager getLoaderManager() {
@@ -1444,7 +1481,8 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene
writer.print(" mMenuVisible="); writer.print(mMenuVisible);
writer.print(" mHasMenu="); writer.println(mHasMenu);
writer.print(prefix); writer.print("mRetainInstance="); writer.print(mRetainInstance);
- writer.print(" mRetaining="); writer.println(mRetaining);
+ writer.print(" mRetaining="); writer.print(mRetaining);
+ writer.print(" mUserVisibleHint="); writer.println(mUserVisibleHint);
if (mFragmentManager != null) {
writer.print(prefix); writer.print("mFragmentManager=");
writer.println(mFragmentManager);
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 3da4f29..a8c9cba 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -382,6 +382,7 @@ final class FragmentManagerImpl extends FragmentManager {
static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state";
static final String TARGET_STATE_TAG = "android:target_state";
static final String VIEW_STATE_TAG = "android:view_state";
+ static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint";
ArrayList<Runnable> mPendingActions;
Runnable[] mTmpActions;
@@ -406,6 +407,7 @@ final class FragmentManagerImpl extends FragmentManager {
boolean mStateSaved;
boolean mDestroyed;
String mNoTransactionsBecause;
+ boolean mHavePendingDeferredStart;
// Temporary vars for state save and restore.
Bundle mStateBundle = null;
@@ -709,6 +711,18 @@ final class FragmentManagerImpl extends FragmentManager {
return AnimatorInflater.loadAnimator(mActivity, anim);
}
+ public void performPendingDeferredStart(Fragment f) {
+ if (f.mDeferStart) {
+ if (mExecutingActions) {
+ // Wait until we're done executing our pending transactions
+ mHavePendingDeferredStart = true;
+ return;
+ }
+ f.mDeferStart = false;
+ moveToState(f, mCurState, 0, 0);
+ }
+ }
+
void moveToState(Fragment f, int newState, int transit, int transitionStyle) {
// Fragments that are not currently added will sit in the onCreate() state.
if (!f.mAdded && newState > Fragment.CREATED) {
@@ -718,7 +732,11 @@ final class FragmentManagerImpl extends FragmentManager {
// While removing a fragment, we can't change it to a higher state.
newState = f.mState;
}
-
+ // Defer start if requested; don't allow it to move to STARTED or higher
+ // if it's not already started.
+ if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) {
+ newState = Fragment.STOPPED;
+ }
if (f.mState < newState) {
// For fragments that are created from a layout, when restoring from
// state we don't want to allow them to be created until they are
@@ -746,6 +764,14 @@ final class FragmentManagerImpl extends FragmentManager {
f.mTargetRequestCode = f.mSavedFragmentState.getInt(
FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0);
}
+ f.mUserVisibleHint = f.mSavedFragmentState.getBoolean(
+ FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true);
+ if (!f.mUserVisibleHint) {
+ f.mDeferStart = true;
+ if (newState > Fragment.STOPPED) {
+ newState = Fragment.STOPPED;
+ }
+ }
}
f.mActivity = mActivity;
f.mFragmentManager = mActivity.mFragments;
@@ -992,13 +1018,21 @@ final class FragmentManagerImpl extends FragmentManager {
mCurState = newState;
if (mActive != null) {
+ boolean loadersRunning = false;
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null) {
moveToState(f, newState, transit, transitStyle);
+ if (f.mLoaderManager != null) {
+ loadersRunning |= f.mLoaderManager.hasRunningLoaders();
+ }
}
}
+ if (!loadersRunning) {
+ startPendingDeferredFragments();
+ }
+
if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) {
mActivity.invalidateOptionsMenu();
mNeedMenuInvalidate = false;
@@ -1006,6 +1040,17 @@ final class FragmentManagerImpl extends FragmentManager {
}
}
+ void startPendingDeferredFragments() {
+ if (mActive == null) return;
+
+ for (int i=0; i<mActive.size(); i++) {
+ Fragment f = mActive.get(i);
+ if (f != null) {
+ performPendingDeferredStart(f);
+ }
+ }
+ }
+
void makeActive(Fragment f) {
if (f.mIndex >= 0) {
return;
@@ -1313,7 +1358,7 @@ final class FragmentManagerImpl extends FragmentManager {
synchronized (this) {
if (mPendingActions == null || mPendingActions.size() == 0) {
- return didSomething;
+ break;
}
numActions = mPendingActions.size();
@@ -1333,8 +1378,23 @@ final class FragmentManagerImpl extends FragmentManager {
mExecutingActions = false;
didSomething = true;
}
+
+ if (mHavePendingDeferredStart) {
+ boolean loadersRunning = false;
+ for (int i=0; i<mActive.size(); i++) {
+ Fragment f = mActive.get(i);
+ if (f != null && f.mLoaderManager != null) {
+ loadersRunning |= f.mLoaderManager.hasRunningLoaders();
+ }
+ }
+ if (!loadersRunning) {
+ mHavePendingDeferredStart = false;
+ startPendingDeferredFragments();
+ }
+ }
+ return didSomething;
}
-
+
void reportBackStackChanged() {
if (mBackStackChangeListeners != null) {
for (int i=0; i<mBackStackChangeListeners.size(); i++) {
@@ -1470,6 +1530,10 @@ final class FragmentManagerImpl extends FragmentManager {
result.putSparseParcelableArray(
FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState);
}
+ if (!f.mUserVisibleHint) {
+ // Only add this if it's not the default value
+ result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint);
+ }
return result;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 26813bf..5222d37 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -234,6 +234,7 @@ public interface IActivityManager extends IInterface {
public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException;
public void killBackgroundProcesses(final String packageName) throws RemoteException;
+ public void killAllBackgroundProcesses() throws RemoteException;
public void forceStopPackage(final String packageName) throws RemoteException;
// Note: probably don't want to allow applications access to these.
@@ -605,4 +606,5 @@ public interface IActivityManager extends IInterface {
int GET_PROCESS_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+136;
int SHOW_BOOT_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+137;
int DISMISS_KEYGUARD_ON_NEXT_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+138;
+ int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139;
}
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index 522f477..0c6baeb 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -901,6 +901,7 @@ public final class LoadedApk {
private RuntimeException mUnbindLocation;
private boolean mDied;
+ private boolean mForgotten;
private static class ConnectionInfo {
IBinder binder;
@@ -959,6 +960,7 @@ public final class LoadedApk {
ci.binder.unlinkToDeath(ci.deathMonitor, 0);
}
mActiveConnections.clear();
+ mForgotten = true;
}
}
@@ -1020,6 +1022,11 @@ public final class LoadedApk {
ServiceDispatcher.ConnectionInfo info;
synchronized (this) {
+ if (mForgotten) {
+ // We unbound before receiving the connection; ignore
+ // any connection received.
+ return;
+ }
old = mActiveConnections.get(name);
if (old != null && old.binder == service) {
// Huh, already have this one. Oh well!
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 89e9ddd..1b8a4f5 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -418,6 +418,10 @@ class LoaderManagerImpl extends LoaderManager {
info.destroy();
mInactiveLoaders.remove(mId);
}
+
+ if (mActivity != null && !hasRunningLoaders()) {
+ mActivity.mFragments.startPendingDeferredFragments();
+ }
}
void callOnLoadFinished(Loader<Object> loader, Object data) {
@@ -677,6 +681,9 @@ class LoaderManagerImpl extends LoaderManager {
mInactiveLoaders.removeAt(idx);
info.destroy();
}
+ if (mActivity != null && !hasRunningLoaders()) {
+ mActivity.mFragments.startPendingDeferredFragments();
+ }
}
/**
@@ -820,4 +827,14 @@ class LoaderManagerImpl extends LoaderManager {
}
}
}
+
+ public boolean hasRunningLoaders() {
+ boolean loadersRunning = false;
+ final int count = mLoaders.size();
+ for (int i = 0; i < count; i++) {
+ final LoaderInfo li = mLoaders.valueAt(i);
+ loadersRunning |= li.mStarted && !li.mDeliveredData;
+ }
+ return loadersRunning;
+ }
}
diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java
index 3290b9d..3aa159e 100644
--- a/core/java/android/app/SearchManager.java
+++ b/core/java/android/app/SearchManager.java
@@ -24,6 +24,7 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.database.Cursor;
+import android.graphics.Rect;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -498,8 +499,24 @@ public class SearchManager
ComponentName launchActivity,
Bundle appSearchData,
boolean globalSearch) {
+ startSearch(initialQuery, selectInitialQuery, launchActivity,
+ appSearchData, globalSearch, null);
+ }
+
+ /**
+ * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including
+ * source bounds for the global search intent.
+ *
+ * @hide
+ */
+ public void startSearch(String initialQuery,
+ boolean selectInitialQuery,
+ ComponentName launchActivity,
+ Bundle appSearchData,
+ boolean globalSearch,
+ Rect sourceBounds) {
if (globalSearch) {
- startGlobalSearch(initialQuery, selectInitialQuery, appSearchData);
+ startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds);
return;
}
@@ -520,7 +537,7 @@ public class SearchManager
* Starts the global search activity.
*/
/* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
- Bundle appSearchData) {
+ Bundle appSearchData, Rect sourceBounds) {
ComponentName globalSearchActivity = getGlobalSearchActivity();
if (globalSearchActivity == null) {
Log.w(TAG, "No global search activity found.");
@@ -546,6 +563,7 @@ public class SearchManager
if (selectInitialQuery) {
intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery);
}
+ intent.setSourceBounds(sourceBounds);
try {
if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
mContext.startActivity(intent);
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index f81ea81..b1c1f30 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -212,7 +212,11 @@ public class WallpaperManager {
*/
mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER);
}
-
+
+ public Handler getHandler() {
+ return mHandler;
+ }
+
public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) {
synchronized (this) {
if (mWallpaper != null) {
@@ -604,7 +608,7 @@ public class WallpaperManager {
// Ignore
}
}
-
+
/**
* Set the position of the current wallpaper within any larger space, when
* that wallpaper is visible behind the given window. The X and Y offsets
@@ -619,16 +623,26 @@ public class WallpaperManager {
* @param yOffset The offset along the Y dimension, from 0 to 1.
*/
public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) {
- try {
- //Log.v(TAG, "Sending new wallpaper offsets from app...");
- ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
- windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep);
- //Log.v(TAG, "...app returning after sending offsets!");
- } catch (RemoteException e) {
- // Ignore.
- }
+ final IBinder fWindowToken = windowToken;
+ final float fXOffset = xOffset;
+ final float fYOffset = yOffset;
+ sGlobals.getHandler().post(new Runnable() {
+ public void run() {
+ try {
+ //Log.v(TAG, "Sending new wallpaper offsets from app...");
+ ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition(
+ fWindowToken, fXOffset, fYOffset, mWallpaperXStep, mWallpaperYStep);
+ //Log.v(TAG, "...app returning after sending offsets!");
+ } catch (RemoteException e) {
+ // Ignore.
+ } catch (IllegalArgumentException e) {
+ // Since this is being posted, it's possible that this windowToken is no longer
+ // valid, for example, if setWallpaperOffsets is called just before rotation.
+ }
+ }
+ });
}
-
+
/**
* For applications that use multiple virtual screens showing a wallpaper,
* specify the step size between virtual screens. For example, if the
diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java
index 761c7eb..61a9dce 100644
--- a/core/java/android/appwidget/AppWidgetHostView.java
+++ b/core/java/android/appwidget/AppWidgetHostView.java
@@ -26,6 +26,7 @@ import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
@@ -41,8 +42,8 @@ import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.FrameLayout;
import android.widget.RemoteViews;
-import android.widget.TextView;
import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback;
+import android.widget.TextView;
/**
* Provides the glue to show AppWidget views. This class offers automatic animation
@@ -106,7 +107,9 @@ public class AppWidgetHostView extends FrameLayout {
}
/**
- * Set the AppWidget that will be displayed by this view.
+ * Set the AppWidget that will be displayed by this view. This method also adds default padding
+ * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)}
+ * and can be overridden in order to add custom padding.
*/
public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) {
mAppWidgetId = appWidgetId;
@@ -116,49 +119,57 @@ public class AppWidgetHostView extends FrameLayout {
// a widget, eg. for some widgets in safe mode.
if (info != null) {
// We add padding to the AppWidgetHostView if necessary
- Padding padding = getPaddingForWidget(info.provider);
+ Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null);
setPadding(padding.left, padding.top, padding.right, padding.bottom);
}
}
- private static class Padding {
- int left = 0;
- int right = 0;
- int top = 0;
- int bottom = 0;
- }
-
/**
* As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting
* ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend
* that widget developers do not add extra padding to their widgets. This will help
* achieve consistency among widgets.
+ *
+ * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in
+ * order for the AppWidgetHost to account for the automatic padding when computing the number
+ * of cells to allocate to a particular widget.
+ *
+ * @param context the current context
+ * @param component the component name of the widget
+ * @param padding Rect in which to place the output, if null, a new Rect will be allocated and
+ * returned
+ * @return default padding for this widget
*/
- private Padding getPaddingForWidget(ComponentName component) {
- PackageManager packageManager = mContext.getPackageManager();
- Padding p = new Padding();
+ public static Rect getDefaultPaddingForWidget(Context context, ComponentName component,
+ Rect padding) {
+ PackageManager packageManager = context.getPackageManager();
ApplicationInfo appInfo;
+ if (padding == null) {
+ padding = new Rect(0, 0, 0, 0);
+ } else {
+ padding.set(0, 0, 0, 0);
+ }
+
try {
appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0);
- } catch (Exception e) {
+ } catch (NameNotFoundException e) {
// if we can't find the package, return 0 padding
- return p;
+ return padding;
}
if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
- Resources r = getResources();
- p.left = r.getDimensionPixelSize(com.android.internal.
+ Resources r = context.getResources();
+ padding.left = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_left);
- p.right = r.getDimensionPixelSize(com.android.internal.
+ padding.right = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_right);
- p.top = r.getDimensionPixelSize(com.android.internal.
+ padding.top = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_top);
- p.bottom = r.getDimensionPixelSize(com.android.internal.
+ padding.bottom = r.getDimensionPixelSize(com.android.internal.
R.dimen.default_app_widget_padding_bottom);
}
-
- return p;
+ return padding;
}
public int getAppWidgetId() {
diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java
index 4cb8220..0306521 100644
--- a/core/java/android/bluetooth/BluetoothDevice.java
+++ b/core/java/android/bluetooth/BluetoothDevice.java
@@ -247,13 +247,12 @@ public final class BluetoothDevice implements Parcelable {
* has been fetched. This intent is sent only when the UUIDs of the remote
* device are requested to be fetched using Service Discovery Protocol
* <p> Always contains the extra field {@link #EXTRA_DEVICE}
- * <p> Always contains the extra filed {@link #EXTRA_UUID}
+ * <p> Always contains the extra field {@link #EXTRA_UUID}
* <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
- * @hide
*/
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
public static final String ACTION_UUID =
- "android.bleutooth.device.action.UUID";
+ "android.bluetooth.device.action.UUID";
/**
* Broadcast Action: Indicates a failure to retrieve the name of a remote
@@ -451,7 +450,6 @@ public final class BluetoothDevice implements Parcelable {
* Used as an extra field in {@link #ACTION_UUID} intents,
* Contains the {@link android.os.ParcelUuid}s of the remote device which
* is a parcelable version of {@link UUID}.
- * @hide
*/
public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
@@ -770,7 +768,18 @@ public final class BluetoothDevice implements Parcelable {
return false;
}
- /** @hide */
+ /**
+ * Returns the supported features (UUIDs) of the remote device.
+ *
+ * <p>This method does not start a service discovery procedure to retrieve the UUIDs
+ * from the remote device. Instead, the local cached copy of the service
+ * UUIDs are returned.
+ * <p>Use {@link #fetchUuidsWithSdp} if fresh UUIDs are desired.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+ *
+ * @return the supported features (UUIDs) of the remote device,
+ * or null on error
+ */
public ParcelUuid[] getUuids() {
try {
return sService.getRemoteUuids(mAddress);
@@ -779,18 +788,19 @@ public final class BluetoothDevice implements Parcelable {
}
/**
- * Perform a SDP query on the remote device to get the UUIDs
- * supported. This API is asynchronous and an Intent is sent,
- * with the UUIDs supported by the remote end. If there is an error
- * in getting the SDP records or if the process takes a long time,
- * an Intent is sent with the UUIDs that is currently present in the
- * cache. Clients should use the {@link #getUuids} to get UUIDs
- * is SDP is not to be performed.
+ * Perform a service discovery on the remote device to get the UUIDs supported.
+ *
+ * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent,
+ * with the UUIDs supported by the remote end. If there is an error
+ * in getting the SDP records or if the process takes a long time,
+ * {@link #ACTION_UUID} intent is sent with the UUIDs that is currently
+ * present in the cache. Clients should use the {@link #getUuids} to get UUIDs
+ * if service discovery is not to be performed.
+ * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
*
- * @return False if the sanity check fails, True if the process
+ * @return False if the sanity check fails, True if the process
* of initiating an ACL connection to the remote device
* was started.
- * @hide
*/
public boolean fetchUuidsWithSdp() {
try {
diff --git a/core/java/android/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
index 7addd4a..b1d0070 100644
--- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java
+++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java
@@ -86,7 +86,7 @@ public final class BluetoothDeviceProfileState extends StateMachine {
private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104;
private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105;
- private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
+ public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs
private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs
private static final int CONNECTION_ACCESS_UNDEFINED = -1;
private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec
diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl
index fefeb93..deea2b8 100644
--- a/core/java/android/bluetooth/IBluetooth.aidl
+++ b/core/java/android/bluetooth/IBluetooth.aidl
@@ -90,7 +90,7 @@ interface IBluetooth
boolean connectHeadset(String address);
boolean disconnectHeadset(String address);
- boolean notifyIncomingConnection(String address);
+ boolean notifyIncomingConnection(String address, boolean rejected);
// HID profile APIs
boolean connectInputDevice(in BluetoothDevice device);
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index e923349..cc3219b 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.database.ContentObserver;
+import android.database.CrossProcessCursorWrapper;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.database.IContentObserver;
@@ -1568,7 +1569,7 @@ public abstract class ContentResolver {
samplePercent);
}
- private final class CursorWrapperInner extends CursorWrapper {
+ private final class CursorWrapperInner extends CrossProcessCursorWrapper {
private final IContentProvider mContentProvider;
public static final String TAG="CursorWrapperInner";
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9468581..bfbd0ac 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -185,7 +185,14 @@ public abstract class Context {
* used to reduce the amount that the client process's overall importance
* is used to impact it.
*/
- public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0040;
+ public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080;
+
+ /**
+ * Flag for {@link #bindService}: Don't consider the bound service to be
+ * visible, even if the caller is visible.
+ * @hide
+ */
+ public static final int BIND_NOT_VISIBLE = 0x40000000;
/** Return an AssetManager instance for your application's package. */
public abstract AssetManager getAssets();
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index b962800..2d2a90d 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -183,6 +183,12 @@ public class Loader<D> {
}
/**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when the associated fragment/activity
+ * is being started. When using a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
* Starts an asynchronous load of the Loader's data. When the result
* is ready the callbacks will be called on the process's main thread.
* If a previous load has been completed and is still valid
@@ -232,7 +238,13 @@ public class Loader<D> {
}
/**
- * Stops delivery of updates until the next time {@link #startLoading()} is called.
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when the associated fragment/activity
+ * is being stopped. When using a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
+ * <p>Stops delivery of updates until the next time {@link #startLoading()} is called.
* Implementations should <em>not</em> invalidate their data at this point --
* clients are still free to use the last data the loader reported. They will,
* however, typically stop reporting new data if the data changes; they can
@@ -260,6 +272,12 @@ public class Loader<D> {
}
/**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when restarting a Loader. When using
+ * a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
* Tell the Loader that it is being abandoned. This is called prior
* to {@link #reset} to have it retain its current data but not report
* any new data.
@@ -282,6 +300,12 @@ public class Loader<D> {
}
/**
+ * This function will normally be called for you automatically by
+ * {@link android.app.LoaderManager} when destroying a Loader. When using
+ * a Loader with {@link android.app.LoaderManager},
+ * you <em>must not</em> call this method yourself, or you will conflict
+ * with its management of the Loader.
+ *
* Resets the state of the Loader. The Loader should at this point free
* all of its resources, since it may never be called again; however, its
* {@link #startLoading()} may later be called at which point it must be
diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java
index 7d683a5..b2909b3 100644
--- a/core/java/android/content/SyncManager.java
+++ b/core/java/android/content/SyncManager.java
@@ -416,7 +416,8 @@ public class SyncManager implements OnAccountsUpdateListener {
intent.setComponent(syncAdapterInfo.componentName);
if (!mContext.bindService(intent,
new InitializerServiceConnection(account, authority, mContext, mMainHandler),
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) {
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT)) {
Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent);
}
}
@@ -971,7 +972,8 @@ public class SyncManager implements OnAccountsUpdateListener {
mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0));
mBound = true;
final boolean bindResult = mContext.bindService(intent, this,
- Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND);
+ Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALLOW_OOM_MANAGEMENT);
if (!bindResult) {
mBound = false;
}
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 3eb7647..8541748 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -153,10 +153,14 @@ public abstract class PackageManager {
public static final int GET_PERMISSIONS = 0x00001000;
/**
- * Flag parameter to retrieve all applications(even uninstalled ones) with data directories.
- * This state could have resulted if applications have been deleted with flag
- * DONT_DELETE_DATA
- * with a possibility of being replaced or reinstalled in future
+ * Flag parameter to retrieve some information about all applications (even
+ * uninstalled ones) which have data directories. This state could have
+ * resulted if applications have been deleted with flag
+ * {@code DONT_DELETE_DATA} with a possibility of being replaced or
+ * reinstalled in future.
+ * <p>
+ * Note: this flag may cause less information about currently installed
+ * applications to be returned.
*/
public static final int GET_UNINSTALLED_PACKAGES = 0x00002000;
diff --git a/core/java/android/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java
index ad1bfb2..bea6529 100644
--- a/core/java/android/content/res/XmlBlock.java
+++ b/core/java/android/content/res/XmlBlock.java
@@ -484,7 +484,7 @@ final class XmlBlock {
private final AssetManager mAssets;
private final int mNative;
- private final StringBlock mStrings;
+ /*package*/ final StringBlock mStrings;
private boolean mOpen = true;
private int mOpenCount = 1;
@@ -494,9 +494,9 @@ final class XmlBlock {
private static final native int nativeGetStringBlock(int obj);
private static final native int nativeCreateParseState(int obj);
- private static final native int nativeNext(int state);
+ /*package*/ static final native int nativeNext(int state);
private static final native int nativeGetNamespace(int state);
- private static final native int nativeGetName(int state);
+ /*package*/ static final native int nativeGetName(int state);
private static final native int nativeGetText(int state);
private static final native int nativeGetLineNumber(int state);
private static final native int nativeGetAttributeCount(int state);
diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java
index ee6aec6..74fef29 100644
--- a/core/java/android/database/AbstractCursor.java
+++ b/core/java/android/database/AbstractCursor.java
@@ -53,7 +53,10 @@ public abstract class AbstractCursor implements CrossProcessCursor {
abstract public boolean isNull(int column);
public int getType(int column) {
- throw new UnsupportedOperationException();
+ // Reflects the assumption that all commonly used field types (meaning everything
+ // but blobs) are convertible to strings so it should be safe to call
+ // getString to retrieve them.
+ return FIELD_TYPE_STRING;
}
// TODO implement getBlob in all cursor types
@@ -185,46 +188,9 @@ public abstract class AbstractCursor implements CrossProcessCursor {
return result;
}
- /**
- * Copy data from cursor to CursorWindow
- * @param position start position of data
- * @param window
- */
+ @Override
public void fillWindow(int position, CursorWindow window) {
- if (position < 0 || position >= getCount()) {
- return;
- }
- window.acquireReference();
- try {
- int oldpos = mPos;
- mPos = position - 1;
- window.clear();
- window.setStartPosition(position);
- int columnNum = getColumnCount();
- window.setNumColumns(columnNum);
- while (moveToNext() && window.allocRow()) {
- for (int i = 0; i < columnNum; i++) {
- String field = getString(i);
- if (field != null) {
- if (!window.putString(field, mPos, i)) {
- window.freeLastRow();
- break;
- }
- } else {
- if (!window.putNull(mPos, i)) {
- window.freeLastRow();
- break;
- }
- }
- }
- }
-
- mPos = oldpos;
- } catch (IllegalStateException e){
- // simply ignore it
- } finally {
- window.releaseReference();
- }
+ DatabaseUtils.cursorFillWindow(this, position, window);
}
public final boolean move(int offset) {
diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java
index d0aedd2..083485f 100644
--- a/core/java/android/database/AbstractWindowedCursor.java
+++ b/core/java/android/database/AbstractWindowedCursor.java
@@ -188,15 +188,14 @@ public abstract class AbstractWindowedCursor extends AbstractCursor {
/**
* If there is a window, clear it.
- * Otherwise, creates a local window.
+ * Otherwise, creates a new window.
*
* @param name The window name.
* @hide
*/
- protected void clearOrCreateLocalWindow(String name) {
+ protected void clearOrCreateWindow(String name) {
if (mWindow == null) {
- // If there isn't a window set already it will only be accessed locally
- mWindow = new CursorWindow(name, true /* the window is local only */);
+ mWindow = new CursorWindow(name);
} else {
mWindow.clear();
}
diff --git a/core/java/android/database/CrossProcessCursor.java b/core/java/android/database/CrossProcessCursor.java
index 8e6a5aa..26379cc 100644
--- a/core/java/android/database/CrossProcessCursor.java
+++ b/core/java/android/database/CrossProcessCursor.java
@@ -16,27 +16,63 @@
package android.database;
+/**
+ * A cross process cursor is an extension of a {@link Cursor} that also supports
+ * usage from remote processes.
+ * <p>
+ * The contents of a cross process cursor are marshalled to the remote process by
+ * filling {@link CursorWindow} objects using {@link #fillWindow}. As an optimization,
+ * the cursor can provide a pre-filled window to use via {@link #getWindow} thereby
+ * obviating the need to copy the data to yet another cursor window.
+ */
public interface CrossProcessCursor extends Cursor {
/**
- * returns a pre-filled window, return NULL if no such window
+ * Returns a pre-filled window that contains the data within this cursor.
+ * <p>
+ * In particular, the window contains the row indicated by {@link Cursor#getPosition}.
+ * The window's contents are automatically scrolled whenever the current
+ * row moved outside the range covered by the window.
+ * </p>
+ *
+ * @return The pre-filled window, or null if none.
*/
CursorWindow getWindow();
/**
- * copies cursor data into the window start at pos
+ * Copies cursor data into the window.
+ * <p>
+ * Clears the window and fills it with data beginning at the requested
+ * row position until all of the data in the cursor is exhausted
+ * or the window runs out of space.
+ * </p><p>
+ * The filled window uses the same row indices as the original cursor.
+ * For example, if you fill a window starting from row 5 from the cursor,
+ * you can query the contents of row 5 from the window just by asking it
+ * for row 5 because there is a direct correspondence between the row indices
+ * used by the cursor and the window.
+ * </p><p>
+ * The current position of the cursor, as returned by {@link #getPosition},
+ * is not changed by this method.
+ * </p>
+ *
+ * @param position The zero-based index of the first row to copy into the window.
+ * @param window The window to fill.
*/
- void fillWindow(int pos, CursorWindow winow);
+ void fillWindow(int position, CursorWindow window);
/**
* This function is called every time the cursor is successfully scrolled
* to a new position, giving the subclass a chance to update any state it
- * may have. If it returns false the move function will also do so and the
+ * may have. If it returns false the move function will also do so and the
* cursor will scroll to the beforeFirst position.
+ * <p>
+ * This function should be called by methods such as {@link #moveToPosition(int)},
+ * so it will typically not be called from outside of the cursor class itself.
+ * </p>
*
- * @param oldPosition the position that we're moving from
- * @param newPosition the position that we're moving to
- * @return true if the move is successful, false otherwise
+ * @param oldPosition The position that we're moving from.
+ * @param newPosition The position that we're moving to.
+ * @return True if the move is successful, false otherwise.
*/
boolean onMove(int oldPosition, int newPosition);
-
}
diff --git a/core/java/android/database/CrossProcessCursorWrapper.java b/core/java/android/database/CrossProcessCursorWrapper.java
new file mode 100644
index 0000000..8c250b8
--- /dev/null
+++ b/core/java/android/database/CrossProcessCursorWrapper.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2011 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.database;
+
+import android.database.CrossProcessCursor;
+import android.database.Cursor;
+import android.database.CursorWindow;
+import android.database.CursorWrapper;
+
+/**
+ * Cursor wrapper that implements {@link CrossProcessCursor}.
+ * <p>
+ * If the wrapper cursor implemented {@link CrossProcessCursor}, then delegates
+ * {@link #fillWindow}, {@link #getWindow()} and {@link #onMove} to it. Otherwise,
+ * provides default implementations of these methods that traverse the contents
+ * of the cursor similar to {@link AbstractCursor#fillWindow}.
+ * </p><p>
+ * This wrapper can be used to adapt an ordinary {@link Cursor} into a
+ * {@link CrossProcessCursor}.
+ * </p>
+ */
+public class CrossProcessCursorWrapper extends CursorWrapper implements CrossProcessCursor {
+ /**
+ * Creates a cross process cursor wrapper.
+ * @param cursor The underlying cursor to wrap.
+ */
+ public CrossProcessCursorWrapper(Cursor cursor) {
+ super(cursor);
+ }
+
+ @Override
+ public void fillWindow(int position, CursorWindow window) {
+ if (mCursor instanceof CrossProcessCursor) {
+ final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor;
+ crossProcessCursor.fillWindow(position, window);
+ return;
+ }
+
+ DatabaseUtils.cursorFillWindow(mCursor, position, window);
+ }
+
+ @Override
+ public CursorWindow getWindow() {
+ if (mCursor instanceof CrossProcessCursor) {
+ final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor;
+ return crossProcessCursor.getWindow();
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean onMove(int oldPosition, int newPosition) {
+ if (mCursor instanceof CrossProcessCursor) {
+ final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor;
+ return crossProcessCursor.onMove(oldPosition, newPosition);
+ }
+
+ return true;
+ }
+}
diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java
index dd2c9b7..215035d 100644
--- a/core/java/android/database/CursorToBulkCursorAdaptor.java
+++ b/core/java/android/database/CursorToBulkCursorAdaptor.java
@@ -25,9 +25,9 @@ import android.util.Log;
/**
* Wraps a BulkCursor around an existing Cursor making it remotable.
* <p>
- * If the wrapped cursor is a {@link AbstractWindowedCursor} then it owns
- * the cursor window. Otherwise, the adaptor takes ownership of the
- * cursor itself and ensures it gets closed as needed during deactivation
+ * If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow}
+ * then it is assumed to own the window. Otherwise, the adaptor provides a
+ * window to be filled and ensures it gets closed as needed during deactivation
* and requeries.
* </p>
*
@@ -48,12 +48,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
private CrossProcessCursor mCursor;
/**
- * The cursor window used by the cross process cursor.
- * This field is always null for abstract windowed cursors since they are responsible
- * for managing the lifetime of their window.
+ * The cursor window that was filled by the cross process cursor in the
+ * case where the cursor does not support getWindow.
+ * This field is only ever non-null when the window has actually be filled.
*/
- private CursorWindow mWindowForNonWindowedCursor;
- private boolean mWindowForNonWindowedCursorWasFilled;
+ private CursorWindow mFilledWindow;
private static final class ContentObserverProxy extends ContentObserver {
protected IContentObserver mRemote;
@@ -90,11 +89,10 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer,
String providerName) {
- try {
- mCursor = (CrossProcessCursor) cursor;
- } catch (ClassCastException e) {
- throw new UnsupportedOperationException(
- "Only CrossProcessCursor cursors are supported across process for now", e);
+ if (cursor instanceof CrossProcessCursor) {
+ mCursor = (CrossProcessCursor)cursor;
+ } else {
+ mCursor = new CrossProcessCursorWrapper(cursor);
}
mProviderName = providerName;
@@ -103,11 +101,10 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
}
}
- private void closeWindowForNonWindowedCursorLocked() {
- if (mWindowForNonWindowedCursor != null) {
- mWindowForNonWindowedCursor.close();
- mWindowForNonWindowedCursor = null;
- mWindowForNonWindowedCursorWasFilled = false;
+ private void closeFilledWindowLocked() {
+ if (mFilledWindow != null) {
+ mFilledWindow.close();
+ mFilledWindow = null;
}
}
@@ -118,7 +115,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
mCursor = null;
}
- closeWindowForNonWindowedCursorLocked();
+ closeFilledWindowLocked();
}
private void throwIfCursorIsClosed() {
@@ -139,30 +136,24 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
synchronized (mLock) {
throwIfCursorIsClosed();
- CursorWindow window;
- if (mCursor instanceof AbstractWindowedCursor) {
- AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor)mCursor;
- window = windowedCursor.getWindow();
- if (window == null) {
- window = new CursorWindow(mProviderName, false /*localOnly*/);
- windowedCursor.setWindow(window);
- }
+ if (!mCursor.moveToPosition(startPos)) {
+ closeFilledWindowLocked();
+ return null;
+ }
- mCursor.moveToPosition(startPos);
+ CursorWindow window = mCursor.getWindow();
+ if (window != null) {
+ closeFilledWindowLocked();
} else {
- window = mWindowForNonWindowedCursor;
+ window = mFilledWindow;
if (window == null) {
- window = new CursorWindow(mProviderName, false /*localOnly*/);
- mWindowForNonWindowedCursor = window;
- }
-
- mCursor.moveToPosition(startPos);
-
- if (!mWindowForNonWindowedCursorWasFilled
- || startPos < window.getStartPosition()
+ mFilledWindow = new CursorWindow(mProviderName);
+ window = mFilledWindow;
+ mCursor.fillWindow(startPos, window);
+ } else if (startPos < window.getStartPosition()
|| startPos >= window.getStartPosition() + window.getNumRows()) {
+ window.clear();
mCursor.fillWindow(startPos, window);
- mWindowForNonWindowedCursorWasFilled = true;
}
}
@@ -211,7 +202,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
mCursor.deactivate();
}
- closeWindowForNonWindowedCursorLocked();
+ closeFilledWindowLocked();
}
}
@@ -227,7 +218,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative
synchronized (mLock) {
throwIfCursorIsClosed();
- closeWindowForNonWindowedCursorLocked();
+ closeFilledWindowLocked();
try {
if (!mCursor.requery()) {
diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java
index a1be121..e9675e8 100644
--- a/core/java/android/database/CursorWindow.java
+++ b/core/java/android/database/CursorWindow.java
@@ -31,8 +31,8 @@ import android.util.SparseIntArray;
/**
* A buffer containing multiple cursor rows.
* <p>
- * A {@link CursorWindow} is read-write when created and used locally. When sent
- * to a remote process (by writing it to a {@link Parcel}), the remote process
+ * A {@link CursorWindow} is read-write when initially created and used locally.
+ * When sent to a remote process (by writing it to a {@link Parcel}), the remote process
* receives a read-only view of the cursor window. Typically the cursor window
* will be allocated by the producer, filled with data, and then sent to the
* consumer for reading.
@@ -59,8 +59,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
private final CloseGuard mCloseGuard = CloseGuard.get();
- private static native int nativeCreate(String name,
- int cursorWindowSize, boolean localOnly);
+ private static native int nativeCreate(String name, int cursorWindowSize);
private static native int nativeCreateFromParcel(Parcel parcel);
private static native void nativeDispose(int windowPtr);
private static native void nativeWriteToParcel(int windowPtr, Parcel parcel);
@@ -96,15 +95,11 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </p>
*
* @param name The name of the cursor window, or null if none.
- * @param localWindow True if this window will be used in this process only,
- * false if it might be sent to another processes.
- *
- * @hide
*/
- public CursorWindow(String name, boolean localWindow) {
+ public CursorWindow(String name) {
mStartPos = 0;
mName = name;
- mWindowPtr = nativeCreate(name, sCursorWindowSize, localWindow);
+ mWindowPtr = nativeCreate(name, sCursorWindowSize);
if (mWindowPtr == 0) {
throw new CursorWindowAllocationException("Cursor window allocation of " +
(sCursorWindowSize / 1024) + " kb failed. " + printStats());
@@ -121,10 +116,14 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </p>
*
* @param localWindow True if this window will be used in this process only,
- * false if it might be sent to another processes.
+ * false if it might be sent to another processes. This argument is ignored.
+ *
+ * @deprecated There is no longer a distinction between local and remote
+ * cursor windows. Use the {@link #CursorWindow(String)} constructor instead.
*/
+ @Deprecated
public CursorWindow(boolean localWindow) {
- this(null, localWindow);
+ this((String)null);
}
private CursorWindow(Parcel source) {
@@ -285,8 +284,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Returns true if the field at the specified row and column index
* has type {@link Cursor#FIELD_TYPE_NULL}.
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}.
* @deprecated Use {@link #getType(int, int)} instead.
@@ -300,8 +298,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Returns true if the field at the specified row and column index
* has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}.
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or
* {@link Cursor#FIELD_TYPE_NULL}.
@@ -317,8 +314,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Returns true if the field at the specified row and column index
* has type {@link Cursor#FIELD_TYPE_INTEGER}.
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}.
* @deprecated Use {@link #getType(int, int)} instead.
@@ -332,8 +328,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Returns true if the field at the specified row and column index
* has type {@link Cursor#FIELD_TYPE_FLOAT}.
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}.
* @deprecated Use {@link #getType(int, int)} instead.
@@ -347,8 +342,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Returns true if the field at the specified row and column index
* has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}.
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if the field has type {@link Cursor#FIELD_TYPE_STRING}
* or {@link Cursor#FIELD_TYPE_NULL}.
@@ -373,8 +367,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </ul>
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The field type.
*/
@@ -404,8 +397,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </ul>
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a byte array.
*/
@@ -440,8 +432,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </ul>
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a string.
*/
@@ -479,8 +470,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </ul>
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically
* resized if the requested string is larger than the buffer's current capacity.
@@ -515,8 +505,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </ul>
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a <code>long</code>.
*/
@@ -548,8 +537,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* </ul>
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a <code>double</code>.
*/
@@ -570,8 +558,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* result to <code>short</code>.
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as a <code>short</code>.
*/
@@ -587,8 +574,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* result to <code>int</code>.
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as an <code>int</code>.
*/
@@ -604,8 +590,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* result to <code>float</code>.
* </p>
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return The value of the field as an <code>float</code>.
*/
@@ -617,8 +602,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Copies a byte array into the field at the specified row and column index.
*
* @param value The value to store.
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
@@ -635,8 +619,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Copies a string into the field at the specified row and column index.
*
* @param value The value to store.
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
@@ -653,8 +636,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* Puts a long integer into the field at the specified row and column index.
*
* @param value The value to store.
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
@@ -672,8 +654,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
* specified row and column index.
*
* @param value The value to store.
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
@@ -689,8 +670,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable {
/**
* Puts a null value into the field at the specified row and column index.
*
- * @param row The zero-based row index, relative to the cursor window's
- * start position ({@link #getStartPosition()}).
+ * @param row The zero-based row index.
* @param column The zero-based column index.
* @return True if successful.
*/
diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java
index 320733e..7baeb8c 100644
--- a/core/java/android/database/CursorWrapper.java
+++ b/core/java/android/database/CursorWrapper.java
@@ -25,9 +25,13 @@ import android.os.Bundle;
* use for this class is to extend a cursor while overriding only a subset of its methods.
*/
public class CursorWrapper implements Cursor {
+ /** @hide */
+ protected final Cursor mCursor;
- private final Cursor mCursor;
-
+ /**
+ * Creates a cursor wrapper.
+ * @param cursor The underlying cursor to wrap.
+ */
public CursorWrapper(Cursor cursor) {
mCursor = cursor;
}
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 8e6f699..a10ca15 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -237,7 +237,8 @@ public class DatabaseUtils {
return Cursor.FIELD_TYPE_BLOB;
} else if (obj instanceof Float || obj instanceof Double) {
return Cursor.FIELD_TYPE_FLOAT;
- } else if (obj instanceof Long || obj instanceof Integer) {
+ } else if (obj instanceof Long || obj instanceof Integer
+ || obj instanceof Short || obj instanceof Byte) {
return Cursor.FIELD_TYPE_INTEGER;
} else {
return Cursor.FIELD_TYPE_STRING;
@@ -245,6 +246,82 @@ public class DatabaseUtils {
}
/**
+ * Fills the specified cursor window by iterating over the contents of the cursor.
+ * The window is filled until the cursor is exhausted or the window runs out
+ * of space.
+ *
+ * The original position of the cursor is left unchanged by this operation.
+ *
+ * @param cursor The cursor that contains the data to put in the window.
+ * @param position The start position for filling the window.
+ * @param window The window to fill.
+ * @hide
+ */
+ public static void cursorFillWindow(final Cursor cursor,
+ int position, final CursorWindow window) {
+ if (position < 0 || position >= cursor.getCount()) {
+ return;
+ }
+ window.acquireReference();
+ try {
+ final int oldPos = cursor.getPosition();
+ final int numColumns = cursor.getColumnCount();
+ window.clear();
+ window.setStartPosition(position);
+ window.setNumColumns(numColumns);
+ if (cursor.moveToPosition(position)) {
+ do {
+ if (!window.allocRow()) {
+ break;
+ }
+ for (int i = 0; i < numColumns; i++) {
+ final int type = cursor.getType(i);
+ final boolean success;
+ switch (type) {
+ case Cursor.FIELD_TYPE_NULL:
+ success = window.putNull(position, i);
+ break;
+
+ case Cursor.FIELD_TYPE_INTEGER:
+ success = window.putLong(cursor.getLong(i), position, i);
+ break;
+
+ case Cursor.FIELD_TYPE_FLOAT:
+ success = window.putDouble(cursor.getDouble(i), position, i);
+ break;
+
+ case Cursor.FIELD_TYPE_BLOB: {
+ final byte[] value = cursor.getBlob(i);
+ success = value != null ? window.putBlob(value, position, i)
+ : window.putNull(position, i);
+ break;
+ }
+
+ default: // assume value is convertible to String
+ case Cursor.FIELD_TYPE_STRING: {
+ final String value = cursor.getString(i);
+ success = value != null ? window.putString(value, position, i)
+ : window.putNull(position, i);
+ break;
+ }
+ }
+ if (!success) {
+ window.freeLastRow();
+ break;
+ }
+ }
+ position += 1;
+ } while (cursor.moveToNext());
+ }
+ cursor.moveToPosition(oldPos);
+ } catch (IllegalStateException e){
+ // simply ignore it
+ } finally {
+ window.releaseReference();
+ }
+ }
+
+ /**
* Appends an SQL string to the given StringBuilder, including the opening
* and closing single quotes. Any single quotes internal to sqlString will
* be escaped.
diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java
index bdb96b1..dafbc79 100644
--- a/core/java/android/database/sqlite/SQLiteCompiledSql.java
+++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java
@@ -49,7 +49,7 @@ import android.util.Log;
/** the following are for debugging purposes */
private String mSqlStmt = null;
- private Throwable mStackTrace = null;
+ private final Throwable mStackTrace;
/** when in cache and is in use, this member is set */
private boolean mInUse = false;
@@ -59,7 +59,11 @@ import android.util.Log;
db.verifyLockOwner();
mDatabase = db;
mSqlStmt = sql;
- mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
+ if (StrictMode.vmSqliteObjectLeaksEnabled()) {
+ mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
+ } else {
+ mStackTrace = null;
+ }
nHandle = db.mNativeHandle;
native_compile(sql);
}
@@ -112,7 +116,7 @@ import android.util.Log;
// but if the database itself is not closed and is GC'ed, then
// all sub-objects attached to the database could end up getting GC'ed too.
// in that case, don't print any warning.
- if (mInUse && StrictMode.vmSqliteObjectLeaksEnabled()) {
+ if (mInUse && mStackTrace != null) {
int len = mSqlStmt.length();
StrictMode.onSqliteObjectLeaked(
"Releasing statement in a finalizer. Please ensure " +
diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java
index a1c36e2..c24acd4 100644
--- a/core/java/android/database/sqlite/SQLiteCursor.java
+++ b/core/java/android/database/sqlite/SQLiteCursor.java
@@ -95,7 +95,11 @@ public class SQLiteCursor extends AbstractWindowedCursor {
if (query.mDatabase == null) {
throw new IllegalArgumentException("query.mDatabase cannot be null");
}
- mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
+ if (StrictMode.vmSqliteObjectLeaksEnabled()) {
+ mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace();
+ } else {
+ mStackTrace = null;
+ }
mDriver = driver;
mEditTable = editTable;
mColumnNameMap = null;
@@ -155,7 +159,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
}
private void fillWindow(int startPos) {
- clearOrCreateLocalWindow(getDatabase().getPath());
+ clearOrCreateWindow(getDatabase().getPath());
mWindow.setStartPosition(startPos);
int count = getQuery().fillWindow(mWindow);
if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0
@@ -319,7 +323,7 @@ public class SQLiteCursor extends AbstractWindowedCursor {
try {
// if the cursor hasn't been closed yet, close it first
if (mWindow != null) {
- if (StrictMode.vmSqliteObjectLeaksEnabled()) {
+ if (mStackTrace != null) {
int len = mQuery.mSql.length();
StrictMode.onSqliteObjectLeaked(
"Finalizing a Cursor that has not been deactivated or closed. " +
diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html
index ff0f9f5..ceed171 100644
--- a/core/java/android/database/sqlite/package.html
+++ b/core/java/android/database/sqlite/package.html
@@ -3,7 +3,7 @@
Contains the SQLite database management
classes that an application would use to manage its own private database.
<p>
-Applications use these classes to maange private databases. If creating a
+Applications use these classes to manage private databases. If creating a
content provider, you will probably have to use these classes to create and
manage your own database to store content. See <a
href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> to learn
diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java
index 68f0247..c2a757f 100644
--- a/core/java/android/hardware/Camera.java
+++ b/core/java/android/hardware/Camera.java
@@ -22,9 +22,12 @@ import android.graphics.ImageFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.SystemProperties;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
@@ -154,6 +157,7 @@ public class Camera {
private boolean mOneShot;
private boolean mWithBuffer;
private boolean mFaceDetectionRunning = false;
+ private boolean mReleased = false;
/**
* Broadcast Action: A new picture is taken by the camera, and the entry of
@@ -303,7 +307,7 @@ public class Camera {
}
protected void finalize() {
- native_release();
+ release();
}
private native final void native_setup(Object camera_this, int cameraId);
@@ -318,6 +322,15 @@ public class Camera {
public final void release() {
native_release();
mFaceDetectionRunning = false;
+ if (mCameraSoundPlayers != null) {
+ for (CameraSoundPlayer csp: mCameraSoundPlayers) {
+ if (csp != null) {
+ csp.release();
+ }
+ }
+ mCameraSoundPlayers = null;
+ }
+ mReleased = true;
}
/**
@@ -1098,9 +1111,21 @@ public class Camera {
* Parameters#getMaxNumDetectedFaces()} returns a number larger than 0.
* If the face detection has started, apps should not call this again.
*
- * When the face detection is running, {@link Parameters#setWhiteBalance(String)},
+ * <p>When the face detection is running, {@link Parameters#setWhiteBalance(String)},
* {@link Parameters#setFocusAreas(List)}, and {@link Parameters#setMeteringAreas(List)}
- * have no effect.
+ * have no effect. The camera uses the detected faces to do auto-white balance,
+ * auto exposure, and autofocus.
+ *
+ * <p>If the apps call {@link #autoFocus(AutoFocusCallback)}, the camera
+ * will stop sending face callbacks. The last face callback indicates the
+ * areas used to do autofocus. After focus completes, face detection will
+ * resume sending face callbacks. If the apps call {@link
+ * #cancelAutoFocus()}, the face callbacks will also resume.</p>
+ *
+ * <p>After calling {@link #takePicture(Camera.ShutterCallback, Camera.PictureCallback,
+ * Camera.PictureCallback)} or {@link #stopPreview()}, and then resuming
+ * preview with {@link #startPreview()}, the apps should call this method
+ * again to resume face detection.</p>
*
* @throws IllegalArgumentException if the face detection is unsupported.
* @throws RuntimeException if the method fails or the face detection is
@@ -1150,14 +1175,31 @@ public class Camera {
* camera field of view, and (1000, 1000) represents the bottom-right of
* the field of view. For example, suppose the size of the viewfinder UI
* is 800x480. The rect passed from the driver is (-1000, -1000, 0, 0).
- * The corresponding viewfinder rect should be (0, 0, 400, 240). The
- * width and height of the rect will not be 0 or negative. The
- * coordinates can be smaller than -1000 or bigger than 1000. But at
- * least one vertex will be within (-1000, -1000) and (1000, 1000).
+ * The corresponding viewfinder rect should be (0, 0, 400, 240). It is
+ * guaranteed left < right and top < bottom. The coordinates can be
+ * smaller than -1000 or bigger than 1000. But at least one vertex will
+ * be within (-1000, -1000) and (1000, 1000).
*
* <p>The direction is relative to the sensor orientation, that is, what
* the sensor sees. The direction is not affected by the rotation or
- * mirroring of {@link #setDisplayOrientation(int)}.</p>
+ * mirroring of {@link #setDisplayOrientation(int)}. The face bounding
+ * rectangle does not provide any information about face orientation.</p>
+ *
+ * <p>Here is the matrix to convert driver coordinates to View coordinates
+ * in pixels.</p>
+ * <pre>
+ * Matrix matrix = new Matrix();
+ * CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId];
+ * // Need mirror for front camera.
+ * boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+ * matrix.setScale(mirror ? -1 : 1, 1);
+ * // This is the value for android.hardware.Camera.setDisplayOrientation.
+ * matrix.postRotate(displayOrientation);
+ * // Camera driver coordinates range from (-1000, -1000) to (1000, 1000).
+ * // UI coordinates range from (0, 0) to (width, height).
+ * matrix.postScale(view.getWidth() / 2000f, view.getHeight() / 2000f);
+ * matrix.postTranslate(view.getWidth() / 2f, view.getHeight() / 2f);
+ * </pre>
*
* @see #startFaceDetection()
*/
@@ -2354,7 +2396,7 @@ public class Camera {
*
* <p>The reference code is as follows.
*
- * <pre>
+ * <pre>
* public void onOrientationChanged(int orientation) {
* if (orientation == ORIENTATION_UNKNOWN) return;
* android.hardware.Camera.CameraInfo info =
@@ -2369,7 +2411,7 @@ public class Camera {
* }
* mParameters.setRotation(rotation);
* }
- * </pre>
+ * </pre>
*
* @param rotation The rotation angle in degrees relative to the
* orientation of the camera. Rotation can only be 0,
@@ -3259,7 +3301,6 @@ public class Camera {
* disable video stabilization.
* @see #isVideoStabilizationSupported()
* @see #getVideoStabilization()
- * @hide
*/
public void setVideoStabilization(boolean toggle) {
set(KEY_VIDEO_STABILIZATION, toggle ? TRUE : FALSE);
@@ -3272,7 +3313,6 @@ public class Camera {
* @return true if video stabilization is enabled
* @see #isVideoStabilizationSupported()
* @see #setVideoStabilization(boolean)
- * @hide
*/
public boolean getVideoStabilization() {
String str = get(KEY_VIDEO_STABILIZATION);
@@ -3286,7 +3326,6 @@ public class Camera {
* @return true if video stabilization is supported
* @see #setVideoStabilization(boolean)
* @see #getVideoStabilization()
- * @hide
*/
public boolean isVideoStabilizationSupported() {
String str = get(KEY_VIDEO_STABILIZATION_SUPPORTED);
@@ -3455,4 +3494,194 @@ public class Camera {
return result;
}
};
+
+ /**
+ * <p>The set of default system sounds for camera actions. Use this with
+ * {@link #playSound} to play an appropriate sound when implementing a
+ * custom still or video recording mechanism through the preview
+ * callbacks.</p>
+ *
+ * <p>There is no need to play sounds when using {@link #takePicture} or
+ * {@link android.media.MediaRecorder} for still images or video,
+ * respectively, as these play their own sounds when needed.</p>
+ *
+ * @see #playSound
+ * @hide
+ */
+ public static class Sound {
+ /**
+ * The sound used by {@link android.hardware.Camera#takePicture} to
+ * indicate still image capture.
+ */
+ public static final int SHUTTER_CLICK = 0;
+
+ /**
+ * A sound to indicate that focusing has completed. Because deciding
+ * when this occurs is application-dependent, this sound is not used by
+ * any methods in the Camera class.
+ */
+ public static final int FOCUS_COMPLETE = 1;
+
+ /**
+ * The sound used by {@link android.media.MediaRecorder#start} to
+ * indicate the start of video recording.
+ */
+ public static final int START_VIDEO_RECORDING = 2;
+
+ /**
+ * The sound used by {@link android.media.MediaRecorder#stop} to
+ * indicate the end of video recording.
+ */
+ public static final int STOP_VIDEO_RECORDING = 3;
+
+ private static final int NUM_SOUNDS = 4;
+ };
+
+ /**
+ * <p>Play one of the predefined platform sounds for camera actions.</p>
+ *
+ * <p>Use this method to play a platform-specific sound for various camera
+ * actions. The sound playing is done asynchronously, with the same behavior
+ * and content as the sounds played by {@link #takePicture takePicture},
+ * {@link android.media.MediaRecorder#start MediaRecorder.start}, and
+ * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p>
+ *
+ * <p>Using this method makes it easy to match the default device sounds
+ * when recording or capturing data through the preview callbacks
+ * ({@link #setPreviewCallback setPreviewCallback},
+ * {@link #setPreviewTexture setPreviewTexture}).</p>
+ *
+ * @param soundId The type of sound to play, selected from the options in
+ * {@link android.hardware.Camera.Sound}
+ * @see android.hardware.Camera.Sound
+ * @see #takePicture
+ * @see android.media.MediaRecorder
+ * @hide
+ */
+ public void playSound(int soundId) {
+ if (mReleased) return;
+ if (mCameraSoundPlayers == null) {
+ mCameraSoundPlayers = new CameraSoundPlayer[Sound.NUM_SOUNDS];
+ }
+ if (mCameraSoundPlayers[soundId] == null) {
+ mCameraSoundPlayers[soundId] = new CameraSoundPlayer(soundId);
+ }
+ mCameraSoundPlayers[soundId].play();
+ }
+
+ private CameraSoundPlayer[] mCameraSoundPlayers;
+
+ private static class CameraSoundPlayer implements Runnable {
+ private int mSoundId;
+ private int mAudioStreamType;
+ private MediaPlayer mPlayer;
+ private Thread mThread;
+ private boolean mExit;
+ private int mPlayCount;
+
+ private static final String mShutterSound =
+ "/system/media/audio/ui/camera_click.ogg";
+ private static final String mFocusSound =
+ "/system/media/audio/ui/camera_focus.ogg";
+ private static final String mVideoStartSound =
+ "/system/media/audio/ui/VideoRecord.ogg";
+ private static final String mVideoStopSound =
+ "/system/media/audio/ui/VideoRecord.ogg";
+
+ @Override
+ public void run() {
+ String soundFilePath;
+ switch (mSoundId) {
+ case Sound.SHUTTER_CLICK:
+ soundFilePath = mShutterSound;
+ break;
+ case Sound.FOCUS_COMPLETE:
+ soundFilePath = mFocusSound;
+ break;
+ case Sound.START_VIDEO_RECORDING:
+ soundFilePath = mVideoStartSound;
+ break;
+ case Sound.STOP_VIDEO_RECORDING:
+ soundFilePath = mVideoStopSound;
+ break;
+ default:
+ Log.e(TAG, "Unknown sound " + mSoundId + " requested.");
+ return;
+ }
+ mPlayer = new MediaPlayer();
+ try {
+ mPlayer.setAudioStreamType(mAudioStreamType);
+ mPlayer.setDataSource(soundFilePath);
+ mPlayer.setLooping(false);
+ mPlayer.prepare();
+ } catch(IOException e) {
+ Log.e(TAG, "Error setting up sound " + mSoundId, e);
+ return;
+ }
+
+ while(true) {
+ try {
+ synchronized (this) {
+ while(true) {
+ if (mExit) {
+ return;
+ } else if (mPlayCount <= 0) {
+ wait();
+ } else {
+ mPlayCount--;
+ break;
+ }
+ }
+ }
+ mPlayer.start();
+ } catch (Exception e) {
+ Log.e(TAG, "Error playing sound " + mSoundId, e);
+ }
+ }
+ }
+
+ public CameraSoundPlayer(int soundId) {
+ mSoundId = soundId;
+ if (SystemProperties.get("ro.camera.sound.forced", "0").equals("0")) {
+ mAudioStreamType = AudioManager.STREAM_MUSIC;
+ } else {
+ mAudioStreamType = AudioManager.STREAM_SYSTEM_ENFORCED;
+ }
+ }
+
+ public void play() {
+ if (mThread == null) {
+ mThread = new Thread(this);
+ mThread.start();
+ }
+ synchronized (this) {
+ mPlayCount++;
+ notifyAll();
+ }
+ }
+
+ public void release() {
+ if (mThread != null) {
+ synchronized (this) {
+ mExit = true;
+ notifyAll();
+ }
+ try {
+ mThread.join();
+ } catch (InterruptedException e) {
+ }
+ mThread = null;
+ }
+ if (mPlayer != null) {
+ mPlayer.release();
+ mPlayer = null;
+ }
+ }
+
+ @Override
+ protected void finalize() {
+ release();
+ }
+ }
+
}
diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java
index 5343e2a..5143f7f 100644
--- a/core/java/android/inputmethodservice/KeyboardView.java
+++ b/core/java/android/inputmethodservice/KeyboardView.java
@@ -1145,44 +1145,29 @@ public class KeyboardView extends View implements View.OnClickListener {
@Override
public boolean onHoverEvent(MotionEvent event) {
- // If touch exploring is enabled we ignore touch events and transform
- // the stream of hover events as touch events. This allows one consistent
- // event stream to drive the keyboard since during touch exploring the
- // first touch generates only hover events and tapping on the same
- // location generates hover and touch events.
if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) {
final int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_HOVER_ENTER:
- event.setAction(MotionEvent.ACTION_DOWN);
- break;
case MotionEvent.ACTION_HOVER_MOVE:
- event.setAction(MotionEvent.ACTION_MOVE);
+ final int touchX = (int) event.getX() - mPaddingLeft;
+ int touchY = (int) event.getY() - mPaddingTop;
+ if (touchY >= -mVerticalCorrection) {
+ touchY += mVerticalCorrection;
+ }
+ final int keyIndex = getKeyIndices(touchX, touchY, null);
+ showPreview(keyIndex);
break;
case MotionEvent.ACTION_HOVER_EXIT:
- event.setAction(MotionEvent.ACTION_UP);
+ showPreview(NOT_A_KEY);
break;
}
- onTouchEventInternal(event);
- event.setAction(action);
}
- return super.onHoverEvent(event);
+ return true;
}
@Override
- public boolean onTouchEvent(MotionEvent event) {
- // If touch exploring is enabled we ignore touch events and transform
- // the stream of hover events as touch events. This allows one consistent
- // event stream to drive the keyboard since during touch exploring the
- // first touch generates only hover events and tapping on the same
- // location generates hover and touch events.
- if (mAccessibilityManager.isTouchExplorationEnabled()) {
- return true;
- }
- return onTouchEventInternal(event);
- }
-
- private boolean onTouchEventInternal(MotionEvent me) {
+ public boolean onTouchEvent(MotionEvent me) {
// Convert multi-pointer up/down events to single up/down events to
// deal with the typical multi-pointer behavior of two-thumb typing
final int pointerCount = me.getPointerCount();
diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java
index 7159260..df1afee 100644
--- a/core/java/android/inputmethodservice/SoftInputWindow.java
+++ b/core/java/android/inputmethodservice/SoftInputWindow.java
@@ -18,17 +18,13 @@ package android.inputmethodservice;
import android.app.Dialog;
import android.content.Context;
-import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.IBinder;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.MotionEvent;
-import android.view.View;
import android.view.WindowManager;
-import java.lang.Math;
-
/**
* A SoftInputWindow is a Dialog that is intended to be used for a top-level input
* method window. It will be displayed along the edge of the screen, moving
@@ -46,7 +42,7 @@ class SoftInputWindow extends Dialog {
}
/**
- * Create a DockWindow that uses a custom style.
+ * Create a SoftInputWindow that uses a custom style.
*
* @param context The Context in which the DockWindow should run. In
* particular, it uses the window manager and theme from this context
diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java
index 69ac1e7..f6e627c 100644
--- a/core/java/android/net/NetworkStats.java
+++ b/core/java/android/net/NetworkStats.java
@@ -16,10 +16,11 @@
package android.net;
+import static com.android.internal.util.Preconditions.checkNotNull;
+
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
-import android.util.Log;
import android.util.SparseBooleanArray;
import com.android.internal.util.Objects;
@@ -54,6 +55,8 @@ public class NetworkStats implements Parcelable {
/** {@link #tag} value for total data across all tags. */
public static final int TAG_NONE = 0;
+ // TODO: move fields to "mVariable" notation
+
/**
* {@link SystemClock#elapsedRealtime()} timestamp when this data was
* generated.
@@ -162,6 +165,17 @@ public class NetworkStats implements Parcelable {
dest.writeLongArray(operations);
}
+ @Override
+ public NetworkStats clone() {
+ final NetworkStats clone = new NetworkStats(elapsedRealtime, size);
+ NetworkStats.Entry entry = null;
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ clone.addValues(entry);
+ }
+ return clone;
+ }
+
// @VisibleForTesting
public NetworkStats addIfaceValues(
String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) {
@@ -295,8 +309,33 @@ public class NetworkStats implements Parcelable {
*/
public int findIndex(String iface, int uid, int set, int tag) {
for (int i = 0; i < size; i++) {
- if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i]
- && tag == this.tag[i]) {
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && Objects.equal(iface, this.iface[i])) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Find first stats index that matches the requested parameters, starting
+ * search around the hinted index as an optimization.
+ */
+ // @VisibleForTesting
+ public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) {
+ for (int offset = 0; offset < size; offset++) {
+ final int halfOffset = offset / 2;
+
+ // search outwards from hint index, alternating forward and backward
+ final int i;
+ if (offset % 2 == 0) {
+ i = (hintIndex + halfOffset) % size;
+ } else {
+ i = (size + hintIndex - halfOffset - 1) % size;
+ }
+
+ if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i]
+ && Objects.equal(iface, this.iface[i])) {
return i;
}
}
@@ -423,24 +462,9 @@ public class NetworkStats implements Parcelable {
* Subtract the given {@link NetworkStats}, effectively leaving the delta
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
- *
- * @throws IllegalArgumentException when given {@link NetworkStats} is
- * non-monotonic.
*/
- public NetworkStats subtract(NetworkStats value) {
- return subtract(value, true, false);
- }
-
- /**
- * Subtract the given {@link NetworkStats}, effectively leaving the delta
- * between two snapshots in time. Assumes that statistics rows collect over
- * time, and that none of them have disappeared.
- * <p>
- * Instead of throwing when counters are non-monotonic, this variant clamps
- * results to never be negative.
- */
- public NetworkStats subtractClamped(NetworkStats value) {
- return subtract(value, false, true);
+ public NetworkStats subtract(NetworkStats value) throws NonMonotonicException {
+ return subtract(value, false);
}
/**
@@ -448,16 +472,14 @@ public class NetworkStats implements Parcelable {
* between two snapshots in time. Assumes that statistics rows collect over
* time, and that none of them have disappeared.
*
- * @param enforceMonotonic Validate that incoming value is strictly
- * monotonic compared to this object.
- * @param clampNegative Instead of throwing like {@code enforceMonotonic},
- * clamp resulting counters at 0 to prevent negative values.
+ * @param clampNonMonotonic When non-monotonic stats are found, just clamp
+ * to 0 instead of throwing {@link NonMonotonicException}.
*/
- private NetworkStats subtract(
- NetworkStats value, boolean enforceMonotonic, boolean clampNegative) {
+ public NetworkStats subtract(NetworkStats value, boolean clampNonMonotonic)
+ throws NonMonotonicException {
final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime;
- if (enforceMonotonic && deltaRealtime < 0) {
- throw new IllegalArgumentException("found non-monotonic realtime");
+ if (deltaRealtime < 0) {
+ throw new NonMonotonicException(this, value);
}
// result will have our rows, and elapsed time between snapshots
@@ -470,7 +492,7 @@ public class NetworkStats implements Parcelable {
entry.tag = tag[i];
// find remote row that matches, and subtract
- final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag);
+ final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i);
if (j == -1) {
// newly appearing row, return entire value
entry.rxBytes = rxBytes[i];
@@ -485,20 +507,18 @@ public class NetworkStats implements Parcelable {
entry.txBytes = txBytes[i] - value.txBytes[j];
entry.txPackets = txPackets[i] - value.txPackets[j];
entry.operations = operations[i] - value.operations[j];
- if (enforceMonotonic
- && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
- || entry.txPackets < 0 || entry.operations < 0)) {
- Log.v(TAG, "lhs=" + this);
- Log.v(TAG, "rhs=" + value);
- throw new IllegalArgumentException(
- "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]");
- }
- if (clampNegative) {
- entry.rxBytes = Math.max(0, entry.rxBytes);
- entry.rxPackets = Math.max(0, entry.rxPackets);
- entry.txBytes = Math.max(0, entry.txBytes);
- entry.txPackets = Math.max(0, entry.txPackets);
- entry.operations = Math.max(0, entry.operations);
+
+ if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0
+ || entry.txPackets < 0 || entry.operations < 0) {
+ if (clampNonMonotonic) {
+ entry.rxBytes = Math.max(entry.rxBytes, 0);
+ entry.rxPackets = Math.max(entry.rxPackets, 0);
+ entry.txBytes = Math.max(entry.txBytes, 0);
+ entry.txPackets = Math.max(entry.txPackets, 0);
+ entry.operations = Math.max(entry.operations, 0);
+ } else {
+ throw new NonMonotonicException(this, i, value, j);
+ }
}
}
@@ -564,12 +584,31 @@ public class NetworkStats implements Parcelable {
return stats;
}
+ /**
+ * Return all rows except those attributed to the requested UID; doesn't
+ * mutate the original structure.
+ */
+ public NetworkStats withoutUid(int uid) {
+ final NetworkStats stats = new NetworkStats(elapsedRealtime, 10);
+
+ Entry entry = new Entry();
+ for (int i = 0; i < size; i++) {
+ entry = getValues(i, entry);
+ if (entry.uid != uid) {
+ stats.addValues(entry);
+ }
+ }
+
+ return stats;
+ }
+
public void dump(String prefix, PrintWriter pw) {
pw.print(prefix);
pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime);
for (int i = 0; i < size; i++) {
pw.print(prefix);
- pw.print(" iface="); pw.print(iface[i]);
+ pw.print(" ["); pw.print(i); pw.print("]");
+ pw.print(" iface="); pw.print(iface[i]);
pw.print(" uid="); pw.print(uid[i]);
pw.print(" set="); pw.print(setToString(set[i]));
pw.print(" tag="); pw.print(tagToString(tag[i]));
@@ -625,4 +664,23 @@ public class NetworkStats implements Parcelable {
return new NetworkStats[size];
}
};
+
+ public static class NonMonotonicException extends Exception {
+ public final NetworkStats left;
+ public final NetworkStats right;
+ public final int leftIndex;
+ public final int rightIndex;
+
+ public NonMonotonicException(NetworkStats left, NetworkStats right) {
+ this(left, -1, right, -1);
+ }
+
+ public NonMonotonicException(
+ NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) {
+ this.left = checkNotNull(left, "missing left");
+ this.right = checkNotNull(right, "missing right");
+ this.leftIndex = leftIndex;
+ this.rightIndex = rightIndex;
+ }
+ }
}
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 18eb9f6..cd585b2 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -20,6 +20,7 @@ import android.app.DownloadManager;
import android.app.backup.BackupManager;
import android.content.Context;
import android.media.MediaPlayer;
+import android.net.NetworkStats.NonMonotonicException;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -192,12 +193,15 @@ public class TrafficStats {
throw new IllegalStateException("not profiling data");
}
- // subtract starting values and return delta
- final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
- final NetworkStats profilingDelta = profilingStop.subtractClamped(
- sActiveProfilingStart);
- sActiveProfilingStart = null;
- return profilingDelta;
+ try {
+ // subtract starting values and return delta
+ final NetworkStats profilingStop = getDataLayerSnapshotForUid(context);
+ final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart);
+ sActiveProfilingStart = null;
+ return profilingDelta;
+ } catch (NonMonotonicException e) {
+ throw new RuntimeException(e);
+ }
}
}
diff --git a/core/java/android/net/wimax/WimaxManagerConstants.java b/core/java/android/net/wimax/WimaxManagerConstants.java
new file mode 100644
index 0000000..b4aaf5b
--- /dev/null
+++ b/core/java/android/net/wimax/WimaxManagerConstants.java
@@ -0,0 +1,104 @@
+package android.net.wimax;
+
+/**
+ * {@hide}
+ */
+public class WimaxManagerConstants
+{
+
+ /**
+ * Used by android.net.wimax.WimaxManager for handling management of
+ * Wimax access.
+ */
+ public static final String WIMAX_SERVICE = "WiMax";
+
+ /**
+ * Broadcast intent action indicating that Wimax has been enabled, disabled,
+ * enabling, disabling, or unknown. One extra provides this state as an int.
+ * Another extra provides the previous state, if available.
+ */
+ public static final String NET_4G_STATE_CHANGED_ACTION =
+ "android.net.fourG.NET_4G_STATE_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Wimax is enabled,
+ * disabled, enabling, disabling, or unknown.
+ */
+ public static final String EXTRA_WIMAX_STATUS = "wimax_status";
+
+ /**
+ * Broadcast intent action indicating that Wimax state has been changed
+ * state could be scanning, connecting, connected, disconnecting, disconnected
+ * initializing, initialized, unknown and ready. One extra provides this state as an int.
+ * Another extra provides the previous state, if available.
+ */
+ public static final String WIMAX_NETWORK_STATE_CHANGED_ACTION =
+ "android.net.fourG.wimax.WIMAX_NETWORK_STATE_CHANGED";
+
+ /**
+ * Broadcast intent action indicating that Wimax signal level has been changed.
+ * Level varies from 0 to 3.
+ */
+ public static final String SIGNAL_LEVEL_CHANGED_ACTION =
+ "android.net.wimax.SIGNAL_LEVEL_CHANGED";
+
+ /**
+ * The lookup key for an int that indicates whether Wimax state is
+ * scanning, connecting, connected, disconnecting, disconnected
+ * initializing, initialized, unknown and ready.
+ */
+ public static final String EXTRA_WIMAX_STATE = "WimaxState";
+ public static final String EXTRA_4G_STATE = "4g_state";
+ public static final String EXTRA_WIMAX_STATE_INT = "WimaxStateInt";
+ /**
+ * The lookup key for an int that indicates whether state of Wimax
+ * is idle.
+ */
+ public static final String EXTRA_WIMAX_STATE_DETAIL = "WimaxStateDetail";
+
+ /**
+ * The lookup key for an int that indicates Wimax signal level.
+ */
+ public static final String EXTRA_NEW_SIGNAL_LEVEL = "newSignalLevel";
+
+ /**
+ * Indicatates Wimax is disabled.
+ */
+ public static final int NET_4G_STATE_DISABLED = 1;
+
+ /**
+ * Indicatates Wimax is enabled.
+ */
+ public static final int NET_4G_STATE_ENABLED = 3;
+
+ /**
+ * Indicatates Wimax status is known.
+ */
+ public static final int NET_4G_STATE_UNKNOWN = 4;
+
+ /**
+ * Indicatates Wimax is in idle state.
+ */
+ public static final int WIMAX_IDLE = 6;
+
+ /**
+ * Indicatates Wimax is being deregistered.
+ */
+ public static final int WIMAX_DEREGISTRATION = 8;
+
+ /**
+ * Indicatates wimax state is unknown.
+ */
+ public static final int WIMAX_STATE_UNKNOWN = 0;
+
+ /**
+ * Indicatates wimax state is connected.
+ */
+ public static final int WIMAX_STATE_CONNECTED = 7;
+
+ /**
+ * Indicatates wimax state is disconnected.
+ */
+ public static final int WIMAX_STATE_DISCONNECTED = 9;
+
+}
diff --git a/core/java/android/nfc/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl
index 016af58..0b93ad0 100644
--- a/core/java/android/nfc/INfcAdapter.aidl
+++ b/core/java/android/nfc/INfcAdapter.aidl
@@ -32,7 +32,7 @@ import android.nfc.INfcTag;
interface INfcAdapter
{
INfcTag getNfcTagInterface();
- INfcAdapterExtras getNfcAdapterExtrasInterface();
+ INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg);
int getState();
boolean disable();
diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/core/java/android/nfc/INfcAdapterExtras.aidl
index 0c2a2fd..2b9d4f0 100644
--- a/core/java/android/nfc/INfcAdapterExtras.aidl
+++ b/core/java/android/nfc/INfcAdapterExtras.aidl
@@ -23,10 +23,10 @@ import android.os.Bundle;
* {@hide}
*/
interface INfcAdapterExtras {
- Bundle open(IBinder b);
- Bundle close();
- Bundle transceive(in byte[] data_in);
- int getCardEmulationRoute();
- void setCardEmulationRoute(int route);
- void authenticate(in byte[] token);
+ Bundle open(in String pkg, IBinder b);
+ Bundle close(in String pkg, IBinder b);
+ Bundle transceive(in String pkg, in byte[] data_in);
+ int getCardEmulationRoute(in String pkg);
+ void setCardEmulationRoute(in String pkg, int route);
+ void authenticate(in String pkg, in byte[] token);
}
diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java
index fe0106d..2857ac5 100644
--- a/core/java/android/nfc/NfcAdapter.java
+++ b/core/java/android/nfc/NfcAdapter.java
@@ -16,6 +16,8 @@
package android.nfc;
+import java.util.HashMap;
+
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.app.Activity;
@@ -197,15 +199,21 @@ public final class NfcAdapter {
static INfcTag sTagService;
/**
- * NfcAdapter is currently a singleton, and does not require a context.
- * However all the public API's are future-proofed to require a context.
- * If we start using that then we'll need to keep a HashMap of
- * Context.getApplicationContext() -> NfcAdapter, such that NfcAdapter
- * is a singleton within each application context.
+ * The NfcAdapter object for each application context.
+ * There is a 1-1 relationship between application context and
+ * NfcAdapter object.
+ */
+ static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class
+
+ /**
+ * NfcAdapter used with a null context. This ctor was deprecated but we have
+ * to support it for backwards compatibility. New methods that require context
+ * might throw when called on the null-context NfcAdapter.
*/
- static NfcAdapter sSingleton; // protected by NfcAdapter.class
+ static NfcAdapter sNullContextNfcAdapter; // protected by NfcAdapter.class
final NfcActivityManager mNfcActivityManager;
+ final Context mContext;
/**
* A callback to be invoked when the system successfully delivers your {@link NdefMessage}
@@ -280,12 +288,12 @@ public final class NfcAdapter {
}
/**
- * Returns the singleton, or throws if NFC is not available.
+ * Returns the NfcAdapter for application context,
+ * or throws if NFC is not available.
+ * @hide
*/
- static synchronized NfcAdapter getSingleton() {
+ public static synchronized NfcAdapter getNfcAdapter(Context context) {
if (!sIsInitialized) {
- sIsInitialized = true;
-
/* is this device meant to have NFC */
if (!hasNfcFeature()) {
Log.v(TAG, "this device does not have NFC support");
@@ -303,12 +311,21 @@ public final class NfcAdapter {
Log.e(TAG, "could not retrieve NFC Tag service");
throw new UnsupportedOperationException();
}
- sSingleton = new NfcAdapter();
+
+ sIsInitialized = true;
+ }
+ if (context == null) {
+ if (sNullContextNfcAdapter == null) {
+ sNullContextNfcAdapter = new NfcAdapter(null);
+ }
+ return sNullContextNfcAdapter;
}
- if (sSingleton == null) {
- throw new UnsupportedOperationException();
+ NfcAdapter adapter = sNfcAdapters.get(context);
+ if (adapter == null) {
+ adapter = new NfcAdapter(context);
+ sNfcAdapters.put(context, adapter);
}
- return sSingleton;
+ return adapter;
}
/** get handle to NFC service interface */
@@ -336,32 +353,45 @@ public final class NfcAdapter {
* @return the default NFC adapter, or null if no NFC adapter exists
*/
public static NfcAdapter getDefaultAdapter(Context context) {
+ if (context == null) {
+ throw new IllegalArgumentException("context cannot be null");
+ }
+ context = context.getApplicationContext();
/* use getSystemService() instead of just instantiating to take
* advantage of the context's cached NfcManager & NfcAdapter */
NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE);
+ if (manager == null) {
+ // NFC not available
+ return null;
+ }
return manager.getDefaultAdapter();
}
/**
- * Get a handle to the default NFC Adapter on this Android device.
- * <p>
- * Most Android devices will only have one NFC Adapter (NFC Controller).
- *
- * @return the default NFC adapter, or null if no NFC adapter exists
+ * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p>
+ * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required
+ * for many NFC API methods. Those methods will fail when called on an NfcAdapter
+ * object created from this method.<p>
* @deprecated use {@link #getDefaultAdapter(Context)}
*/
@Deprecated
public static NfcAdapter getDefaultAdapter() {
Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " +
"NfcAdapter.getDefaultAdapter(Context) instead", new Exception());
- return getSingleton();
+
+ return NfcAdapter.getNfcAdapter(null);
+ }
+
+ NfcAdapter(Context context) {
+ mContext = context;
+ mNfcActivityManager = new NfcActivityManager(this);
}
/**
- * Does not currently need a context.
+ * @hide
*/
- NfcAdapter() {
- mNfcActivityManager = new NfcActivityManager(this);
+ public Context getContext() {
+ return mContext;
}
/**
@@ -875,8 +905,12 @@ public final class NfcAdapter {
* @hide
*/
public INfcAdapterExtras getNfcAdapterExtrasInterface() {
+ if (mContext == null) {
+ throw new UnsupportedOperationException("You need a context on NfcAdapter to use the "
+ + " NFC extras APIs");
+ }
try {
- return sService.getNfcAdapterExtrasInterface();
+ return sService.getNfcAdapterExtrasInterface(mContext.getPackageName());
} catch (RemoteException e) {
attemptDeadServiceRecovery(e);
return null;
diff --git a/core/java/android/nfc/NfcManager.java b/core/java/android/nfc/NfcManager.java
index 300ab45..6ec2e21 100644
--- a/core/java/android/nfc/NfcManager.java
+++ b/core/java/android/nfc/NfcManager.java
@@ -39,8 +39,9 @@ public final class NfcManager {
*/
public NfcManager(Context context) {
NfcAdapter adapter;
+ context = context.getApplicationContext();
try {
- adapter = NfcAdapter.getSingleton();
+ adapter = NfcAdapter.getNfcAdapter(context);
} catch (UnsupportedOperationException e) {
adapter = null;
}
diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java
index 17a882d..7d03494 100644
--- a/core/java/android/os/Build.java
+++ b/core/java/android/os/Build.java
@@ -277,7 +277,7 @@ public class Build {
public static final int HONEYCOMB_MR2 = 13;
/**
- * Android 4.0.
+ * October 2011: Android 4.0.
*
* <p>Applications targeting this or a later release will get these
* new changes in behavior:</p>
@@ -309,6 +309,11 @@ public class Build {
* </ul>
*/
public static final int ICE_CREAM_SANDWICH = 14;
+
+ /**
+ * Android 4.1.
+ */
+ public static final int ICE_CREAM_SANDWICH_MR1 = 15;
}
/** The type of build, like "user" or "eng". */
diff --git a/core/java/android/os/CountDownTimer.java b/core/java/android/os/CountDownTimer.java
index 0c5c615..15e6405 100644
--- a/core/java/android/os/CountDownTimer.java
+++ b/core/java/android/os/CountDownTimer.java
@@ -25,7 +25,7 @@ import android.util.Log;
* Example of showing a 30 second countdown in a text field:
*
* <pre class="prettyprint">
- * new CountdownTimer(30000, 1000) {
+ * new CountDownTimer(30000, 1000) {
*
* public void onTick(long millisUntilFinished) {
* mTextField.setText("seconds remaining: " + millisUntilFinished / 1000);
diff --git a/core/java/android/os/RemoteException.java b/core/java/android/os/RemoteException.java
index 9d76156..e30d24f 100644
--- a/core/java/android/os/RemoteException.java
+++ b/core/java/android/os/RemoteException.java
@@ -24,4 +24,8 @@ public class RemoteException extends AndroidException {
public RemoteException() {
super();
}
+
+ public RemoteException(String message) {
+ super(message);
+ }
}
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index 4d7a9bb..99f58a0 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -35,7 +35,6 @@ import dalvik.system.VMDebug;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
@@ -117,6 +116,14 @@ public final class StrictMode {
private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE);
/**
+ * Boolean system property to disable strict mode checks outright.
+ * Set this to 'true' to force disable; 'false' has no effect on other
+ * enable/disable policy.
+ * @hide
+ */
+ public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable";
+
+ /**
* The boolean system property to control screen flashes on violations.
*
* @hide
@@ -892,25 +899,31 @@ public final class StrictMode {
* @hide
*/
public static boolean conditionallyEnableDebugLogging() {
- boolean doFlashes = !amTheSystemServerProcess() &&
- SystemProperties.getBoolean(VISUAL_PROPERTY, IS_ENG_BUILD);
+ boolean doFlashes = SystemProperties.getBoolean(VISUAL_PROPERTY, false)
+ && !amTheSystemServerProcess();
+ final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false);
// For debug builds, log event loop stalls to dropbox for analysis.
// Similar logic also appears in ActivityThread.java for system apps.
- if (IS_USER_BUILD && !doFlashes) {
+ if (!doFlashes && (IS_USER_BUILD || suppress)) {
setCloseGuardEnabled(false);
return false;
}
+ // Eng builds have flashes on all the time. The suppression property
+ // overrides this, so we force the behavior only after the short-circuit
+ // check above.
+ if (IS_ENG_BUILD) {
+ doFlashes = true;
+ }
+
+ // Thread policy controls BlockGuard.
int threadPolicyMask = StrictMode.DETECT_DISK_WRITE |
StrictMode.DETECT_DISK_READ |
StrictMode.DETECT_NETWORK;
if (!IS_USER_BUILD) {
threadPolicyMask |= StrictMode.PENALTY_DROPBOX;
- if (IS_ENG_BUILD) {
- threadPolicyMask |= StrictMode.PENALTY_LOG;
- }
}
if (doFlashes) {
threadPolicyMask |= StrictMode.PENALTY_FLASH;
@@ -918,6 +931,8 @@ public final class StrictMode {
StrictMode.setThreadPolicyMask(threadPolicyMask);
+ // VM Policy controls CloseGuard, detection of Activity leaks,
+ // and instance counting.
if (IS_USER_BUILD) {
setCloseGuardEnabled(false);
} else {
diff --git a/core/java/android/os/TransactionTooLargeException.java b/core/java/android/os/TransactionTooLargeException.java
new file mode 100644
index 0000000..25f09e8
--- /dev/null
+++ b/core/java/android/os/TransactionTooLargeException.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2011 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.os;
+import android.os.RemoteException;
+
+/**
+ * The Binder transaction failed because it was too large.
+ * <p>
+ * During a remote procedure call, the arguments and the return value of the call
+ * are transferred as {@link Parcel} objects stored in the Binder transaction buffer.
+ * If the arguments or the return value are too large to fit in the transaction buffer,
+ * then the call will fail and {@link TransactionTooLargeException} will be thrown.
+ * </p><p>
+ * The Binder transaction buffer has a limited fixed size, currently 1Mb, which
+ * is shared by all transactions in progress for the process. Consequently this
+ * exception can be thrown when there are many transactions in progress even when
+ * most of the individual transactions are of moderate size.
+ * </p><p>
+ * There are two possible outcomes when a remote procedure call throws
+ * {@link TransactionTooLargeException}. Either the client was unable to send
+ * its request to the service (most likely if the arguments were too large to fit in
+ * the transaction buffer), or the service was unable to send its response back
+ * to the client (most likely if the return value was too large to fit
+ * in the transaction buffer). It is not possible to tell which of these outcomes
+ * actually occurred. The client should assume that a partial failure occurred.
+ * </p><p>
+ * The key to avoiding {@link TransactionTooLargeException} is to keep all
+ * transactions relatively small. Try to minimize the amount of memory needed to create
+ * a {@link Parcel} for the arguments and the return value of the remote procedure call.
+ * Avoid transferring huge arrays of strings or large bitmaps.
+ * If possible, try to break up big requests into smaller pieces.
+ * </p><p>
+ * If you are implementing a service, it may help to impose size or complexity
+ * contraints on the queries that clients can perform. For example, if the result set
+ * could become large, then don't allow the client to request more than a few records
+ * at a time. Alternately, instead of returning all of the available data all at once,
+ * return the essential information first and make the client ask for additional information
+ * later as needed.
+ * </p>
+ */
+public class TransactionTooLargeException extends RemoteException {
+ public TransactionTooLargeException() {
+ super();
+ }
+}
diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java
index 4b4d308..413150b 100644
--- a/core/java/android/provider/CalendarContract.java
+++ b/core/java/android/provider/CalendarContract.java
@@ -72,7 +72,7 @@ import android.util.Log;
* {@link Calendars#MAX_REMINDERS} which is set by the Sync Adapter that owns
* the given calendar. Reminders are specified in minutes before the event and
* have a type.</li>
- * <li>The {@link ExtendedProperties} table hold opaque data fields used by the
+ * <li>The {@link ExtendedProperties} table holds opaque data fields used by the
* sync adapter. The provider takes no action with items in this table except to
* delete them when their related events are deleted.</li>
* </ul>
@@ -300,8 +300,23 @@ public final class CalendarContract {
public static final String CALENDAR_COLOR = "calendar_color";
/**
+ * A key for looking up a color from the {@link Colors} table. NULL or
+ * an empty string are reserved for indicating that the calendar does
+ * not use a key for looking up the color. The provider will update
+ * {@link #CALENDAR_COLOR} automatically when a valid key is written to
+ * this column. The key must reference an existing row of the
+ * {@link Colors} table. @see Colors
+ * <P>
+ * Type: TEXT
+ * </P>
+ */
+ public static final String CALENDAR_COLOR_KEY = "calendar_color_index";
+
+ /**
* The display name of the calendar. Column name.
- * <P>Type: TEXT</P>
+ * <P>
+ * Type: TEXT
+ * </P>
*/
public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName";
@@ -392,6 +407,28 @@ public final class CalendarContract {
* <P>Type: TEXT</P>
*/
public static final String ALLOWED_REMINDERS = "allowedReminders";
+
+ /**
+ * A comma separated list of availability types supported for this
+ * calendar in the format "#,#,#". Valid types are
+ * {@link Events#AVAILABILITY_BUSY}, {@link Events#AVAILABILITY_FREE},
+ * {@link Events#AVAILABILITY_TENTATIVE}. Setting this field to only
+ * {@link Events#AVAILABILITY_BUSY} should be used to indicate that
+ * changing the availability is not supported.
+ *
+ */
+ public static final String ALLOWED_AVAILABILITY = "allowedAvailability";
+
+ /**
+ * A comma separated list of attendee types supported for this calendar
+ * in the format "#,#,#". Valid types are {@link Attendees#TYPE_NONE},
+ * {@link Attendees#TYPE_OPTIONAL}, {@link Attendees#TYPE_REQUIRED},
+ * {@link Attendees#TYPE_RESOURCE}. Setting this field to only
+ * {@link Attendees#TYPE_NONE} should be used to indicate that changing
+ * the attendee type is not supported.
+ *
+ */
+ public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes";
}
/**
@@ -527,6 +564,8 @@ public final class CalendarContract {
* <li>{@link #SYNC_EVENTS} set to 1</li>
* <li>{@link #CALENDAR_TIME_ZONE}</li>
* <li>{@link #ALLOWED_REMINDERS}</li>
+ * <li>{@link #ALLOWED_AVAILABILITY}</li>
+ * <li>{@link #ALLOWED_ATTENDEE_TYPES}</li>
* </ul>
* <dt><b>Update</b></dt>
* <dd>To perform an update on a calendar the {@link #_ID} of the calendar
@@ -566,6 +605,8 @@ public final class CalendarContract {
* <li>{@link #OWNER_ACCOUNT}</li>
* <li>{@link #MAX_REMINDERS}</li>
* <li>{@link #ALLOWED_REMINDERS}</li>
+ * <li>{@link #ALLOWED_AVAILABILITY}</li>
+ * <li>{@link #ALLOWED_ATTENDEE_TYPES}</li>
* <li>{@link #CAN_MODIFY_TIME_ZONE}</li>
* <li>{@link #CAN_ORGANIZER_RESPOND}</li>
* <li>{@link #CAN_PARTIALLY_UPDATE}</li>
@@ -688,13 +729,21 @@ public final class CalendarContract {
/**
* The type of attendee. Column name.
- * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})</P>
+ * <P>
+ * Type: Integer (one of {@link #TYPE_NONE}, {@link #TYPE_REQUIRED},
+ * {@link #TYPE_OPTIONAL}, {@link #TYPE_RESOURCE})
+ * </P>
*/
public static final String ATTENDEE_TYPE = "attendeeType";
public static final int TYPE_NONE = 0;
public static final int TYPE_REQUIRED = 1;
public static final int TYPE_OPTIONAL = 2;
+ /**
+ * This specifies that an attendee is a resource, like a room, a
+ * cabbage, or something and not an actual person.
+ */
+ public static final int TYPE_RESOURCE = 3;
/**
* The attendance status of the attendee. Column name.
@@ -787,13 +836,26 @@ public final class CalendarContract {
public static final String EVENT_LOCATION = "eventLocation";
/**
- * A secondary color for the individual event. Reserved for future use.
- * Column name.
+ * A secondary color for the individual event. This should only be
+ * updated by the sync adapter for a given account.
* <P>Type: INTEGER</P>
*/
public static final String EVENT_COLOR = "eventColor";
/**
+ * A secondary color key for the individual event. NULL or an empty
+ * string are reserved for indicating that the event does not use a key
+ * for looking up the color. The provider will update
+ * {@link #EVENT_COLOR} automatically when a valid key is written to
+ * this column. The key must reference an existing row of the
+ * {@link Colors} table. @see Colors
+ * <P>
+ * Type: TEXT
+ * </P>
+ */
+ public static final String EVENT_COLOR_KEY = "eventColor_index";
+
+ /**
* The event status. Column name.
* <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P>
*/
@@ -949,8 +1011,10 @@ public final class CalendarContract {
/**
* If this event counts as busy time or is still free time that can be
* scheduled over. Column name.
- * <P>Type: INTEGER (One of {@link #AVAILABILITY_BUSY},
- * {@link #AVAILABILITY_FREE})</P>
+ * <P>
+ * Type: INTEGER (One of {@link #AVAILABILITY_BUSY},
+ * {@link #AVAILABILITY_FREE}, {@link #AVAILABILITY_TENTATIVE})
+ * </P>
*/
public static final String AVAILABILITY = "availability";
@@ -964,6 +1028,11 @@ public final class CalendarContract {
* other events.
*/
public static final int AVAILABILITY_FREE = 1;
+ /**
+ * Indicates that the owner's availability may change, but should be
+ * considered busy time that will conflict.
+ */
+ public static final int AVAILABILITY_TENTATIVE = 2;
/**
* Whether the event has an alarm or not. Column name.
@@ -1335,7 +1404,10 @@ public final class CalendarContract {
* <dd>When inserting a new event the following fields must be included:
* <ul>
* <li>dtstart</li>
- * <li>dtend -or- a (rrule or rdate) and a duration</li>
+ * <li>dtend if the event is non-recurring</li>
+ * <li>duration if the event is recurring</li>
+ * <li>rrule or rdate if the event is recurring</li>
+ * <li>eventTimezone</li>
* <li>a calendar_id</li>
* </ul>
* There are also further requirements when inserting or updating an event.
@@ -1473,6 +1545,8 @@ public final class CalendarContract {
CAL_SYNC9,
CAL_SYNC10,
ALLOWED_REMINDERS,
+ ALLOWED_ATTENDEE_TYPES,
+ ALLOWED_AVAILABILITY,
CALENDAR_ACCESS_LEVEL,
CALENDAR_COLOR,
CALENDAR_TIME_ZONE,
@@ -2224,6 +2298,76 @@ public final class CalendarContract {
}
}
+ protected interface ColorsColumns extends SyncStateContract.Columns {
+
+ /**
+ * The type of color, which describes how it should be used. Valid types
+ * are {@link #TYPE_CALENDAR} and {@link #TYPE_EVENT}. Column name.
+ * <P>
+ * Type: INTEGER (NOT NULL)
+ * </P>
+ */
+ public static final String COLOR_TYPE = "color_type";
+
+ /**
+ * This indicateds a color that can be used for calendars.
+ */
+ public static final int TYPE_CALENDAR = 0;
+ /**
+ * This indicates a color that can be used for events.
+ */
+ public static final int TYPE_EVENT = 1;
+
+ /**
+ * The key used to reference this color. This can be any non-empty
+ * string, but must be unique for a given {@link #ACCOUNT_TYPE} and
+ * {@link #ACCOUNT_NAME}. Column name.
+ * <P>
+ * Type: TEXT
+ * </P>
+ */
+ public static final String COLOR_KEY = "color_index";
+
+ /**
+ * The color as an 8-bit ARGB integer value. Colors should specify alpha
+ * as fully opaque (eg 0xFF993322) as the alpha may be ignored or
+ * modified for display. It is reccomended that colors be usable with
+ * light (near white) text. Apps should not depend on that assumption,
+ * however. Column name.
+ * <P>
+ * Type: INTEGER (NOT NULL)
+ * </P>
+ */
+ public static final String COLOR = "color";
+
+ }
+
+ /**
+ * Fields for accessing colors available for a given account. Colors are
+ * referenced by {@link #COLOR_KEY} which must be unique for a given
+ * account name/type. These values can only be updated by the sync
+ * adapter. Only {@link #COLOR} may be updated after the initial insert. In
+ * addition, a row can only be deleted once all references to that color
+ * have been removed from the {@link Calendars} or {@link Events} tables.
+ */
+ public static final class Colors implements ColorsColumns {
+ /**
+ * @hide
+ */
+ public static final String TABLE_NAME = "Colors";
+ /**
+ * The Uri for querying color information
+ */
+ @SuppressWarnings("hiding")
+ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/colors");
+
+ /**
+ * This utility class cannot be instantiated
+ */
+ private Colors() {
+ }
+ }
+
protected interface ExtendedPropertiesColumns {
/**
* The event the extended property belongs to. Column name.
@@ -2247,7 +2391,7 @@ public final class CalendarContract {
/**
* Fields for accessing the Extended Properties. This is a generic set of
- * name/value pairs for use by sync adapters or apps to add extra
+ * name/value pairs for use by sync adapters to add extra
* information to events. There are three writable columns and all three
* must be present when inserting a new value. They are:
* <ul>
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 4bc0892..83acef8 100644
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -187,6 +187,16 @@ public final class ContactsContract {
public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query";
/**
+ * A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI},
+ * {@link CommonDataKinds.Email#CONTENT_URI}, and
+ * {@link CommonDataKinds.StructuredPostal#CONTENT_URI}.
+ * This enables a content provider to remove duplicate entries in results.
+ *
+ * @hide
+ */
+ public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries";
+
+ /**
* <p>
* API for obtaining a pre-authorized version of a URI that normally requires special
* permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI
@@ -1673,7 +1683,6 @@ public final class ContactsContract {
* Querying for social stream data requires android.permission.READ_SOCIAL_STREAM
* permission.
* </p>
- * @hide
*/
public static final class StreamItems implements StreamItemsColumns {
/**
@@ -2736,7 +2745,6 @@ public final class ContactsContract {
* inserting or updating social stream items requires android.permission.WRITE_SOCIAL_STREAM
* permission.
* </p>
- * @hide
*/
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
@@ -3149,7 +3157,6 @@ public final class ContactsContract {
* </pre>
* </dd>
* </dl>
- * @hide
*/
public static final class StreamItems implements BaseColumns, StreamItemsColumns {
/**
@@ -3247,7 +3254,6 @@ public final class ContactsContract {
* Columns in the StreamItems table.
*
* @see ContactsContract.StreamItems
- * @hide
*/
protected interface StreamItemsColumns {
/**
@@ -3538,7 +3544,6 @@ public final class ContactsContract {
* <pre>
* </dd>
* </dl>
- * @hide
*/
public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns {
/**
@@ -3566,7 +3571,6 @@ public final class ContactsContract {
* Columns in the StreamItemPhotos table.
*
* @see ContactsContract.StreamItemPhotos
- * @hide
*/
protected interface StreamItemPhotosColumns {
/**
@@ -4666,6 +4670,13 @@ public final class ContactsContract {
* @hide
*/
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone_lookup";
+
+ /**
+ * Boolean parameter that is used to look up a SIP address.
+ *
+ * @hide
+ */
+ public static final String QUERY_PARAMETER_SIP_ADDRESS = "sip";
}
/**
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index a0652f7..b032169 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -1183,6 +1183,10 @@ public final class Settings {
public static final String RADIO_WIFI = "wifi";
/**
+ * {@hide}
+ */
+ public static final String RADIO_WIMAX = "wimax";
+ /**
* Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio.
*/
public static final String RADIO_CELL = "cell";
@@ -2899,6 +2903,11 @@ public final class Settings {
*/
public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON =
"wifi_networks_available_notification_on";
+ /**
+ * {@hide}
+ */
+ public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON =
+ "wimax_networks_available_notification_on";
/**
* Delay (in seconds) before repeating the Wi-Fi networks available notification.
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 79995d0..0e6d07d 100755
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -1271,9 +1271,6 @@ public final class Telephony {
Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(),
uri, ID_PROJECTION, null, null, null);
- if (DEBUG) {
- Log.v(TAG, "getOrCreateThreadId cursor cnt: " + cursor.getCount());
- }
if (cursor != null) {
try {
if (cursor.moveToFirst()) {
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java
index 1b473ec..a2038c9 100644
--- a/core/java/android/server/BluetoothEventLoop.java
+++ b/core/java/android/server/BluetoothEventLoop.java
@@ -784,11 +784,12 @@ class BluetoothEventLoop {
// machine. We don't handle AVCTP signals currently. We only send
// intents for AVDTP state changes. We need to handle both of them in
// some cases. For now, just don't move to incoming state in this case.
- mBluetoothService.notifyIncomingA2dpConnection(address);
+ mBluetoothService.notifyIncomingA2dpConnection(address, false);
} else {
Log.i(TAG, "" + authorized +
"Incoming A2DP / AVRCP connection from " + address);
mA2dp.allowIncomingConnect(device, authorized);
+ mBluetoothService.notifyIncomingA2dpConnection(address, true);
}
} else if (BluetoothUuid.isInputDevice(uuid)) {
// We can have more than 1 input device connected.
diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java
index 5ea8e2a..28e231e 100755
--- a/core/java/android/server/BluetoothService.java
+++ b/core/java/android/server/BluetoothService.java
@@ -89,7 +89,7 @@ public class BluetoothService extends IBluetooth.Stub {
private int mNativeData;
private BluetoothEventLoop mEventLoop;
- private BluetoothHeadset mBluetoothHeadset;
+ private BluetoothHeadset mHeadsetProxy;
private BluetoothInputDevice mInputDevice;
private BluetoothPan mPan;
private boolean mIsAirplaneSensitive;
@@ -605,6 +605,7 @@ public class BluetoothService extends IBluetooth.Stub {
}
mBondState.initBondState();
initProfileState();
+ getProfileProxy();
}
/**
@@ -1766,8 +1767,8 @@ public class BluetoothService extends IBluetooth.Stub {
private void dumpHeadsetService(PrintWriter pw) {
pw.println("\n--Headset Service--");
- if (mBluetoothHeadset != null) {
- List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+ if (mHeadsetProxy != null) {
+ List<BluetoothDevice> deviceList = mHeadsetProxy.getConnectedDevices();
if (deviceList.size() == 0) {
pw.println("No headsets connected");
} else {
@@ -1775,21 +1776,20 @@ public class BluetoothService extends IBluetooth.Stub {
pw.println("\ngetConnectedDevices[0] = " + device);
dumpHeadsetConnectionState(pw, device);
pw.println("getBatteryUsageHint() = " +
- mBluetoothHeadset.getBatteryUsageHint(device));
+ mHeadsetProxy.getBatteryUsageHint(device));
}
deviceList.clear();
- deviceList = mBluetoothHeadset.getDevicesMatchingConnectionStates(new int[] {
+ deviceList = mHeadsetProxy.getDevicesMatchingConnectionStates(new int[] {
BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED});
pw.println("--Connected and Disconnected Headsets");
for (BluetoothDevice device: deviceList) {
pw.println(device);
- if (mBluetoothHeadset.isAudioConnected(device)) {
+ if (mHeadsetProxy.isAudioConnected(device)) {
pw.println("SCO audio connected to device:" + device);
}
}
}
- mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
}
private void dumpInputDeviceProfile(PrintWriter pw) {
@@ -1824,7 +1824,6 @@ public class BluetoothService extends IBluetooth.Stub {
pw.println(device);
}
}
- mAdapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mBluetoothHeadset);
}
private void dumpPanProfile(PrintWriter pw) {
@@ -1862,7 +1861,7 @@ public class BluetoothService extends IBluetooth.Stub {
private void dumpHeadsetConnectionState(PrintWriter pw,
BluetoothDevice device) {
- switch (mBluetoothHeadset.getConnectionState(device)) {
+ switch (mHeadsetProxy.getConnectionState(device)) {
case BluetoothHeadset.STATE_CONNECTING:
pw.println("getConnectionState() = STATE_CONNECTING");
break;
@@ -1884,7 +1883,6 @@ public class BluetoothService extends IBluetooth.Stub {
Integer pid = mServiceRecordToPid.get(handle).first;
pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle));
}
- mAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothHeadset);
}
private void dumpAclConnectedDevices(PrintWriter pw) {
@@ -1927,11 +1925,16 @@ public class BluetoothService extends IBluetooth.Stub {
}
}
+ private void getProfileProxy() {
+ mAdapter.getProfileProxy(mContext,
+ mBluetoothProfileServiceListener, BluetoothProfile.HEADSET);
+ }
+
private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
if (profile == BluetoothProfile.HEADSET) {
- mBluetoothHeadset = (BluetoothHeadset) proxy;
+ mHeadsetProxy = (BluetoothHeadset) proxy;
} else if (profile == BluetoothProfile.INPUT_DEVICE) {
mInputDevice = (BluetoothInputDevice) proxy;
} else if (profile == BluetoothProfile.PAN) {
@@ -1940,7 +1943,7 @@ public class BluetoothService extends IBluetooth.Stub {
}
public void onServiceDisconnected(int profile) {
if (profile == BluetoothProfile.HEADSET) {
- mBluetoothHeadset = null;
+ mHeadsetProxy = null;
} else if (profile == BluetoothProfile.INPUT_DEVICE) {
mInputDevice = null;
} else if (profile == BluetoothProfile.PAN) {
@@ -2424,25 +2427,43 @@ public class BluetoothService extends IBluetooth.Stub {
}
}
- public boolean notifyIncomingConnection(String address) {
- BluetoothDeviceProfileState state =
- mDeviceProfileState.get(address);
+ public boolean notifyIncomingConnection(String address, boolean rejected) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
if (state != null) {
Message msg = new Message();
- msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING;
- state.sendMessage(msg);
+ if (rejected) {
+ if (mA2dpService.getPriority(getRemoteDevice(address)) >=
+ BluetoothProfile.PRIORITY_ON) {
+ msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES;
+ msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING;
+ state.sendMessageDelayed(msg,
+ BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY);
+ }
+ } else {
+ msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING;
+ state.sendMessage(msg);
+ }
return true;
}
return false;
}
- /*package*/ boolean notifyIncomingA2dpConnection(String address) {
- BluetoothDeviceProfileState state =
- mDeviceProfileState.get(address);
+ /*package*/ boolean notifyIncomingA2dpConnection(String address, boolean rejected) {
+ BluetoothDeviceProfileState state = mDeviceProfileState.get(address);
if (state != null) {
Message msg = new Message();
- msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING;
- state.sendMessage(msg);
+ if (rejected) {
+ if (mHeadsetProxy.getPriority(getRemoteDevice(address)) >=
+ BluetoothProfile.PRIORITY_ON) {
+ msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES;
+ msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING;
+ state.sendMessageDelayed(msg,
+ BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY);
+ }
+ } else {
+ msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING;
+ state.sendMessage(msg);
+ }
return true;
}
return false;
diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java
index 2ecf307..28251a6 100644
--- a/core/java/android/service/textservice/SpellCheckerService.java
+++ b/core/java/android/service/textservice/SpellCheckerService.java
@@ -146,6 +146,14 @@ public abstract class SpellCheckerService extends Service {
public void onCancel() {}
/**
+ * Request to close this session.
+ * This function will run on the incoming IPC thread.
+ * So, this is not called on the main thread,
+ * but will be called in series on another thread.
+ */
+ public void onClose() {}
+
+ /**
* @return Locale for this session
*/
public String getLocale() {
@@ -162,7 +170,7 @@ public abstract class SpellCheckerService extends Service {
// Preventing from exposing ISpellCheckerSession.aidl, create an internal class.
private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub {
- private final ISpellCheckerSessionListener mListener;
+ private ISpellCheckerSessionListener mListener;
private final Session mSession;
private final String mLocale;
private final Bundle mBundle;
@@ -192,6 +200,12 @@ public abstract class SpellCheckerService extends Service {
mSession.onCancel();
}
+ @Override
+ public void onClose() {
+ mSession.onClose();
+ mListener = null;
+ }
+
public String getLocale() {
return mLocale;
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index ba94ab2..a9a628a 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -148,7 +148,10 @@ public abstract class WallpaperService extends Service {
int mCurWidth;
int mCurHeight;
int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
+ int mWindowPrivateFlags =
+ WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
int mCurWindowFlags = mWindowFlags;
+ int mCurWindowPrivateFlags = mWindowPrivateFlags;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
final Rect mContentInsets = new Rect();
@@ -359,6 +362,25 @@ public abstract class WallpaperService extends Service {
updateSurface(false, false, false);
}
}
+
+ /**
+ * Control whether this wallpaper will receive notifications when the wallpaper
+ * has been scrolled. By default, wallpapers will receive notifications, although
+ * the default static image wallpapers do not. It is a performance optimization to
+ * set this to false.
+ *
+ * @param enabled whether the wallpaper wants to receive offset notifications
+ */
+ public void setOffsetNotificationsEnabled(boolean enabled) {
+ mWindowPrivateFlags = enabled
+ ? (mWindowPrivateFlags |
+ WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS)
+ : (mWindowPrivateFlags &
+ ~WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS);
+ if (mCreated) {
+ updateSurface(false, false, false);
+ }
+ }
/**
* Called once to initialize the engine. After returning, the
@@ -478,6 +500,8 @@ public abstract class WallpaperService extends Service {
out.print(prefix); out.print("mType="); out.print(mType);
out.print(" mWindowFlags="); out.print(mWindowFlags);
out.print(" mCurWindowFlags="); out.println(mCurWindowFlags);
+ out.print(" mWindowPrivateFlags="); out.print(mWindowPrivateFlags);
+ out.print(" mCurWindowPrivateFlags="); out.println(mCurWindowPrivateFlags);
out.print(prefix); out.print("mVisibleInsets=");
out.print(mVisibleInsets.toShortString());
out.print(" mWinFrame="); out.print(mWinFrame.toShortString());
@@ -528,7 +552,8 @@ public abstract class WallpaperService extends Service {
final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat();
boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean typeChanged = mType != mSurfaceHolder.getRequestedType();
- final boolean flagsChanged = mCurWindowFlags != mWindowFlags;
+ final boolean flagsChanged = mCurWindowFlags != mWindowFlags ||
+ mCurWindowPrivateFlags != mWindowPrivateFlags;
if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged
|| typeChanged || flagsChanged || redrawNeeded) {
@@ -554,6 +579,8 @@ public abstract class WallpaperService extends Service {
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
;
+ mCurWindowPrivateFlags = mWindowPrivateFlags;
+ mLayout.privateFlags = mWindowPrivateFlags;
mLayout.memoryType = mType;
mLayout.token = mWindowToken;
diff --git a/core/java/android/speech/tts/AudioMessageParams.java b/core/java/android/speech/tts/AudioMessageParams.java
index 68d8738..29b4367 100644
--- a/core/java/android/speech/tts/AudioMessageParams.java
+++ b/core/java/android/speech/tts/AudioMessageParams.java
@@ -15,12 +15,12 @@
*/
package android.speech.tts;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
class AudioMessageParams extends MessageParams {
private final BlockingMediaPlayer mPlayer;
- AudioMessageParams(UtteranceCompletedDispatcher dispatcher,
+ AudioMessageParams(UtteranceProgressDispatcher dispatcher,
String callingApp, BlockingMediaPlayer player) {
super(dispatcher, callingApp);
mPlayer = player;
diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java
index d970ae6..0194240 100644
--- a/core/java/android/speech/tts/AudioPlaybackHandler.java
+++ b/core/java/android/speech/tts/AudioPlaybackHandler.java
@@ -312,10 +312,11 @@ class AudioPlaybackHandler {
private void handleSilence(MessageParams msg) {
if (DBG) Log.d(TAG, "handleSilence()");
SilenceMessageParams params = (SilenceMessageParams) msg;
+ params.getDispatcher().dispatchOnStart();
if (params.getSilenceDurationMs() > 0) {
params.getConditionVariable().block(params.getSilenceDurationMs());
}
- params.getDispatcher().dispatchUtteranceCompleted();
+ params.getDispatcher().dispatchOnDone();
if (DBG) Log.d(TAG, "handleSilence() done.");
}
@@ -323,11 +324,12 @@ class AudioPlaybackHandler {
private void handleAudio(MessageParams msg) {
if (DBG) Log.d(TAG, "handleAudio()");
AudioMessageParams params = (AudioMessageParams) msg;
+ params.getDispatcher().dispatchOnStart();
// Note that the BlockingMediaPlayer spawns a separate thread.
//
// TODO: This can be avoided.
params.getPlayer().startAndWait();
- params.getDispatcher().dispatchUtteranceCompleted();
+ params.getDispatcher().dispatchOnDone();
if (DBG) Log.d(TAG, "handleAudio() done.");
}
@@ -361,6 +363,7 @@ class AudioPlaybackHandler {
if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]");
param.setAudioTrack(audioTrack);
+ msg.getDispatcher().dispatchOnStart();
}
// More data available to be flushed to the audio track.
@@ -411,6 +414,7 @@ class AudioPlaybackHandler {
final AudioTrack audioTrack = params.getAudioTrack();
if (audioTrack == null) {
+ params.getDispatcher().dispatchOnError();
return;
}
@@ -439,7 +443,7 @@ class AudioPlaybackHandler {
audioTrack.release();
params.setAudioTrack(null);
}
- params.getDispatcher().dispatchUtteranceCompleted();
+ params.getDispatcher().dispatchOnDone();
mLastSynthesisRequest = null;
params.mLogger.onWriteData();
}
diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java
index 5808919..04c3377 100644
--- a/core/java/android/speech/tts/FileSynthesisCallback.java
+++ b/core/java/android/speech/tts/FileSynthesisCallback.java
@@ -16,10 +16,10 @@
package android.speech.tts;
import android.media.AudioFormat;
+import android.os.FileUtils;
import android.util.Log;
import java.io.File;
-import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
@@ -63,7 +63,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
* Must be called while holding the monitor on {@link #mStateLock}.
*/
private void cleanUp() {
- closeFile();
+ closeFileAndWidenPermissions();
if (mFile != null) {
mFileName.delete();
}
@@ -72,7 +72,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
/**
* Must be called while holding the monitor on {@link #mStateLock}.
*/
- private void closeFile() {
+ private void closeFileAndWidenPermissions() {
try {
if (mFile != null) {
mFile.close();
@@ -81,6 +81,18 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
} catch (IOException ex) {
Log.e(TAG, "Failed to close " + mFileName + ": " + ex);
}
+
+ try {
+ // Make the written file readable and writeable by everyone.
+ // This allows the app that requested synthesis to read the file.
+ //
+ // Note that the directory this file was written must have already
+ // been world writeable in order it to have been
+ // written to in the first place.
+ FileUtils.setPermissions(mFileName.getAbsolutePath(), 0666, -1, -1); //-rw-rw-rw
+ } catch (SecurityException se) {
+ Log.e(TAG, "Security exception setting rw permissions on : " + mFileName);
+ }
}
@Override
@@ -168,7 +180,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback {
int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH);
mFile.write(
makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength));
- closeFile();
+ closeFileAndWidenPermissions();
mDone = true;
return TextToSpeech.SUCCESS;
} catch (IOException ex) {
diff --git a/core/java/android/speech/tts/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
index 40902ae..f0287d4 100755
--- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl
@@ -21,5 +21,7 @@ package android.speech.tts;
* {@hide}
*/
oneway interface ITextToSpeechCallback {
- void utteranceCompleted(String utteranceId);
+ void onStart(String utteranceId);
+ void onDone(String utteranceId);
+ void onError(String utteranceId);
}
diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl
index ff3fa11..1a8c1fb 100644
--- a/core/java/android/speech/tts/ITextToSpeechService.aidl
+++ b/core/java/android/speech/tts/ITextToSpeechService.aidl
@@ -114,6 +114,21 @@ interface ITextToSpeechService {
int isLanguageAvailable(in String lang, in String country, in String variant);
/**
+ * Returns a list of features available for a given language. Elements of the returned
+ * string array can be passed in as keys to {@link TextToSpeech#speak} and
+ * {@link TextToSpeech#synthesizeToFile} to select a given feature or features to be
+ * used during synthesis.
+ *
+ * @param lang ISO-3 language code.
+ * @param country ISO-3 country code. May be empty or null.
+ * @param variant Language variant. May be empty or null.
+ * @return An array of strings containing the set of features supported for
+ * the supplied locale. The array of strings must not contain
+ * duplicates.
+ */
+ String[] getFeaturesForLanguage(in String lang, in String country, in String variant);
+
+ /**
* Notifies the engine that it should load a speech synthesis language.
*
* @param lang ISO-3 language code.
diff --git a/core/java/android/speech/tts/MessageParams.java b/core/java/android/speech/tts/MessageParams.java
index e7d6da3..de9cc07 100644
--- a/core/java/android/speech/tts/MessageParams.java
+++ b/core/java/android/speech/tts/MessageParams.java
@@ -15,22 +15,22 @@
*/
package android.speech.tts;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
abstract class MessageParams {
static final int TYPE_SYNTHESIS = 1;
static final int TYPE_AUDIO = 2;
static final int TYPE_SILENCE = 3;
- private final UtteranceCompletedDispatcher mDispatcher;
+ private final UtteranceProgressDispatcher mDispatcher;
private final String mCallingApp;
- MessageParams(UtteranceCompletedDispatcher dispatcher, String callingApp) {
+ MessageParams(UtteranceProgressDispatcher dispatcher, String callingApp) {
mDispatcher = dispatcher;
mCallingApp = callingApp;
}
- UtteranceCompletedDispatcher getDispatcher() {
+ UtteranceProgressDispatcher getDispatcher() {
return mDispatcher;
}
diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
index 0cca06a..ce3522b 100644
--- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java
+++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java
@@ -15,7 +15,7 @@
*/
package android.speech.tts;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
import android.util.Log;
/**
@@ -62,12 +62,12 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback {
private volatile boolean mDone = false;
- private final UtteranceCompletedDispatcher mDispatcher;
+ private final UtteranceProgressDispatcher mDispatcher;
private final String mCallingApp;
private final EventLogger mLogger;
PlaybackSynthesisCallback(int streamType, float volume, float pan,
- AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher,
+ AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher,
String callingApp, EventLogger logger) {
mStreamType = streamType;
mVolume = volume;
diff --git a/core/java/android/speech/tts/SilenceMessageParams.java b/core/java/android/speech/tts/SilenceMessageParams.java
index 7a4ff1c..9909126 100644
--- a/core/java/android/speech/tts/SilenceMessageParams.java
+++ b/core/java/android/speech/tts/SilenceMessageParams.java
@@ -16,13 +16,13 @@
package android.speech.tts;
import android.os.ConditionVariable;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
class SilenceMessageParams extends MessageParams {
private final ConditionVariable mCondVar = new ConditionVariable();
private final long mSilenceDurationMs;
- SilenceMessageParams(UtteranceCompletedDispatcher dispatcher,
+ SilenceMessageParams(UtteranceProgressDispatcher dispatcher,
String callingApp, long silenceDurationMs) {
super(dispatcher, callingApp);
mSilenceDurationMs = silenceDurationMs;
diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java
index 779721e..0c0f033 100644
--- a/core/java/android/speech/tts/SynthesisMessageParams.java
+++ b/core/java/android/speech/tts/SynthesisMessageParams.java
@@ -17,7 +17,7 @@ package android.speech.tts;
import android.media.AudioFormat;
import android.media.AudioTrack;
-import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher;
+import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher;
import java.util.LinkedList;
@@ -56,7 +56,7 @@ final class SynthesisMessageParams extends MessageParams {
SynthesisMessageParams(int streamType, int sampleRate,
int audioFormat, int channelCount,
- float volume, float pan, UtteranceCompletedDispatcher dispatcher,
+ float volume, float pan, UtteranceProgressDispatcher dispatcher,
String callingApp, EventLogger logger) {
super(dispatcher, callingApp);
diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java
index 98ab310..38699ea 100755
--- a/core/java/android/speech/tts/TextToSpeech.java
+++ b/core/java/android/speech/tts/TextToSpeech.java
@@ -31,10 +31,13 @@ import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
+import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
/**
*
@@ -147,7 +150,25 @@ public class TextToSpeech {
}
/**
- * Constants and parameter names for controlling text-to-speech.
+ * Constants and parameter names for controlling text-to-speech. These include:
+ *
+ * <ul>
+ * <li>
+ * Intents to ask engine to install data or check its data and
+ * extras for a TTS engine's check data activity.
+ * </li>
+ * <li>
+ * Keys for the parameters passed with speak commands, e.g.
+ * {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}.
+ * </li>
+ * <li>
+ * A list of feature strings that engines might support, e.g
+ * {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to
+ * {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify
+ * engine behaviour. The engine can be queried for the set of features it supports
+ * through {@link TextToSpeech#getFeatures(java.util.Locale)}.
+ * </li>
+ * </ul>
*/
public class Engine {
@@ -435,6 +456,25 @@ public class TextToSpeech {
*/
public static final String KEY_PARAM_PAN = "pan";
+ /**
+ * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)}
+ * for a description of how feature keys work. If set (and supported by the engine
+ * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must
+ * use network based synthesis.
+ *
+ * @see TextToSpeech#speak(String, int, java.util.HashMap)
+ * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)
+ * @see TextToSpeech#getFeatures(java.util.Locale)
+ */
+ public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts";
+
+ /**
+ * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)}
+ * for a description of how feature keys work. If set and supported by the engine
+ * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize
+ * text on-device (without making network requests).
+ */
+ public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts";
}
private final Context mContext;
@@ -442,7 +482,7 @@ public class TextToSpeech {
private OnInitListener mInitListener;
// Written from an unspecified application thread, read from
// a binder thread.
- private volatile OnUtteranceCompletedListener mUtteranceCompletedListener;
+ private volatile UtteranceProgressListener mUtteranceProgressListener;
private final Object mStartLock = new Object();
private String mRequestedEngine;
@@ -812,6 +852,36 @@ public class TextToSpeech {
}
/**
+ * Queries the engine for the set of features it supports for a given locale.
+ * Features can either be framework defined, e.g.
+ * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific.
+ * Engine specific keys must be prefixed by the name of the engine they
+ * are intended for. These keys can be used as parameters to
+ * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and
+ * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}.
+ *
+ * Features are boolean flags, and their values in the synthesis parameters
+ * must be behave as per {@link Boolean#parseBoolean(String)}.
+ *
+ * @param locale The locale to query features for.
+ */
+ public Set<String> getFeatures(final Locale locale) {
+ return runAction(new Action<Set<String>>() {
+ @Override
+ public Set<String> run(ITextToSpeechService service) throws RemoteException {
+ String[] features = service.getFeaturesForLanguage(
+ locale.getISO3Language(), locale.getISO3Country(), locale.getVariant());
+ if (features != null) {
+ final Set<String> featureSet = new HashSet<String>();
+ Collections.addAll(featureSet, features);
+ return featureSet;
+ }
+ return null;
+ }
+ }, null, "getFeatures");
+ }
+
+ /**
* Checks whether the TTS engine is busy speaking. Note that a speech item is
* considered complete once it's audio data has been sent to the audio mixer, or
* written to a file. There might be a finite lag between this point, and when
@@ -1017,6 +1087,10 @@ public class TextToSpeech {
copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME);
copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN);
+ // Copy feature strings defined by the framework.
+ copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS);
+ copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS);
+
// Copy over all parameters that start with the name of the
// engine that we are currently connected to. The engine is
// free to interpret them as it chooses.
@@ -1072,9 +1146,28 @@ public class TextToSpeech {
* @param listener The listener to use.
*
* @return {@link #ERROR} or {@link #SUCCESS}.
+ *
+ * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)}
+ * instead.
*/
+ @Deprecated
public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) {
- mUtteranceCompletedListener = listener;
+ mUtteranceProgressListener = UtteranceProgressListener.from(listener);
+ return TextToSpeech.SUCCESS;
+ }
+
+ /**
+ * Sets the listener that will be notified of various events related to the
+ * synthesis of a given utterance.
+ *
+ * See {@link UtteranceProgressListener} and
+ * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
+ *
+ * @param listener the listener to use.
+ * @return {@link #ERROR} or {@link #SUCCESS}
+ */
+ public int setOnUtteranceProgressListener(UtteranceProgressListener listener) {
+ mUtteranceProgressListener = listener;
return TextToSpeech.SUCCESS;
}
@@ -1130,10 +1223,26 @@ public class TextToSpeech {
private ITextToSpeechService mService;
private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() {
@Override
- public void utteranceCompleted(String utteranceId) {
- OnUtteranceCompletedListener listener = mUtteranceCompletedListener;
+ public void onDone(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onDone(utteranceId);
+ }
+ }
+
+ @Override
+ public void onError(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
+ if (listener != null) {
+ listener.onError(utteranceId);
+ }
+ }
+
+ @Override
+ public void onStart(String utteranceId) {
+ UtteranceProgressListener listener = mUtteranceProgressListener;
if (listener != null) {
- listener.onUtteranceCompleted(utteranceId);
+ listener.onStart(utteranceId);
}
}
};
diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java
index 48739ba..39922da 100644
--- a/core/java/android/speech/tts/TextToSpeechService.java
+++ b/core/java/android/speech/tts/TextToSpeechService.java
@@ -36,6 +36,7 @@ import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Locale;
+import java.util.Set;
/**
@@ -67,7 +68,6 @@ import java.util.Locale;
* any. Any pending data from the current synthesis will be discarded.
*
*/
-// TODO: Add a link to the sample TTS engine once it's done.
public abstract class TextToSpeechService extends Service {
private static final boolean DBG = false;
@@ -196,6 +196,18 @@ public abstract class TextToSpeechService extends Service {
protected abstract void onSynthesizeText(SynthesisRequest request,
SynthesisCallback callback);
+ /**
+ * Queries the service for a set of features supported for a given language.
+ *
+ * @param lang ISO-3 language code.
+ * @param country ISO-3 country code. May be empty or null.
+ * @param variant Language variant. May be empty or null.
+ * @return A list of features supported for the given language.
+ */
+ protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) {
+ return null;
+ }
+
private int getDefaultSpeechRate() {
return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE);
}
@@ -294,6 +306,7 @@ public abstract class TextToSpeechService extends Service {
*/
public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) {
if (!speechItem.isValid()) {
+ speechItem.dispatchOnError();
return TextToSpeech.ERROR;
}
@@ -320,6 +333,7 @@ public abstract class TextToSpeechService extends Service {
return TextToSpeech.SUCCESS;
} else {
Log.w(TAG, "SynthThread has quit");
+ speechItem.dispatchOnError();
return TextToSpeech.ERROR;
}
}
@@ -369,14 +383,16 @@ public abstract class TextToSpeechService extends Service {
}
}
- interface UtteranceCompletedDispatcher {
- public void dispatchUtteranceCompleted();
+ interface UtteranceProgressDispatcher {
+ public void dispatchOnDone();
+ public void dispatchOnStart();
+ public void dispatchOnError();
}
/**
* An item in the synth thread queue.
*/
- private abstract class SpeechItem implements UtteranceCompletedDispatcher {
+ private abstract class SpeechItem implements UtteranceProgressDispatcher {
private final String mCallingApp;
protected final Bundle mParams;
private boolean mStarted = false;
@@ -431,10 +447,27 @@ public abstract class TextToSpeechService extends Service {
stopImpl();
}
- public void dispatchUtteranceCompleted() {
+ @Override
+ public void dispatchOnDone() {
final String utteranceId = getUtteranceId();
if (!TextUtils.isEmpty(utteranceId)) {
- mCallbacks.dispatchUtteranceCompleted(getCallingApp(), utteranceId);
+ mCallbacks.dispatchOnDone(getCallingApp(), utteranceId);
+ }
+ }
+
+ @Override
+ public void dispatchOnStart() {
+ final String utteranceId = getUtteranceId();
+ if (!TextUtils.isEmpty(utteranceId)) {
+ mCallbacks.dispatchOnStart(getCallingApp(), utteranceId);
+ }
+ }
+
+ @Override
+ public void dispatchOnError() {
+ final String utteranceId = getUtteranceId();
+ if (!TextUtils.isEmpty(utteranceId)) {
+ mCallbacks.dispatchOnError(getCallingApp(), utteranceId);
}
}
@@ -605,9 +638,12 @@ public abstract class TextToSpeechService extends Service {
@Override
protected int playImpl() {
+ dispatchOnStart();
int status = super.playImpl();
if (status == TextToSpeech.SUCCESS) {
- dispatchUtteranceCompleted();
+ dispatchOnDone();
+ } else {
+ dispatchOnError();
}
return status;
}
@@ -778,6 +814,18 @@ public abstract class TextToSpeechService extends Service {
return onIsLanguageAvailable(lang, country, variant);
}
+ public String[] getFeaturesForLanguage(String lang, String country, String variant) {
+ Set<String> features = onGetFeaturesForLanguage(lang, country, variant);
+ String[] featuresArray = null;
+ if (features != null) {
+ featuresArray = new String[features.size()];
+ features.toArray(featuresArray);
+ } else {
+ featuresArray = new String[0];
+ }
+ return featuresArray;
+ }
+
/*
* There is no point loading a non default language if defaults
* are enforced.
@@ -832,16 +880,34 @@ public abstract class TextToSpeechService extends Service {
}
}
- public void dispatchUtteranceCompleted(String packageName, String utteranceId) {
- ITextToSpeechCallback cb;
- synchronized (mAppToCallback) {
- cb = mAppToCallback.get(packageName);
+ public void dispatchOnDone(String packageName, String utteranceId) {
+ ITextToSpeechCallback cb = getCallbackFor(packageName);
+ if (cb == null) return;
+ try {
+ cb.onDone(utteranceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback onDone failed: " + e);
+ }
+ }
+
+ public void dispatchOnStart(String packageName, String utteranceId) {
+ ITextToSpeechCallback cb = getCallbackFor(packageName);
+ if (cb == null) return;
+ try {
+ cb.onStart(utteranceId);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Callback onStart failed: " + e);
}
+
+ }
+
+ public void dispatchOnError(String packageName, String utteranceId) {
+ ITextToSpeechCallback cb = getCallbackFor(packageName);
if (cb == null) return;
try {
- cb.utteranceCompleted(utteranceId);
+ cb.onError(utteranceId);
} catch (RemoteException e) {
- Log.e(TAG, "Callback failed: " + e);
+ Log.e(TAG, "Callback onError failed: " + e);
}
}
@@ -862,6 +928,15 @@ public abstract class TextToSpeechService extends Service {
}
}
+ private ITextToSpeechCallback getCallbackFor(String packageName) {
+ ITextToSpeechCallback cb;
+ synchronized (mAppToCallback) {
+ cb = mAppToCallback.get(packageName);
+ }
+
+ return cb;
+ }
+
}
}
diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java
new file mode 100644
index 0000000..a04458a
--- /dev/null
+++ b/core/java/android/speech/tts/UtteranceProgressListener.java
@@ -0,0 +1,68 @@
+// Copyright 2011 Google Inc. All Rights Reserved.
+
+package android.speech.tts;
+
+/**
+ * Listener for events relating to the progress of an utterance through
+ * the synthesis queue. Each utterance is associated with a call to
+ * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} with an
+ * associated utterance identifier, as per {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}.
+ *
+ * The callbacks specified in this method can be called from multiple threads.
+ */
+public abstract class UtteranceProgressListener {
+ /**
+ * Called when an utterance "starts" as perceived by the caller. This will
+ * be soon before audio is played back in the case of a {@link TextToSpeech#speak}
+ * or before the first bytes of a file are written to storage in the case
+ * of {@link TextToSpeech#synthesizeToFile}.
+ *
+ * @param utteranceId the utterance ID of the utterance.
+ */
+ public abstract void onStart(String utteranceId);
+
+ /**
+ * Called when an utterance has successfully completed processing.
+ * All audio will have been played back by this point for audible output, and all
+ * output will have been written to disk for file synthesis requests.
+ *
+ * This request is guaranteed to be called after {@link #onStart(String)}.
+ *
+ * @param utteranceId the utterance ID of the utterance.
+ */
+ public abstract void onDone(String utteranceId);
+
+ /**
+ * Called when an error has occurred during processing. This can be called
+ * at any point in the synthesis process. Note that there might be calls
+ * to {@link #onStart(String)} for specified utteranceId but there will never
+ * be a call to both {@link #onDone(String)} and {@link #onError(String)} for
+ * the same utterance.
+ *
+ * @param utteranceId the utterance ID of the utterance.
+ */
+ public abstract void onError(String utteranceId);
+
+ /**
+ * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new
+ * progress listener.
+ *
+ * @hide
+ */
+ static UtteranceProgressListener from(
+ final TextToSpeech.OnUtteranceCompletedListener listener) {
+ return new UtteranceProgressListener() {
+ @Override
+ public synchronized void onDone(String utteranceId) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
+
+ // The following methods are left unimplemented.
+ @Override
+ public void onStart(String utteranceId) { }
+
+ @Override
+ public void onError(String utteranceId) { }
+ };
+ }
+}
diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index f82c9c4..026af34 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -281,9 +281,9 @@ extends Layout
}
reflowed.generate(text, where, where + after,
- getPaint(), getWidth(), getAlignment(), getTextDirectionHeuristic(),
- getSpacingMultiplier(), getSpacingAdd(),
- false, true, mEllipsizedWidth, mEllipsizeAt);
+ getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(),
+ getSpacingAdd(), false,
+ true, mEllipsizedWidth, mEllipsizeAt);
int n = reflowed.getLineCount();
// If the new layout has a blank line at the end, but it is not
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 583cbe6..1dd4c8a 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -137,9 +137,9 @@ public class StaticLayout extends Layout {
mMeasured = MeasuredText.obtain();
- generate(source, bufstart, bufend, paint, outerwidth, align, textDir,
- spacingmult, spacingadd, includepad, includepad,
- ellipsizedWidth, ellipsize);
+ generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult,
+ spacingadd, includepad, includepad, ellipsizedWidth,
+ ellipsize);
mMeasured = MeasuredText.recycle(mMeasured);
mFontMetricsInt = null;
@@ -157,10 +157,10 @@ public class StaticLayout extends Layout {
/* package */ void generate(CharSequence source, int bufStart, int bufEnd,
TextPaint paint, int outerWidth,
- Alignment align, TextDirectionHeuristic textDir,
- float spacingmult, float spacingadd,
- boolean includepad, boolean trackpad,
- float ellipsizedWidth, TextUtils.TruncateAt ellipsize) {
+ TextDirectionHeuristic textDir, float spacingmult,
+ float spacingadd, boolean includepad,
+ boolean trackpad, float ellipsizedWidth,
+ TextUtils.TruncateAt ellipsize) {
mLineCount = 0;
int v = 0;
@@ -328,9 +328,7 @@ public class StaticLayout extends Layout {
whichPaint = mWorkPaint;
}
- float wid = bm.getWidth() *
- -whichPaint.ascent() /
- bm.getHeight();
+ float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight();
w += wid;
hasTabOrEmoji = true;
@@ -398,67 +396,49 @@ public class StaticLayout extends Layout {
okBottom = fitBottom;
}
} else {
- final boolean moreChars = (j + 1 < spanEnd);
- if (ok != here) {
- // Log.e("text", "output ok " + here + " to " +ok);
+ final boolean moreChars = (j + 1 < spanEnd);
+ int endPos;
+ int above, below, top, bottom;
+ float currentTextWidth;
- while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) {
- ok++;
- }
+ if (ok != here) {
+ // If it is a space that makes the length exceed width, cut here
+ if (c == CHAR_SPACE) ok = j + 1;
- v = out(source,
- here, ok,
- okAscent, okDescent, okTop, okBottom,
- v,
- spacingmult, spacingadd, chooseHt,
- chooseHtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- ok == bufEnd, includepad, trackpad,
- chs, widths, paraStart,
- ellipsize, ellipsizedWidth, okWidth,
- paint, moreChars);
-
- here = ok;
- } else if (fit != here) {
- // Log.e("text", "output fit " + here + " to " +fit);
- v = out(source,
- here, fit,
- fitAscent, fitDescent,
- fitTop, fitBottom,
- v,
- spacingmult, spacingadd, chooseHt,
- chooseHtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- fit == bufEnd, includepad, trackpad,
- chs, widths, paraStart,
- ellipsize, ellipsizedWidth, fitWidth,
- paint, moreChars);
-
- here = fit;
- } else {
- // Log.e("text", "output one " + here + " to " +(here + 1));
- // XXX not sure why the existing fm wasn't ok.
- // measureText(paint, mWorkPaint,
- // source, here, here + 1, fm, tab,
- // null);
-
- v = out(source,
- here, here+1,
- fm.ascent, fm.descent,
- fm.top, fm.bottom,
- v,
- spacingmult, spacingadd, chooseHt,
- chooseHtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- here + 1 == bufEnd, includepad,
- trackpad,
- chs, widths, paraStart,
- ellipsize, ellipsizedWidth,
- widths[here - paraStart], paint, moreChars);
-
- here = here + 1;
+ while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) {
+ ok++;
}
+ endPos = ok;
+ above = okAscent;
+ below = okDescent;
+ top = okTop;
+ bottom = okBottom;
+ currentTextWidth = okWidth;
+ } else if (fit != here) {
+ endPos = fit;
+ above = fitAscent;
+ below = fitDescent;
+ top = fitTop;
+ bottom = fitBottom;
+ currentTextWidth = fitWidth;
+ } else {
+ endPos = here + 1;
+ above = fm.ascent;
+ below = fm.descent;
+ top = fm.top;
+ bottom = fm.bottom;
+ currentTextWidth = widths[here - paraStart];
+ }
+
+ v = out(source, here, endPos,
+ above, below, top, bottom,
+ v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji,
+ needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad,
+ chs, widths, paraStart, ellipsize, ellipsizedWidth,
+ currentTextWidth, paint, moreChars);
+ here = endPos;
+
if (here < spanStart) {
// didn't output all the text for this span
// we've measured the raw widths, though, so
@@ -501,10 +481,10 @@ public class StaticLayout extends Layout {
v,
spacingmult, spacingadd, chooseHt,
chooseHtv, fm, hasTabOrEmoji,
- needMultiply, paraStart, chdirs, dir, easy,
- paraEnd == bufEnd, includepad, trackpad,
- chs, widths, paraStart,
- ellipsize, ellipsizedWidth, w, paint, paraEnd != bufEnd);
+ needMultiply, chdirs, dir, easy, bufEnd,
+ includepad, trackpad, chs,
+ widths, paraStart, ellipsize,
+ ellipsizedWidth, w, paint, paraEnd != bufEnd);
}
paraStart = paraEnd;
@@ -525,10 +505,10 @@ public class StaticLayout extends Layout {
v,
spacingmult, spacingadd, null,
null, fm, false,
- needMultiply, bufEnd, null, DEFAULT_DIR, true,
- true, includepad, trackpad,
- null, null, bufStart,
- ellipsize, ellipsizedWidth, 0, paint, false);
+ needMultiply, null, DEFAULT_DIR, true, bufEnd,
+ includepad, trackpad, null,
+ null, bufStart, ellipsize,
+ ellipsizedWidth, 0, paint, false);
}
}
@@ -628,12 +608,12 @@ public class StaticLayout extends Layout {
float spacingmult, float spacingadd,
LineHeightSpan[] chooseHt, int[] chooseHtv,
Paint.FontMetricsInt fm, boolean hasTabOrEmoji,
- boolean needMultiply, int pstart, byte[] chdirs,
- int dir, boolean easy, boolean last,
- boolean includePad, boolean trackPad,
- char[] chs, float[] widths, int widthStart,
- TextUtils.TruncateAt ellipsize, float ellipsisWidth,
- float textWidth, TextPaint paint, boolean moreChars) {
+ boolean needMultiply, byte[] chdirs, int dir,
+ boolean easy, int bufEnd, boolean includePad,
+ boolean trackPad, char[] chs,
+ float[] widths, int widthStart, TextUtils.TruncateAt ellipsize,
+ float ellipsisWidth, float textWidth,
+ TextPaint paint, boolean moreChars) {
int j = mLineCount;
int off = j * mColumns;
int want = off + mColumns + TOP;
@@ -683,7 +663,7 @@ public class StaticLayout extends Layout {
above = top;
}
}
- if (last) {
+ if (end == bufEnd) {
if (trackPad) {
mBottomPadding = bottom - below;
}
diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java
index e93039b..4ec4bc4 100644
--- a/core/java/android/text/method/ArrowKeyMovementMethod.java
+++ b/core/java/android/text/method/ArrowKeyMovementMethod.java
@@ -197,16 +197,18 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
@Override
protected boolean leftWord(TextView widget, Spannable buffer) {
final int selectionEnd = widget.getSelectionEnd();
- mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
- return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer));
+ final WordIterator wordIterator = widget.getWordIterator();
+ wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
+ return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer));
}
/** {@hide} */
@Override
protected boolean rightWord(TextView widget, Spannable buffer) {
final int selectionEnd = widget.getSelectionEnd();
- mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
- return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer));
+ final WordIterator wordIterator = widget.getWordIterator();
+ wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd);
+ return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer));
}
@Override
@@ -322,8 +324,6 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme
return sInstance;
}
- private WordIterator mWordIterator = new WordIterator();
-
private static final Object LAST_TAP_DOWN = new Object();
private static ArrowKeyMovementMethod sInstance;
}
diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java
index 6e95016..ed2af10 100644
--- a/core/java/android/text/style/SuggestionSpan.java
+++ b/core/java/android/text/style/SuggestionSpan.java
@@ -60,7 +60,6 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan {
* Sets this flag if the auto correction is about to be applied to a word/text
* that the user is typing/composing. This type of suggestion is rendered differently
* to indicate the auto correction is happening.
- * @hide
*/
public static final int FLAG_AUTO_CORRECTION = 0x0004;
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index c934c7b..d948ec2 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -315,6 +315,27 @@ class GLES20Canvas extends HardwareCanvas {
private static native void nFlushCaches(int level);
+ /**
+ * Release all resources associated with the underlying caches. This should
+ * only be called after a full flushCaches().
+ *
+ * @hide
+ */
+ public static void terminateCaches() {
+ nTerminateCaches();
+ }
+
+ private static native void nTerminateCaches();
+
+ /**
+ * @hide
+ */
+ public static void initCaches() {
+ nInitCaches();
+ }
+
+ private static native void nInitCaches();
+
///////////////////////////////////////////////////////////////////////////
// Display list
///////////////////////////////////////////////////////////////////////////
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index b86d21d..8e39d6e 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -22,9 +22,13 @@ import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.SurfaceTexture;
import android.opengl.GLUtils;
+import android.opengl.ManagedEGLContext;
+import android.os.Handler;
+import android.os.Looper;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.util.Log;
+import com.google.android.gles_jni.EGLImpl;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
@@ -34,6 +38,8 @@ import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
+import java.io.File;
+
import static javax.microedition.khronos.egl.EGL10.*;
/**
@@ -45,6 +51,11 @@ public abstract class HardwareRenderer {
static final String LOG_TAG = "HardwareRenderer";
/**
+ * Name of the file that holds the shaders cache.
+ */
+ private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache";
+
+ /**
* Turn on to only refresh the parts of the screen that need updating.
* When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY}
* must also have the value "true".
@@ -200,6 +211,18 @@ public abstract class HardwareRenderer {
abstract int getHeight();
/**
+ * Sets the directory to use as a persistent storage for hardware rendering
+ * resources.
+ *
+ * @param cacheDir A directory the current process can write to
+ */
+ public static void setupDiskCache(File cacheDir) {
+ nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath());
+ }
+
+ private static native void nSetupShadersDiskCache(String cacheFile);
+
+ /**
* Interface used to receive callbacks whenever a view is drawn by
* a hardware renderer instance.
*/
@@ -325,6 +348,15 @@ public abstract class HardwareRenderer {
}
/**
+ * Invoke this method when the system needs to clean up all resources
+ * associated with hardware rendering.
+ */
+ static void terminate() {
+ Log.d(LOG_TAG, "Terminating hardware rendering");
+ Gl20Renderer.terminate();
+ }
+
+ /**
* Indicates whether hardware acceleration is currently enabled.
*
* @return True if hardware acceleration is in use, false otherwise.
@@ -380,7 +412,8 @@ public abstract class HardwareRenderer {
static final Object[] sEglLock = new Object[0];
int mWidth = -1, mHeight = -1;
- static final ThreadLocal<EGLContext> sEglContextStorage = new ThreadLocal<EGLContext>();
+ static final ThreadLocal<Gl20Renderer.MyEGLContext> sEglContextStorage
+ = new ThreadLocal<Gl20Renderer.MyEGLContext>();
EGLContext mEglContext;
Thread mEglThread;
@@ -532,12 +565,13 @@ public abstract class HardwareRenderer {
}
}
- mEglContext = sEglContextStorage.get();
+ Gl20Renderer.MyEGLContext managedContext = sEglContextStorage.get();
+ mEglContext = managedContext != null ? managedContext.getContext() : null;
mEglThread = Thread.currentThread();
if (mEglContext == null) {
mEglContext = createContext(sEgl, sEglDisplay, sEglConfig);
- sEglContextStorage.set(mEglContext);
+ sEglContextStorage.set(new Gl20Renderer.MyEGLContext(mEglContext));
}
}
@@ -632,6 +666,8 @@ public abstract class HardwareRenderer {
throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
+ GLUtils.getEGLErrorString(sEgl.eglGetError()));
}
+
+ initCaches();
// If mDirtyRegions is set, this means we have an EGL configuration
// with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set
@@ -652,6 +688,8 @@ public abstract class HardwareRenderer {
return mEglContext.getGL();
}
+ abstract void initCaches();
+
EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
@@ -871,6 +909,51 @@ public abstract class HardwareRenderer {
private static EGLSurface sPbuffer;
private static final Object[] sPbufferLock = new Object[0];
+ static class MyEGLContext extends ManagedEGLContext {
+ final Handler mHandler = new Handler();
+
+ public MyEGLContext(EGLContext context) {
+ super(context);
+ }
+
+ @Override
+ public void onTerminate(final EGLContext eglContext) {
+ // Make sure we do this on the correct thread.
+ if (mHandler.getLooper() != Looper.myLooper()) {
+ mHandler.post(new Runnable() {
+ @Override public void run() {
+ onTerminate(eglContext);
+ }
+ });
+ return;
+ }
+
+ synchronized (sEglLock) {
+ if (sEgl == null) return;
+
+ if (EGLImpl.getInitCount(sEglDisplay) == 1) {
+ usePbufferSurface(eglContext);
+ GLES20Canvas.terminateCaches();
+
+ sEgl.eglDestroyContext(sEglDisplay, eglContext);
+ sEglContextStorage.remove();
+
+ sEgl.eglDestroySurface(sEglDisplay, sPbuffer);
+ sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+
+ sEgl.eglReleaseThread();
+ sEgl.eglTerminate(sEglDisplay);
+
+ sEgl = null;
+ sEglDisplay = null;
+ sEglConfig = null;
+ sPbuffer = null;
+ sEglContextStorage.set(null);
+ }
+ }
+ }
+ }
+
Gl20Renderer(boolean translucent) {
super(2, translucent);
}
@@ -895,6 +978,11 @@ public abstract class HardwareRenderer {
EGL_NONE
};
}
+
+ @Override
+ void initCaches() {
+ GLES20Canvas.initCaches();
+ }
@Override
boolean canDraw() {
@@ -982,21 +1070,12 @@ public abstract class HardwareRenderer {
static void trimMemory(int level) {
if (sEgl == null || sEglConfig == null) return;
- EGLContext eglContext = sEglContextStorage.get();
+ Gl20Renderer.MyEGLContext managedContext = sEglContextStorage.get();
// We do not have OpenGL objects
- if (eglContext == null) {
+ if (managedContext == null) {
return;
} else {
- synchronized (sPbufferLock) {
- // Create a temporary 1x1 pbuffer so we have a context
- // to clear our OpenGL objects
- if (sPbuffer == null) {
- sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
- EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
- });
- }
- }
- sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
+ usePbufferSurface(managedContext.getContext());
}
switch (level) {
@@ -1010,5 +1089,18 @@ public abstract class HardwareRenderer {
break;
}
}
+
+ private static void usePbufferSurface(EGLContext eglContext) {
+ synchronized (sPbufferLock) {
+ // Create a temporary 1x1 pbuffer so we have a context
+ // to clear our OpenGL objects
+ if (sPbuffer == null) {
+ sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] {
+ EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE
+ });
+ }
+ }
+ sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext);
+ }
}
}
diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl
index 6bff5b3..5f0600f 100644
--- a/core/java/android/view/IApplicationToken.aidl
+++ b/core/java/android/view/IApplicationToken.aidl
@@ -20,6 +20,7 @@ package android.view;
/** {@hide} */
interface IApplicationToken
{
+ void windowsDrawn();
void windowsVisible();
void windowsGone();
boolean keyDispatchingTimedOut();
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index bfc7c31..8115b36 100755
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -19,7 +19,6 @@ package android.view;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.ServiceManager;
import java.util.ArrayList;
import java.util.List;
@@ -44,6 +43,7 @@ public final class InputDevice implements Parcelable {
private String mName;
private int mSources;
private int mKeyboardType;
+ private String mKeyCharacterMapFile;
private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>();
@@ -360,6 +360,10 @@ public final class InputDevice implements Parcelable {
return KeyCharacterMap.load(mId);
}
+ String getKeyCharacterMapFile() {
+ return mKeyCharacterMapFile;
+ }
+
/**
* Gets information about the range of values for a particular {@link MotionEvent} axis.
* If the device supports multiple sources, the same axis may have different meanings
@@ -532,6 +536,7 @@ public final class InputDevice implements Parcelable {
mName = in.readString();
mSources = in.readInt();
mKeyboardType = in.readInt();
+ mKeyCharacterMapFile = in.readString();
for (;;) {
int axis = in.readInt();
@@ -549,6 +554,7 @@ public final class InputDevice implements Parcelable {
out.writeString(mName);
out.writeInt(mSources);
out.writeInt(mKeyboardType);
+ out.writeString(mKeyCharacterMapFile);
final int numRanges = mMotionRanges.size();
for (int i = 0; i < numRanges; i++) {
@@ -587,6 +593,8 @@ public final class InputDevice implements Parcelable {
}
description.append("\n");
+ description.append(" Key Character Map: ").append(mKeyCharacterMapFile).append("\n");
+
description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" (");
appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard");
appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad");
diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java
index 885a75f..575af3b 100644
--- a/core/java/android/view/KeyCharacterMap.java
+++ b/core/java/android/view/KeyCharacterMap.java
@@ -20,7 +20,6 @@ import android.text.method.MetaKeyKeyListener;
import android.util.AndroidRuntimeException;
import android.util.SparseIntArray;
import android.os.RemoteException;
-import android.os.ServiceManager;
import android.util.SparseArray;
import java.lang.Character;
@@ -140,7 +139,7 @@ public class KeyCharacterMap {
private final int mDeviceId;
private int mPtr;
- private static native int nativeLoad(int id);
+ private static native int nativeLoad(String file);
private static native void nativeDispose(int ptr);
private static native char nativeGetCharacter(int ptr, int keyCode, int metaState);
@@ -178,7 +177,17 @@ public class KeyCharacterMap {
synchronized (sInstances) {
KeyCharacterMap map = sInstances.get(deviceId);
if (map == null) {
- int ptr = nativeLoad(deviceId); // might throw
+ String kcm = null;
+ if (deviceId != VIRTUAL_KEYBOARD) {
+ InputDevice device = InputDevice.getDevice(deviceId);
+ if (device != null) {
+ kcm = device.getKeyCharacterMapFile();
+ }
+ }
+ if (kcm == null || kcm.length() == 0) {
+ kcm = "/system/usr/keychars/Virtual.kcm";
+ }
+ int ptr = nativeLoad(kcm); // might throw
map = new KeyCharacterMap(deviceId, ptr);
sInstances.put(deviceId, map);
}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 61b13d5..dc46d42 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -1548,7 +1548,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
int mID = NO_ID;
/**
- * The stable ID of this view for accessibility porposes.
+ * The stable ID of this view for accessibility purposes.
*/
int mAccessibilityViewId = NO_ID;
@@ -2317,55 +2317,59 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
private int mBackgroundResource;
private boolean mBackgroundSizeChanged;
- /**
- * Listener used to dispatch focus change events.
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected OnFocusChangeListener mOnFocusChangeListener;
+ static class ListenerInfo {
+ /**
+ * Listener used to dispatch focus change events.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnFocusChangeListener mOnFocusChangeListener;
- /**
- * Listeners for layout change events.
- */
- private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
+ /**
+ * Listeners for layout change events.
+ */
+ private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
- /**
- * Listeners for attach events.
- */
- private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
+ /**
+ * Listeners for attach events.
+ */
+ private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
- /**
- * Listener used to dispatch click events.
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected OnClickListener mOnClickListener;
+ /**
+ * Listener used to dispatch click events.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ public OnClickListener mOnClickListener;
- /**
- * Listener used to dispatch long click events.
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected OnLongClickListener mOnLongClickListener;
+ /**
+ * Listener used to dispatch long click events.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnLongClickListener mOnLongClickListener;
- /**
- * Listener used to build the context menu.
- * This field should be made private, so it is hidden from the SDK.
- * {@hide}
- */
- protected OnCreateContextMenuListener mOnCreateContextMenuListener;
+ /**
+ * Listener used to build the context menu.
+ * This field should be made private, so it is hidden from the SDK.
+ * {@hide}
+ */
+ protected OnCreateContextMenuListener mOnCreateContextMenuListener;
- private OnKeyListener mOnKeyListener;
+ private OnKeyListener mOnKeyListener;
- private OnTouchListener mOnTouchListener;
+ private OnTouchListener mOnTouchListener;
- private OnHoverListener mOnHoverListener;
+ private OnHoverListener mOnHoverListener;
- private OnGenericMotionListener mOnGenericMotionListener;
+ private OnGenericMotionListener mOnGenericMotionListener;
- private OnDragListener mOnDragListener;
+ private OnDragListener mOnDragListener;
+
+ private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
+ }
- private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener;
+ ListenerInfo mListenerInfo;
/**
* The application environment this view lives in.
@@ -3346,13 +3350,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
return mVerticalScrollbarPosition;
}
+ ListenerInfo getListenerInfo() {
+ if (mListenerInfo != null) {
+ return mListenerInfo;
+ }
+ mListenerInfo = new ListenerInfo();
+ return mListenerInfo;
+ }
+
/**
* Register a callback to be invoked when focus of this view changed.
*
* @param l The callback that will run.
*/
public void setOnFocusChangeListener(OnFocusChangeListener l) {
- mOnFocusChangeListener = l;
+ getListenerInfo().mOnFocusChangeListener = l;
}
/**
@@ -3362,11 +3374,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param listener The listener that will be called when layout bounds change.
*/
public void addOnLayoutChangeListener(OnLayoutChangeListener listener) {
- if (mOnLayoutChangeListeners == null) {
- mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>();
+ ListenerInfo li = getListenerInfo();
+ if (li.mOnLayoutChangeListeners == null) {
+ li.mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>();
}
- if (!mOnLayoutChangeListeners.contains(listener)) {
- mOnLayoutChangeListeners.add(listener);
+ if (!li.mOnLayoutChangeListeners.contains(listener)) {
+ li.mOnLayoutChangeListeners.add(listener);
}
}
@@ -3376,10 +3389,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param listener The listener for layout bounds change.
*/
public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) {
- if (mOnLayoutChangeListeners == null) {
+ ListenerInfo li = mListenerInfo;
+ if (li == null || li.mOnLayoutChangeListeners == null) {
return;
}
- mOnLayoutChangeListeners.remove(listener);
+ li.mOnLayoutChangeListeners.remove(listener);
}
/**
@@ -3393,10 +3407,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener)
*/
public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
- if (mOnAttachStateChangeListeners == null) {
- mOnAttachStateChangeListeners = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
+ ListenerInfo li = getListenerInfo();
+ if (li.mOnAttachStateChangeListeners == null) {
+ li.mOnAttachStateChangeListeners
+ = new CopyOnWriteArrayList<OnAttachStateChangeListener>();
}
- mOnAttachStateChangeListeners.add(listener);
+ li.mOnAttachStateChangeListeners.add(listener);
}
/**
@@ -3407,10 +3423,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @see #addOnAttachStateChangeListener(OnAttachStateChangeListener)
*/
public void removeOnAttachStateChangeListener(OnAttachStateChangeListener listener) {
- if (mOnAttachStateChangeListeners == null) {
+ ListenerInfo li = mListenerInfo;
+ if (li == null || li.mOnAttachStateChangeListeners == null) {
return;
}
- mOnAttachStateChangeListeners.remove(listener);
+ li.mOnAttachStateChangeListeners.remove(listener);
}
/**
@@ -3419,7 +3436,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @return The callback, or null if one is not registered.
*/
public OnFocusChangeListener getOnFocusChangeListener() {
- return mOnFocusChangeListener;
+ ListenerInfo li = mListenerInfo;
+ return li != null ? li.mOnFocusChangeListener : null;
}
/**
@@ -3434,7 +3452,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (!isClickable()) {
setClickable(true);
}
- mOnClickListener = l;
+ getListenerInfo().mOnClickListener = l;
+ }
+
+ /**
+ * Return whether this view has an attached OnClickListener. Returns
+ * true if there is a listener, false if there is none.
+ */
+ public boolean hasOnClickListeners() {
+ ListenerInfo li = mListenerInfo;
+ return (li != null && li.mOnClickListener != null);
}
/**
@@ -3449,7 +3476,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (!isLongClickable()) {
setLongClickable(true);
}
- mOnLongClickListener = l;
+ getListenerInfo().mOnLongClickListener = l;
}
/**
@@ -3463,11 +3490,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (!isLongClickable()) {
setLongClickable(true);
}
- mOnCreateContextMenuListener = l;
+ getListenerInfo().mOnCreateContextMenuListener = l;
}
/**
- * Call this view's OnClickListener, if it is defined.
+ * Call this view's OnClickListener, if it is defined. Performs all normal
+ * actions associated with clicking: reporting accessibility event, playing
+ * a sound, etc.
*
* @return True there was an assigned OnClickListener that was called, false
* otherwise is returned.
@@ -3475,9 +3504,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
- if (mOnClickListener != null) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
- mOnClickListener.onClick(this);
+ li.mOnClickListener.onClick(this);
return true;
}
@@ -3485,6 +3515,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
/**
+ * Directly call any attached OnClickListener. Unlike {@link #performClick()},
+ * this only calls the listener, and does not do any associated clicking
+ * actions like reporting an accessibility event.
+ *
+ * @return True there was an assigned OnClickListener that was called, false
+ * otherwise is returned.
+ */
+ public boolean callOnClick() {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnClickListener != null) {
+ li.mOnClickListener.onClick(this);
+ return true;
+ }
+ return false;
+ }
+
+ /**
* Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the
* OnLongClickListener did not consume the event.
*
@@ -3494,8 +3541,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
- if (mOnLongClickListener != null) {
- handled = mOnLongClickListener.onLongClick(View.this);
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnLongClickListener != null) {
+ handled = li.mOnLongClickListener.onLongClick(View.this);
}
if (!handled) {
handled = showContextMenu();
@@ -3563,7 +3611,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param l the key listener to attach to this view
*/
public void setOnKeyListener(OnKeyListener l) {
- mOnKeyListener = l;
+ getListenerInfo().mOnKeyListener = l;
}
/**
@@ -3571,7 +3619,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param l the touch listener to attach to this view
*/
public void setOnTouchListener(OnTouchListener l) {
- mOnTouchListener = l;
+ getListenerInfo().mOnTouchListener = l;
}
/**
@@ -3579,7 +3627,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param l the generic motion listener to attach to this view
*/
public void setOnGenericMotionListener(OnGenericMotionListener l) {
- mOnGenericMotionListener = l;
+ getListenerInfo().mOnGenericMotionListener = l;
}
/**
@@ -3587,7 +3635,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param l the hover listener to attach to this view
*/
public void setOnHoverListener(OnHoverListener l) {
- mOnHoverListener = l;
+ getListenerInfo().mOnHoverListener = l;
}
/**
@@ -3598,7 +3646,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param l An implementation of {@link android.view.View.OnDragListener}.
*/
public void setOnDragListener(OnDragListener l) {
- mOnDragListener = l;
+ getListenerInfo().mOnDragListener = l;
}
/**
@@ -3804,8 +3852,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
}
invalidate(true);
- if (mOnFocusChangeListener != null) {
- mOnFocusChangeListener.onFocusChange(this, gainFocus);
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnFocusChangeListener != null) {
+ li.mOnFocusChangeListener.onFocusChange(this, gainFocus);
}
if (mAttachInfo != null) {
@@ -4122,10 +4171,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
bounds.offset(locationOnScreen[0], locationOnScreen[1]);
info.setBoundsInScreen(bounds);
- ViewParent parent = getParent();
- if (parent instanceof View) {
- View parentView = (View) parent;
- info.setParent(parentView);
+ if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
+ ViewParent parent = getParent();
+ if (parent instanceof View) {
+ View parentView = (View) parent;
+ info.setParent(parentView);
+ }
}
info.setPackageName(mContext.getPackageName());
@@ -4219,6 +4270,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*
* @attr ref android.R.styleable#View_contentDescription
*/
+ @RemotableViewMethod
public void setContentDescription(CharSequence contentDescription) {
mContentDescription = contentDescription;
}
@@ -5438,8 +5490,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
// Give any attached key listener a first crack at the event.
//noinspection SimplifiableIfStatement
- if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
- && mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {
return true;
}
@@ -5478,8 +5531,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnTouchListener.onTouch(this, event)) {
return true;
}
@@ -5571,8 +5625,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
private boolean dispatchGenericMotionEventInternal(MotionEvent event) {
//noinspection SimplifiableIfStatement
- if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
- && mOnGenericMotionListener.onGenericMotion(this, event)) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnGenericMotionListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnGenericMotionListener.onGenericMotion(this, event)) {
return true;
}
@@ -5598,8 +5654,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
protected boolean dispatchHoverEvent(MotionEvent event) {
//noinspection SimplifiableIfStatement
- if (mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
- && mOnHoverListener.onHover(this, event)) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnHoverListener != null
+ && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnHoverListener.onHover(this, event)) {
return true;
}
@@ -5883,7 +5941,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mAttachInfo.mKeepScreenOn = true;
}
mAttachInfo.mSystemUiVisibility |= mSystemUiVisibility;
- if (mOnSystemUiVisibilityChangeListener != null) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
mAttachInfo.mHasSystemUiListeners = true;
}
}
@@ -6117,8 +6176,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
((MenuBuilder)menu).setCurrentMenuInfo(menuInfo);
onCreateContextMenu(menu);
- if (mOnCreateContextMenuListener != null) {
- mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnCreateContextMenuListener != null) {
+ li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo);
}
// Clear the extra information so subsequent items that aren't mine don't
@@ -9722,8 +9782,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
performCollectViewAttributes(visibility);
onAttachedToWindow();
+ ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
- mOnAttachStateChangeListeners;
+ li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
@@ -9755,8 +9816,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onDetachedFromWindow();
+ ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
- mOnAttachStateChangeListeners;
+ li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
@@ -10114,8 +10176,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
mLocalDirtyRect.setEmpty();
}
+ // The layer is not valid if the underlying GPU resources cannot be allocated
+ if (!mHardwareLayer.isValid()) {
+ return null;
+ }
+
HardwareCanvas currentCanvas = mAttachInfo.mHardwareCanvas;
final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas);
+
+ // Make sure all the GPU resources have been properly allocated
+ if (canvas == null) {
+ mHardwareLayer.end(currentCanvas);
+ return null;
+ }
+
mAttachInfo.mHardwareCanvas = canvas;
try {
canvas.setViewport(width, height);
@@ -11180,9 +11254,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~LAYOUT_REQUIRED;
- if (mOnLayoutChangeListeners != null) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
- (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone();
+ (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
@@ -11327,8 +11402,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* {@link SystemClock#uptimeMillis} timebase.
*/
public void scheduleDrawable(Drawable who, Runnable what, long when) {
- if (verifyDrawable(who) && what != null && mAttachInfo != null) {
- mAttachInfo.mHandler.postAtTime(what, who, when);
+ if (verifyDrawable(who) && what != null) {
+ if (mAttachInfo != null) {
+ mAttachInfo.mHandler.postAtTime(what, who, when);
+ } else {
+ ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis());
+ }
}
}
@@ -11339,8 +11418,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param what the action to cancel
*/
public void unscheduleDrawable(Drawable who, Runnable what) {
- if (verifyDrawable(who) && what != null && mAttachInfo != null) {
- mAttachInfo.mHandler.removeCallbacks(what, who);
+ if (verifyDrawable(who) && what != null) {
+ if (mAttachInfo != null) {
+ mAttachInfo.mHandler.removeCallbacks(what, who);
+ } else {
+ ViewRootImpl.getRunQueue().removeCallbacks(what);
+ }
}
}
@@ -13052,7 +13135,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* @param l The {@link OnSystemUiVisibilityChangeListener} to receive callbacks.
*/
public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) {
- mOnSystemUiVisibilityChangeListener = l;
+ getListenerInfo().mOnSystemUiVisibilityChangeListener = l;
if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
mParent.recomputeViewAttributes(this);
}
@@ -13063,8 +13146,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
* the view hierarchy.
*/
public void dispatchSystemUiVisibilityChanged(int visibility) {
- if (mOnSystemUiVisibilityChangeListener != null) {
- mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange(
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
+ li.mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange(
visibility & PUBLIC_STATUS_BAR_VISIBILITY_MASK);
}
}
@@ -13336,8 +13420,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal
*/
public boolean dispatchDragEvent(DragEvent event) {
//noinspection SimplifiableIfStatement
- if (mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
- && mOnDragListener.onDrag(this, event)) {
+ ListenerInfo li = mListenerInfo;
+ if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
+ && li.mOnDragListener.onDrag(this, event)) {
return true;
}
return onDragEvent(event);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 62b20b3..e366e72 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -352,7 +352,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
private static final int ARRAY_CAPACITY_INCREMENT = 12;
// Used to draw cached views
- private final Paint mCachePaint = new Paint();
+ private Paint mCachePaint;
// Used to animate add/remove changes in layout
private LayoutTransition mTransition;
@@ -405,8 +405,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
mChildren = new View[ARRAY_INITIAL_CAPACITY];
mChildrenCount = 0;
- mCachePaint.setDither(false);
-
mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE;
}
@@ -2231,14 +2229,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
@Override
void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfoInternal(info);
- // If the view is not the topmost one in the view hierarchy and it is
- // marked as the logical root of a view hierarchy, do not go any deeper.
- if ((!(getParent() instanceof ViewRootImpl)) && (mPrivateFlags & IS_ROOT_NAMESPACE) != 0) {
- return;
- }
for (int i = 0, count = mChildrenCount; i < count; i++) {
View child = mChildren[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
+ if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
+ && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) {
info.addChild(child);
}
}
@@ -2909,6 +2903,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager
if (layerType == LAYER_TYPE_NONE) {
cachePaint = mCachePaint;
+ if (cachePaint == null) {
+ cachePaint = new Paint();
+ cachePaint.setDither(false);
+ mCachePaint = cachePaint;
+ }
if (alpha < 1.0f) {
cachePaint.setAlpha((int) (alpha * 255));
mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 081e267..b15b155 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -218,6 +218,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
boolean mNewSurfaceNeeded;
boolean mHasHadWindowFocus;
boolean mLastWasImTarget;
+ InputEventMessage mPendingInputEvents = null;
boolean mWindowAttributesChanged = false;
int mWindowAttributesChangesFlag = 0;
@@ -287,7 +288,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
final AccessibilityManager mAccessibilityManager;
- AccessibilityInteractionController mAccessibilityInteractionContrtoller;
+ AccessibilityInteractionController mAccessibilityInteractionController;
AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager;
@@ -431,20 +432,17 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
- // If the application owns the surface, don't enable hardware acceleration
- if (mSurfaceHolder == null) {
- enableHardwareAcceleration(attrs);
- }
-
CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
mTranslator = compatibilityInfo.getTranslator();
- if (mTranslator != null) {
- mSurface.setCompatibilityTranslator(mTranslator);
+ // If the application owns the surface, don't enable hardware acceleration
+ if (mSurfaceHolder == null) {
+ enableHardwareAcceleration(attrs);
}
boolean restore = false;
if (mTranslator != null) {
+ mSurface.setCompatibilityTranslator(mTranslator);
restore = true;
attrs.backup();
mTranslator.translateWindowLayout(attrs);
@@ -569,7 +567,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
- private void destroyHardwareResources() {
+ void destroyHardwareResources() {
if (mAttachInfo.mHardwareRenderer != null) {
if (mAttachInfo.mHardwareRenderer.isEnabled()) {
mAttachInfo.mHardwareRenderer.destroyLayers(mView);
@@ -596,6 +594,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
mAttachInfo.mHardwareAccelerated = false;
mAttachInfo.mHardwareAccelerationRequested = false;
+ // Don't enable hardware acceleration when the application is in compatibility mode
+ if (mTranslator != null) return;
+
// Try to enable hardware acceleration if requested
final boolean hardwareAccelerated =
(attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0;
@@ -832,10 +833,24 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
}
+ private void processInputEvents(boolean outOfOrder) {
+ while (mPendingInputEvents != null) {
+ handleMessage(mPendingInputEvents.mMessage);
+ InputEventMessage tmpMessage = mPendingInputEvents;
+ mPendingInputEvents = mPendingInputEvents.mNext;
+ tmpMessage.recycle();
+ if (outOfOrder) {
+ removeMessages(PROCESS_INPUT_EVENTS);
+ }
+ }
+ }
+
private void performTraversals() {
// cache mView since it is used so much below...
final View host = mView;
+ processInputEvents(true);
+
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
@@ -865,12 +880,10 @@ public final class ViewRootImpl extends Handler implements ViewParent,
|| mNewSurfaceNeeded;
WindowManager.LayoutParams params = null;
- int windowAttributesChanges = 0;
if (mWindowAttributesChanged) {
mWindowAttributesChanged = false;
surfaceChanged = true;
params = lp;
- windowAttributesChanges = mWindowAttributesChangesFlag;
}
CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get();
if (compatibilityInfo.supportsScreen() == mLastInCompatMode) {
@@ -2336,6 +2349,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021;
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022;
public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023;
+ public final static int PROCESS_INPUT_EVENTS = 1024;
@Override
public String getMessageName(Message message) {
@@ -2388,7 +2402,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID";
case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT:
return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT";
-
+ case PROCESS_INPUT_EVENTS:
+ return "PROCESS_INPUT_EVENTS";
+
}
return super.getMessageName(message);
}
@@ -2447,6 +2463,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
case DISPATCH_GENERIC_MOTION:
deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0);
break;
+ case PROCESS_INPUT_EVENTS:
+ processInputEvents(false);
+ break;
case DISPATCH_APP_VISIBILITY:
handleAppVisibility(msg.arg1 != 0);
break;
@@ -3526,10 +3545,10 @@ public final class ViewRootImpl extends Handler implements ViewParent,
throw new IllegalStateException("getAccessibilityInteractionController"
+ " called when there is no mView");
}
- if (mAccessibilityInteractionContrtoller == null) {
- mAccessibilityInteractionContrtoller = new AccessibilityInteractionController();
+ if (mAccessibilityInteractionController == null) {
+ mAccessibilityInteractionController = new AccessibilityInteractionController();
}
- return mAccessibilityInteractionContrtoller;
+ return mAccessibilityInteractionController;
}
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
@@ -3744,7 +3763,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
msg.obj = ri;
sendMessage(msg);
}
-
+
private long mInputEventReceiveTimeNanos;
private long mInputEventDeliverTimeNanos;
private long mInputEventDeliverPostImeTimeNanos;
@@ -3762,6 +3781,78 @@ public final class ViewRootImpl extends Handler implements ViewParent,
}
};
+ /**
+ * Utility class used to queue up input events which are then handled during
+ * performTraversals(). Doing it this way allows us to ensure that we are up to date with
+ * all input events just prior to drawing, instead of placing those events on the regular
+ * handler queue, potentially behind a drawing event.
+ */
+ static class InputEventMessage {
+ Message mMessage;
+ InputEventMessage mNext;
+
+ private static final Object sPoolSync = new Object();
+ private static InputEventMessage sPool;
+ private static int sPoolSize = 0;
+
+ private static final int MAX_POOL_SIZE = 10;
+
+ private InputEventMessage(Message m) {
+ mMessage = m;
+ mNext = null;
+ }
+
+ /**
+ * Return a new Message instance from the global pool. Allows us to
+ * avoid allocating new objects in many cases.
+ */
+ public static InputEventMessage obtain(Message msg) {
+ synchronized (sPoolSync) {
+ if (sPool != null) {
+ InputEventMessage m = sPool;
+ sPool = m.mNext;
+ m.mNext = null;
+ sPoolSize--;
+ m.mMessage = msg;
+ return m;
+ }
+ }
+ return new InputEventMessage(msg);
+ }
+
+ /**
+ * Return the message to the pool.
+ */
+ public void recycle() {
+ mMessage.recycle();
+ synchronized (sPoolSync) {
+ if (sPoolSize < MAX_POOL_SIZE) {
+ mNext = sPool;
+ sPool = this;
+ sPoolSize++;
+ }
+ }
+
+ }
+ }
+
+ /**
+ * Place the input event message at the end of the current pending list
+ */
+ private void enqueueInputEvent(Message msg, long when) {
+ InputEventMessage inputMessage = InputEventMessage.obtain(msg);
+ if (mPendingInputEvents == null) {
+ mPendingInputEvents = inputMessage;
+ } else {
+ InputEventMessage currMessage = mPendingInputEvents;
+ while (currMessage.mNext != null) {
+ currMessage = currMessage.mNext;
+ }
+ currMessage.mNext = inputMessage;
+ }
+ sendEmptyMessageAtTime(PROCESS_INPUT_EVENTS, when);
+ }
+
public void dispatchKey(KeyEvent event) {
dispatchKey(event, false);
}
@@ -3786,7 +3877,7 @@ public final class ViewRootImpl extends Handler implements ViewParent,
if (LOCAL_LOGV) Log.v(
TAG, "sending key " + event + " to " + mView);
- sendMessageAtTime(msg, event.getEventTime());
+ enqueueInputEvent(msg, event.getEventTime());
}
private void dispatchMotion(MotionEvent event, boolean sendDone) {
@@ -3804,21 +3895,21 @@ public final class ViewRootImpl extends Handler implements ViewParent,
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
- sendMessageAtTime(msg, event.getEventTime());
+ enqueueInputEvent(msg, event.getEventTime());
}
private void dispatchTrackball(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_TRACKBALL);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
- sendMessageAtTime(msg, event.getEventTime());
+ enqueueInputEvent(msg, event.getEventTime());
}
private void dispatchGenericMotion(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_GENERIC_MOTION);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
- sendMessageAtTime(msg, event.getEventTime());
+ enqueueInputEvent(msg, event.getEventTime());
}
public void dispatchAppVisibility(boolean visible) {
@@ -4473,19 +4564,20 @@ public final class ViewRootImpl extends Handler implements ViewParent,
* AccessibilityManagerService to the latter can interact with
* the view hierarchy in this ViewAncestor.
*/
- final class AccessibilityInteractionConnection
+ static final class AccessibilityInteractionConnection
extends IAccessibilityInteractionConnection.Stub {
- private final WeakReference<ViewRootImpl> mViewAncestor;
+ private final WeakReference<ViewRootImpl> mRootImpl;
AccessibilityInteractionConnection(ViewRootImpl viewAncestor) {
- mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
+ mRootImpl = new WeakReference<ViewRootImpl>(viewAncestor);
}
public void findAccessibilityNodeInfoByAccessibilityId(int accessibilityId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid) {
- if (mViewAncestor.get() != null) {
- getAccessibilityInteractionController()
+ ViewRootImpl viewRootImpl = mRootImpl.get();
+ if (viewRootImpl != null) {
+ viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityId,
interactionId, callback, interrogatingPid, interrogatingTid);
}
@@ -4494,8 +4586,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void performAccessibilityAction(int accessibilityId, int action,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interogatingPid, long interrogatingTid) {
- if (mViewAncestor.get() != null) {
- getAccessibilityInteractionController()
+ ViewRootImpl viewRootImpl = mRootImpl.get();
+ if (viewRootImpl != null) {
+ viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityId, action, interactionId,
callback, interogatingPid, interrogatingTid);
}
@@ -4504,8 +4597,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void findAccessibilityNodeInfoByViewId(int viewId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid) {
- if (mViewAncestor.get() != null) {
- getAccessibilityInteractionController()
+ ViewRootImpl viewRootImpl = mRootImpl.get();
+ if (viewRootImpl != null) {
+ viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback,
interrogatingPid, interrogatingTid);
}
@@ -4514,8 +4608,9 @@ public final class ViewRootImpl extends Handler implements ViewParent,
public void findAccessibilityNodeInfosByViewText(String text, int accessibilityId,
int interactionId, IAccessibilityInteractionConnectionCallback callback,
int interrogatingPid, long interrogatingTid) {
- if (mViewAncestor.get() != null) {
- getAccessibilityInteractionController()
+ ViewRootImpl viewRootImpl = mRootImpl.get();
+ if (viewRootImpl != null) {
+ viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfosByViewTextClientThread(text, accessibilityId,
interactionId, callback, interrogatingPid, interrogatingTid);
}
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index e8ab227..e74fec6 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -813,6 +813,17 @@ public interface WindowManager extends ViewManager {
public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;
/**
+ * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers
+ * may elect to skp these notifications if they are no doing anything productive with
+ * them (they do not affect the wallpaper scrolling operation) by calling
+ * {@link
+ * android.service.wallpaper.WallpaperService.Engine#setOffsetNotificationsEnabled(boolean)}.
+ *
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;
+
+ /**
* Control flags that are private to the platform.
* @hide
*/
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index 5ef4f3e..660e3f4 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -16,9 +16,12 @@
package android.view;
+import android.app.ActivityManager;
+import android.content.ComponentCallbacks2;
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
+import android.opengl.ManagedEGLContext;
import android.os.IBinder;
import android.util.AndroidRuntimeException;
import android.util.Log;
@@ -409,7 +412,30 @@ public class WindowManagerImpl implements WindowManager {
*/
public void trimMemory(int level) {
if (HardwareRenderer.isAvailable()) {
- HardwareRenderer.trimMemory(level);
+ switch (level) {
+ case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
+ case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
+ // On low and medium end gfx devices
+ if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) {
+ // Force a full memory flush
+ HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
+ // Destroy all hardware surfaces and resources associated to
+ // known windows
+ synchronized (this) {
+ if (mViews == null) return;
+ int count = mViews.length;
+ for (int i = 0; i < count; i++) {
+ mRoots[i].destroyHardwareResources();
+ }
+ }
+ // Terminate the hardware renderer to free all resources
+ ManagedEGLContext.doTerminate();
+ break;
+ }
+ // high end gfx devices fall through to next case
+ default:
+ HardwareRenderer.trimMemory(level);
+ }
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 7671312..fa34ee7 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -293,7 +293,7 @@ public class AccessibilityNodeInfo implements Parcelable {
*/
public AccessibilityNodeInfo getParent() {
enforceSealed();
- if (!canPerformRequestOverConnection(mAccessibilityViewId)) {
+ if (!canPerformRequestOverConnection(mParentAccessibilityViewId)) {
return null;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java
index fe06d98..a4e0688 100644
--- a/core/java/android/view/accessibility/AccessibilityRecord.java
+++ b/core/java/android/view/accessibility/AccessibilityRecord.java
@@ -391,8 +391,6 @@ public class AccessibilityRecord {
* Gets the max scroll offset of the source left edge in pixels.
*
* @return The max scroll.
- *
- * @hide
*/
public int getMaxScrollX() {
return mMaxScrollX;
@@ -401,8 +399,6 @@ public class AccessibilityRecord {
* Sets the max scroll offset of the source left edge in pixels.
*
* @param maxScrollX The max scroll.
- *
- * @hide
*/
public void setMaxScrollX(int maxScrollX) {
enforceNotSealed();
@@ -413,8 +409,6 @@ public class AccessibilityRecord {
* Gets the max scroll offset of the source top edge in pixels.
*
* @return The max scroll.
- *
- * @hide
*/
public int getMaxScrollY() {
return mMaxScrollY;
@@ -424,8 +418,6 @@ public class AccessibilityRecord {
* Sets the max scroll offset of the source top edge in pixels.
*
* @param maxScrollY The max scroll.
- *
- * @hide
*/
public void setMaxScrollY(int maxScrollY) {
enforceNotSealed();
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 3ead9df..b41e6f5 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -384,14 +384,18 @@ public final class InputMethodManager {
}
}
- class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
- public ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn) {
+ private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper {
+ private final InputMethodManager mParentInputMethodManager;
+
+ public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn,
+ final InputMethodManager inputMethodManager) {
super(mainLooper, conn);
+ mParentInputMethodManager = inputMethodManager;
}
@Override
public boolean isActive() {
- return mActive;
+ return mParentInputMethodManager.mActive;
}
}
@@ -439,7 +443,7 @@ public final class InputMethodManager {
mMainLooper = looper;
mH = new H(looper);
mIInputContext = new ControlledInputConnectionWrapper(looper,
- mDummyInputConnection);
+ mDummyInputConnection, this);
if (mInstance == null) {
mInstance = this;
@@ -1016,7 +1020,7 @@ public final class InputMethodManager {
mCursorCandStart = -1;
mCursorCandEnd = -1;
mCursorRect.setEmpty();
- servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic);
+ servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this);
} else {
servedContext = null;
}
diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java
index 793f514..489587e 100644
--- a/core/java/android/view/textservice/SpellCheckerSession.java
+++ b/core/java/android/view/textservice/SpellCheckerSession.java
@@ -123,7 +123,7 @@ public class SpellCheckerSession {
}
mSpellCheckerInfo = info;
mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler);
- mInternalListener = new InternalListener();
+ mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl);
mTextServicesManager = tsm;
mIsUsed = true;
mSpellCheckerSessionListener = listener;
@@ -146,12 +146,20 @@ public class SpellCheckerSession {
}
/**
+ * Cancel pending and running spell check tasks
+ */
+ public void cancel() {
+ mSpellCheckerSessionListenerImpl.cancel();
+ }
+
+ /**
* Finish this session and allow TextServicesManagerService to disconnect the bound spell
* checker.
*/
public void close() {
mIsUsed = false;
try {
+ mSpellCheckerSessionListenerImpl.close();
mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl);
} catch (RemoteException e) {
// do nothing
@@ -190,9 +198,10 @@ public class SpellCheckerSession {
private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub {
private static final int TASK_CANCEL = 1;
private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2;
+ private static final int TASK_CLOSE = 3;
private final Queue<SpellCheckerParams> mPendingTasks =
new LinkedList<SpellCheckerParams>();
- private final Handler mHandler;
+ private Handler mHandler;
private boolean mOpened;
private ISpellCheckerSession mISpellCheckerSession;
@@ -224,6 +233,9 @@ public class SpellCheckerSession {
case TASK_GET_SUGGESTIONS_MULTIPLE:
processGetSuggestionsMultiple(scp);
break;
+ case TASK_CLOSE:
+ processClose();
+ break;
}
}
@@ -237,6 +249,13 @@ public class SpellCheckerSession {
}
}
+ public void cancel() {
+ if (DBG) {
+ Log.w(TAG, "cancel");
+ }
+ processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false));
+ }
+
public void getSuggestionsMultiple(
TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) {
if (DBG) {
@@ -247,6 +266,13 @@ public class SpellCheckerSession {
suggestionsLimit, sequentialWords));
}
+ public void close() {
+ if (DBG) {
+ Log.w(TAG, "close");
+ }
+ processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false));
+ }
+
public boolean isDisconnected() {
return mOpened && mISpellCheckerSession == null;
}
@@ -263,8 +289,22 @@ public class SpellCheckerSession {
if (DBG) {
Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession);
}
+ SpellCheckerParams closeTask = null;
if (mISpellCheckerSession == null) {
+ if (scp.mWhat == TASK_CANCEL) {
+ while (!mPendingTasks.isEmpty()) {
+ final SpellCheckerParams tmp = mPendingTasks.poll();
+ if (tmp.mWhat == TASK_CLOSE) {
+ // Only one close task should be processed, while we need to remove all
+ // close tasks from the queue
+ closeTask = tmp;
+ }
+ }
+ }
mPendingTasks.offer(scp);
+ if (closeTask != null) {
+ mPendingTasks.offer(closeTask);
+ }
} else {
processTask(scp);
}
@@ -284,6 +324,22 @@ public class SpellCheckerSession {
}
}
+ private void processClose() {
+ if (!checkOpenConnection()) {
+ return;
+ }
+ if (DBG) {
+ Log.w(TAG, "Close spell checker tasks.");
+ }
+ try {
+ mISpellCheckerSession.onClose();
+ mISpellCheckerSession = null;
+ mHandler = null;
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to close " + e);
+ }
+ }
+
private void processGetSuggestionsMultiple(SpellCheckerParams scp) {
if (!checkOpenConnection()) {
return;
@@ -316,13 +372,19 @@ public class SpellCheckerSession {
public void onGetSuggestions(SuggestionsInfo[] results);
}
- private class InternalListener extends ITextServicesSessionListener.Stub {
+ private static class InternalListener extends ITextServicesSessionListener.Stub {
+ private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl;
+
+ public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) {
+ mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl;
+ }
+
@Override
public void onServiceConnected(ISpellCheckerSession session) {
if (DBG) {
Log.w(TAG, "SpellCheckerSession connected.");
}
- mSpellCheckerSessionListenerImpl.onServiceConnected(session);
+ mParentSpellCheckerSessionListenerImpl.onServiceConnected(session);
}
}
diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java
index 62a06b9..ddd0361 100644
--- a/core/java/android/view/textservice/SuggestionsInfo.java
+++ b/core/java/android/view/textservice/SuggestionsInfo.java
@@ -39,6 +39,12 @@ public final class SuggestionsInfo implements Parcelable {
* word looks like a typo.
*/
public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 0x0002;
+ /**
+ * Flag of the attributes of the suggestions that can be obtained by
+ * {@link #getSuggestionsAttributes}: this tells that the text service thinks
+ * the result suggestions include highly recommended ones.
+ */
+ public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004;
private final int mSuggestionsAttributes;
private final String[] mSuggestions;
private final boolean mSuggestionsAvailable;
diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java
index 2cc928f..c194559 100644
--- a/core/java/android/webkit/BrowserFrame.java
+++ b/core/java/android/webkit/BrowserFrame.java
@@ -813,8 +813,6 @@ class BrowserFrame extends Handler {
boolean synchronous,
String username,
String password) {
- PerfChecker checker = new PerfChecker();
-
if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) {
cacheMode = mSettings.getCacheMode();
}
@@ -872,11 +870,6 @@ class BrowserFrame extends Handler {
|| headers.containsKey("If-None-Match") ?
WebSettings.LOAD_NO_CACHE : cacheMode);
// Set referrer to current URL?
- if (!loader.executeLoad()) {
- checker.responseAlert("startLoadingResource fail");
- }
- checker.responseAlert("startLoadingResource succeed");
-
return !synchronous ? loadListener : null;
}
@@ -1411,4 +1404,17 @@ class BrowserFrame extends Handler {
native void nativeSslClientCert(int handle,
byte[] pkcs8EncodedPrivateKey,
byte[][] asn1DerEncodedCertificateChain);
+
+ /**
+ * Returns true when the contents of the frame is an RTL or vertical-rl
+ * page. This is used for determining whether a frame should be initially
+ * scrolled right-most as opposed to left-most.
+ * @return true when the frame should be initially scrolled right-most
+ * based on the text direction and writing mode.
+ */
+ /* package */ boolean getShouldStartScrolledRight() {
+ return nativeGetShouldStartScrolledRight(mNativeFrame);
+ }
+
+ private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame);
}
diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java
index b85fd17..fffa90b 100644
--- a/core/java/android/webkit/FindActionModeCallback.java
+++ b/core/java/android/webkit/FindActionModeCallback.java
@@ -18,6 +18,8 @@ package android.webkit;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.Point;
+import android.graphics.Rect;
import android.text.Editable;
import android.text.Selection;
import android.text.Spannable;
@@ -254,13 +256,18 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher,
// Does nothing. Needed to implement TextWatcher.
}
- public int getActionModeHeight() {
+ private Rect mGlobalVisibleRect = new Rect();
+ private Point mGlobalVisibleOffset = new Point();
+ public int getActionModeGlobalBottom() {
if (mActionMode == null) {
return 0;
}
- View parent = (View) mCustomView.getParent();
- return parent != null ? parent.getMeasuredHeight()
- : mCustomView.getMeasuredHeight();
+ View view = (View) mCustomView.getParent();
+ if (view == null) {
+ view = mCustomView;
+ }
+ view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
+ return mGlobalVisibleRect.bottom;
}
}
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 0ea27a0..f29aff2 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -150,7 +150,9 @@ public class HTML5VideoFullScreen extends HTML5VideoView
private void prepareForFullScreen() {
// So in full screen, we reset the MediaPlayer
mPlayer.reset();
- setMediaController(new MediaController(mProxy.getContext()));
+ MediaController mc = new MediaController(mProxy.getContext());
+ mc.setSystemUiVisibility(mLayout.getSystemUiVisibility());
+ setMediaController(mc);
mPlayer.setScreenOnWhilePlaying(true);
prepareDataAndDisplayMode(mProxy);
}
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 42581c2..fe5908e 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -74,11 +74,13 @@ public class HTML5VideoInline extends HTML5VideoView{
public SurfaceTexture getSurfaceTexture(int videoLayerId) {
// Create the surface texture.
if (videoLayerId != mVideoLayerUsingSurfaceTexture
- || mSurfaceTexture == null) {
- if (mTextureNames == null) {
- mTextureNames = new int[1];
- GLES20.glGenTextures(1, mTextureNames, 0);
+ || mSurfaceTexture == null
+ || mTextureNames == null) {
+ if (mTextureNames != null) {
+ GLES20.glDeleteTextures(1, mTextureNames, 0);
}
+ mTextureNames = new int[1];
+ GLES20.glGenTextures(1, mTextureNames, 0);
mSurfaceTexture = new SurfaceTexture(mTextureNames[0]);
}
mVideoLayerUsingSurfaceTexture = videoLayerId;
diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java
index 5b78586..b498435 100644
--- a/core/java/android/webkit/JWebCoreJavaBridge.java
+++ b/core/java/android/webkit/JWebCoreJavaBridge.java
@@ -87,11 +87,9 @@ final class JWebCoreJavaBridge extends Handler {
* Call native timer callbacks.
*/
private void fireSharedTimer() {
- PerfChecker checker = new PerfChecker();
// clear the flag so that sharedTimerFired() can set a new timer
mHasInstantTimer = false;
sharedTimerFired();
- checker.responseAlert("sharedTimer");
}
/**
diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java
index 7759ff3..4662040 100644
--- a/core/java/android/webkit/JniUtil.java
+++ b/core/java/android/webkit/JniUtil.java
@@ -22,6 +22,7 @@ import android.net.Uri;
import android.provider.Settings;
import android.util.Log;
+import java.io.File;
import java.io.InputStream;
class JniUtil {
@@ -79,7 +80,12 @@ class JniUtil {
checkInitialized();
if (sCacheDirectory == null) {
- sCacheDirectory = sContext.getCacheDir().getAbsolutePath();
+ File cacheDir = sContext.getCacheDir();
+ if (cacheDir == null) {
+ sCacheDirectory = "";
+ } else {
+ sCacheDirectory = cacheDir.getAbsolutePath();
+ }
}
return sCacheDirectory;
diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java
index 04af738..37e8bc0 100644
--- a/core/java/android/webkit/LoadListener.java
+++ b/core/java/android/webkit/LoadListener.java
@@ -1136,7 +1136,6 @@ class LoadListener extends Handler implements EventHandler {
// Give the data to WebKit now. We don't have to synchronize on
// mDataBuilder here because pulling each chunk removes it from the
// internal list so it cannot be modified.
- PerfChecker checker = new PerfChecker();
ByteArrayBuilder.Chunk c;
while (true) {
c = mDataBuilder.getFirstChunk();
@@ -1152,7 +1151,6 @@ class LoadListener extends Handler implements EventHandler {
} else {
c.release();
}
- checker.responseAlert("res nativeAddData");
}
}
@@ -1173,13 +1171,11 @@ class LoadListener extends Handler implements EventHandler {
WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget();
}
if (mNativeLoader != 0) {
- PerfChecker checker = new PerfChecker();
if (!mSetNativeResponse) {
setNativeResponse();
}
nativeFinished();
- checker.responseAlert("res nativeFinished");
clearNativeLoader();
}
}
diff --git a/core/java/android/webkit/PerfChecker.java b/core/java/android/webkit/PerfChecker.java
deleted file mode 100644
index 8c5f86e..0000000
--- a/core/java/android/webkit/PerfChecker.java
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2007 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.webkit;
-
-import android.os.SystemClock;
-import android.util.Log;
-
-class PerfChecker {
-
- private long mTime;
- private static final long mResponseThreshold = 2000; // 2s
-
- public PerfChecker() {
- if (false) {
- mTime = SystemClock.uptimeMillis();
- }
- }
-
- /**
- * @param what log string
- * Logs given string if mResponseThreshold time passed between either
- * instantiation or previous responseAlert call
- */
- public void responseAlert(String what) {
- if (false) {
- long upTime = SystemClock.uptimeMillis();
- long time = upTime - mTime;
- if (time > mResponseThreshold) {
- Log.w("webkit", what + " used " + time + " ms");
- }
- // Reset mTime, to permit reuse
- mTime = upTime;
- }
- }
-}
diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java
index ae40ded..3d129f7 100644
--- a/core/java/android/webkit/WebChromeClient.java
+++ b/core/java/android/webkit/WebChromeClient.java
@@ -95,27 +95,33 @@ public class WebChromeClient {
public void onHideCustomView() {}
/**
- * Request the host application to create a new Webview. The host
- * application should handle placement of the new WebView in the view
- * system. The default behavior returns null.
- * @param view The WebView that initiated the callback.
- * @param dialog True if the new window is meant to be a small dialog
- * window.
- * @param userGesture True if the request was initiated by a user gesture
- * such as clicking a link.
- * @param resultMsg The message to send when done creating a new WebView.
- * Set the new WebView through resultMsg.obj which is
- * WebView.WebViewTransport() and then call
- * resultMsg.sendToTarget();
- * @return Similar to javscript dialogs, this method should return true if
- * the client is going to handle creating a new WebView. Note that
- * the WebView will halt processing if this method returns true so
- * make sure to call resultMsg.sendToTarget(). It is undefined
- * behavior to call resultMsg.sendToTarget() after returning false
- * from this method.
+ * Request the host application to create a new window. If the host
+ * application chooses to honor this request, it should return true from
+ * this method, create a new WebView to host the window, insert it into the
+ * View system and send the supplied resultMsg message to its target with
+ * the new WebView as an argument. If the host application chooses not to
+ * honor the request, it should return false from this method. The default
+ * implementation of this method does nothing and hence returns false.
+ * @param view The WebView from which the request for a new window
+ * originated.
+ * @param isDialog True if the new window should be a dialog, rather than
+ * a full-size window.
+ * @param isUserGesture True if the request was initiated by a user gesture,
+ * such as the user clicking a link.
+ * @param resultMsg The message to send when once a new WebView has been
+ * created. resultMsg.obj is a
+ * {@link WebView.WebViewTransport} object. This should be
+ * used to transport the new WebView, by calling
+ * {@link WebView.WebViewTransport#setWebView(WebView)
+ * WebView.WebViewTransport.setWebView(WebView)}.
+ * @return This method should return true if the host application will
+ * create a new window, in which case resultMsg should be sent to
+ * its target. Otherwise, this method should return false. Returning
+ * false from this method but also sending resultMsg will result in
+ * undefined behavior.
*/
- public boolean onCreateWindow(WebView view, boolean dialog,
- boolean userGesture, Message resultMsg) {
+ public boolean onCreateWindow(WebView view, boolean isDialog,
+ boolean isUserGesture, Message resultMsg) {
return false;
}
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index f1c2bde..f947f95 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -42,12 +42,14 @@ public class WebSettings {
* SINGLE_COLUMN moves all content into one column that is the width of the
* view.
* NARROW_COLUMNS makes all columns no wider than the screen if possible.
- * @deprecated This enum is now obsolete.
*/
// XXX: These must match LayoutAlgorithm in Settings.h in WebCore.
- @Deprecated
public enum LayoutAlgorithm {
NORMAL,
+ /**
+ * @deprecated This algorithm is now obsolete.
+ */
+ @Deprecated
SINGLE_COLUMN,
NARROW_COLUMNS
}
@@ -777,7 +779,7 @@ public class WebSettings {
public void setDoubleTapZoom(int doubleTapZoom) {
if (mDoubleTapZoom != doubleTapZoom) {
mDoubleTapZoom = doubleTapZoom;
- mWebView.updateDoubleTapZoom();
+ mWebView.updateDoubleTapZoom(doubleTapZoom);
}
}
@@ -799,7 +801,7 @@ public class WebSettings {
public void setDefaultZoom(ZoomDensity zoom) {
if (mDefaultZoom != zoom) {
mDefaultZoom = zoom;
- mWebView.updateDefaultZoomDensity(zoom.value);
+ mWebView.adjustDefaultZoomDensity(zoom.value);
}
}
@@ -936,9 +938,7 @@ public class WebSettings {
* WebView.
* @param l A LayoutAlgorithm enum specifying the algorithm to use.
* @see WebSettings.LayoutAlgorithm
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) {
// XXX: This will only be affective if libwebcore was built with
// ANDROID_LAYOUT defined.
@@ -953,9 +953,7 @@ public class WebSettings {
* @return LayoutAlgorithm enum value describing the layout algorithm
* being used.
* @see WebSettings.LayoutAlgorithm
- * @deprecated This method is now obsolete.
*/
- @Deprecated
public synchronized LayoutAlgorithm getLayoutAlgorithm() {
return mLayoutAlgorithm;
}
diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java
index 5ee1b8a..f18a396 100644
--- a/core/java/android/webkit/WebTextView.java
+++ b/core/java/android/webkit/WebTextView.java
@@ -16,14 +16,11 @@
package android.webkit;
-import com.android.internal.widget.EditableInputConnection;
-
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.ColorFilter;
import android.graphics.Paint;
-import android.graphics.PixelFormat;
+import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
@@ -54,18 +51,19 @@ import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsoluteLayout;
import android.widget.AbsoluteLayout.LayoutParams;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
+import junit.framework.Assert;
+
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
-import junit.framework.Assert;
-
/**
* WebTextView is a specialized version of EditText used by WebView
* to overlay html textfields (and textareas) to use our standard
@@ -76,6 +74,9 @@ import junit.framework.Assert;
static final String LOGTAG = "webtextview";
+ private Paint mRingPaint;
+ private int mRingInset;
+
private WebView mWebView;
private boolean mSingle;
private int mWidthSpec;
@@ -206,7 +207,13 @@ import junit.framework.Assert;
}
}
};
+ float ringWidth = 4f * context.getResources().getDisplayMetrics().density;
mReceiver = new MyResultReceiver(mHandler);
+ mRingPaint = new Paint();
+ mRingPaint.setColor(0x6633b5e5);
+ mRingPaint.setStrokeWidth(ringWidth);
+ mRingPaint.setStyle(Style.FILL);
+ mRingInset = (int) ringWidth;
}
public void setAutoFillable(int queryId) {
@@ -216,6 +223,40 @@ import junit.framework.Assert;
}
@Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (isFocused()) {
+ final int ib = getHeight() - mRingInset;
+ canvas.drawRect(0, 0, getWidth(), mRingInset, mRingPaint);
+ canvas.drawRect(0, ib, getWidth(), getHeight(), mRingPaint);
+ canvas.drawRect(0, mRingInset, mRingInset, ib, mRingPaint);
+ canvas.drawRect(getWidth() - mRingInset, mRingInset, getWidth(), ib, mRingPaint);
+ }
+ }
+
+ private void growOrShrink(boolean grow) {
+ AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) getLayoutParams();
+ if (grow) {
+ Log.i("webtextview", "grow");
+ lp.x -= mRingInset;
+ lp.y -= mRingInset;
+ lp.width += 2 * mRingInset;
+ lp.height += 2 * mRingInset;
+ setPadding(getPaddingLeft() + mRingInset, getPaddingTop() + mRingInset,
+ getPaddingRight() + mRingInset, getPaddingBottom() + mRingInset);
+ } else {
+ Log.i("webtextview", "shrink");
+ lp.x += mRingInset;
+ lp.y += mRingInset;
+ lp.width -= 2 * mRingInset;
+ lp.height -= 2 * mRingInset;
+ setPadding(getPaddingLeft() - mRingInset, getPaddingTop() - mRingInset,
+ getPaddingRight() - mRingInset, getPaddingBottom() - mRingInset);
+ }
+ setLayoutParams(lp);
+ }
+
+ @Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (event.isSystem()) {
return super.dispatchKeyEvent(event);
@@ -516,6 +557,7 @@ import junit.framework.Assert;
} else if (!mInsideRemove) {
mWebView.setActive(false);
}
+ growOrShrink(focused);
mFromFocusChange = false;
}
@@ -572,7 +614,6 @@ import junit.framework.Assert;
mPreChange.substring(0, mMaxLength).equals(postChange))) {
return;
}
- mPreChange = postChange;
if (0 == count) {
if (before > 0) {
// For this and all changes to the text, update our cache
@@ -610,9 +651,9 @@ import junit.framework.Assert;
// Prefer sending javascript events, so when adding one character,
// don't replace the unchanged text.
if (count > 1 && before == count - 1) {
- String replaceButOne = s.subSequence(start,
+ String replaceButOne = mPreChange.subSequence(start,
start + before).toString();
- String replacedString = getText().subSequence(start,
+ String replacedString = s.subSequence(start,
start + before).toString();
if (replaceButOne.equals(replacedString)) {
// we're just adding one character
@@ -621,6 +662,7 @@ import junit.framework.Assert;
count = 1;
}
}
+ mPreChange = postChange;
// Find the last character being replaced. If it can be represented by
// events, we will pass them to native so we can see javascript events.
// Otherwise, replace the text being changed in the textfield.
@@ -926,18 +968,23 @@ import junit.framework.Assert;
*/
/* package */ void setRect(int x, int y, int width, int height) {
LayoutParams lp = (LayoutParams) getLayoutParams();
+ boolean needsUpdate = false;
if (null == lp) {
lp = new LayoutParams(width, height, x, y);
} else {
- lp.x = x;
- lp.y = y;
- lp.width = width;
- lp.height = height;
+ if ((lp.x != x) || (lp.y != y) || (lp.width != width)
+ || (lp.height != height)) {
+ needsUpdate = true;
+ lp.x = x;
+ lp.y = y;
+ lp.width = width;
+ lp.height = height;
+ }
}
if (getParent() == null) {
// Insert the view so that it's drawn first (at index 0)
mWebView.addView(this, 0, lp);
- } else {
+ } else if (needsUpdate) {
setLayoutParams(lp);
}
// Set up a measure spec so a layout can always be recreated.
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index fef9b02..35fd945 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -119,6 +119,11 @@ import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.microedition.khronos.egl.EGL10;
+import javax.microedition.khronos.egl.EGLContext;
+import javax.microedition.khronos.egl.EGLDisplay;
+import static javax.microedition.khronos.egl.EGL10.*;
+
/**
* <p>A View that displays web pages. This class is the basis upon which you
* can roll your own web browser or simply display some online content within your Activity.
@@ -641,6 +646,7 @@ public class WebView extends AbsoluteLayout
private boolean mAccessibilityScriptInjected;
static final boolean USE_JAVA_TEXT_SELECTION = true;
+ static final boolean DEBUG_TEXT_HANDLES = false;
private Region mTextSelectionRegion = new Region();
private Paint mTextSelectionPaint;
private Drawable mSelectHandleLeft;
@@ -682,7 +688,6 @@ public class WebView extends AbsoluteLayout
private static final int SWITCH_TO_LONGPRESS = 4;
private static final int RELEASE_SINGLE_TAP = 5;
private static final int REQUEST_FORM_DATA = 6;
- private static final int RESUME_WEBCORE_PRIORITY = 7;
private static final int DRAG_HELD_MOTIONLESS = 8;
private static final int AWAKEN_SCROLL_BARS = 9;
private static final int PREVENT_DEFAULT_TIMEOUT = 10;
@@ -909,6 +914,9 @@ public class WebView extends AbsoluteLayout
private Rect mScrollingLayerBounds = new Rect();
private boolean mSentAutoScrollMessage = false;
+ // Temporary hack to work around the context removal upon memory pressure
+ private static boolean mIncrementEGLContextHack = false;
+
// used for serializing asynchronously handled touch events.
private final TouchEventQueue mTouchEventQueue = new TouchEventQueue();
@@ -1341,9 +1349,13 @@ public class WebView extends AbsoluteLayout
}
}
- /* package */void updateDefaultZoomDensity(int zoomDensity) {
+ /* package */ void adjustDefaultZoomDensity(int zoomDensity) {
final float density = mContext.getResources().getDisplayMetrics().density
* 100 / zoomDensity;
+ updateDefaultZoomDensity(density);
+ }
+
+ /* package */ void updateDefaultZoomDensity(float density) {
mNavSlop = (int) (16 * density);
mZoomManager.updateDefaultZoomDensity(density);
}
@@ -1484,7 +1496,21 @@ public class WebView extends AbsoluteLayout
private int getVisibleTitleHeightImpl() {
// need to restrict mScrollY due to over scroll
return Math.max(getTitleHeight() - Math.max(0, mScrollY),
- mFindCallback != null ? mFindCallback.getActionModeHeight() : 0);
+ getOverlappingActionModeHeight());
+ }
+
+ private int mCachedOverlappingActionModeHeight = -1;
+
+ private int getOverlappingActionModeHeight() {
+ if (mFindCallback == null) {
+ return 0;
+ }
+ if (mCachedOverlappingActionModeHeight < 0) {
+ getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset);
+ mCachedOverlappingActionModeHeight = Math.max(0,
+ mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top);
+ }
+ return mCachedOverlappingActionModeHeight;
}
/*
@@ -2004,15 +2030,18 @@ public class WebView extends AbsoluteLayout
}
/**
- * Load the given url with the extra headers.
- * @param url The url of the resource to load.
- * @param extraHeaders The extra headers sent with this url. This should not
- * include the common headers like "user-agent". If it does, it
- * will be replaced by the intrinsic value of the WebView.
+ * Load the given URL with the specified additional HTTP headers.
+ * @param url The URL of the resource to load.
+ * @param additionalHttpHeaders The additional headers to be used in the
+ * HTTP request for this URL, specified as a map from name to
+ * value. Note that if this map contains any of the headers
+ * that are set by default by the WebView, such as those
+ * controlling caching, accept types or the User-Agent, their
+ * values may be overriden by the WebView's defaults.
*/
- public void loadUrl(String url, Map<String, String> extraHeaders) {
+ public void loadUrl(String url, Map<String, String> additionalHttpHeaders) {
checkThread();
- loadUrlImpl(url, extraHeaders);
+ loadUrlImpl(url, additionalHttpHeaders);
}
private void loadUrlImpl(String url, Map<String, String> extraHeaders) {
@@ -2025,8 +2054,8 @@ public class WebView extends AbsoluteLayout
}
/**
- * Load the given url.
- * @param url The url of the resource to load.
+ * Load the given URL.
+ * @param url The URL of the resource to load.
*/
public void loadUrl(String url) {
checkThread();
@@ -2452,7 +2481,9 @@ public class WebView extends AbsoluteLayout
* Set the initial scale for the WebView. 0 means default. If
* {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the
* way. Otherwise it starts with 100%. If initial scale is greater than 0,
- * WebView starts will this value as initial scale.
+ * WebView starts with this value as initial scale.
+ * Please note that unlike the scale properties in the viewport meta tag,
+ * this method doesn't take the screen density into account.
*
* @param scaleInPercent The initial scale in percent.
*/
@@ -2844,48 +2875,49 @@ public class WebView extends AbsoluteLayout
}
// Used to avoid sending many visible rect messages.
- private Rect mLastVisibleRectSent;
- private Rect mLastGlobalRect;
+ private Rect mLastVisibleRectSent = new Rect();
+ private Rect mLastGlobalRect = new Rect();
+ private Rect mVisibleRect = new Rect();
+ private Rect mGlobalVisibleRect = new Rect();
+ private Point mScrollOffset = new Point();
Rect sendOurVisibleRect() {
if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent;
- Rect rect = new Rect();
- calcOurContentVisibleRect(rect);
+ calcOurContentVisibleRect(mVisibleRect);
// Rect.equals() checks for null input.
- if (!rect.equals(mLastVisibleRectSent)) {
+ if (!mVisibleRect.equals(mLastVisibleRectSent)) {
if (!mBlockWebkitViewMessages) {
- Point pos = new Point(rect.left, rect.top);
+ mScrollOffset.set(mVisibleRect.left, mVisibleRect.top);
mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET);
mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET,
- nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, pos);
+ nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, mScrollOffset);
}
- mLastVisibleRectSent = rect;
+ mLastVisibleRectSent.set(mVisibleRect);
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
}
- Rect globalRect = new Rect();
- if (getGlobalVisibleRect(globalRect)
- && !globalRect.equals(mLastGlobalRect)) {
+ if (getGlobalVisibleRect(mGlobalVisibleRect)
+ && !mGlobalVisibleRect.equals(mLastGlobalRect)) {
if (DebugFlags.WEB_VIEW) {
- Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + ","
- + globalRect.top + ",r=" + globalRect.right + ",b="
- + globalRect.bottom);
+ Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + ","
+ + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b="
+ + mGlobalVisibleRect.bottom);
}
// TODO: the global offset is only used by windowRect()
// in ChromeClientAndroid ; other clients such as touch
// and mouse events could return view + screen relative points.
if (!mBlockWebkitViewMessages) {
- mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect);
+ mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect);
}
- mLastGlobalRect = globalRect;
+ mLastGlobalRect.set(mGlobalVisibleRect);
}
- return rect;
+ return mVisibleRect;
}
+ private Point mGlobalVisibleOffset = new Point();
// Sets r to be the visible rectangle of our webview in view coordinates
private void calcOurVisibleRect(Rect r) {
- Point p = new Point();
- getGlobalVisibleRect(r, p);
- r.offset(-p.x, -p.y);
+ getGlobalVisibleRect(r, mGlobalVisibleOffset);
+ r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y);
}
// Sets r to be our visible rectangle in content coordinates
@@ -2901,21 +2933,21 @@ public class WebView extends AbsoluteLayout
r.bottom = viewToContentY(r.bottom);
}
+ private Rect mContentVisibleRect = new Rect();
// Sets r to be our visible rectangle in content coordinates. We use this
// method on the native side to compute the position of the fixed layers.
// Uses floating coordinates (necessary to correctly place elements when
// the scale factor is not 1)
private void calcOurContentVisibleRectF(RectF r) {
- Rect ri = new Rect(0,0,0,0);
- calcOurVisibleRect(ri);
- r.left = viewToContentXf(ri.left);
+ calcOurVisibleRect(mContentVisibleRect);
+ r.left = viewToContentXf(mContentVisibleRect.left);
// viewToContentY will remove the total height of the title bar. Add
// the visible height back in to account for the fact that if the title
// bar is partially visible, the part of the visible rect which is
// displaying our content is displaced by that amount.
- r.top = viewToContentYf(ri.top + getVisibleTitleHeightImpl());
- r.right = viewToContentXf(ri.right);
- r.bottom = viewToContentYf(ri.bottom);
+ r.top = viewToContentYf(mContentVisibleRect.top + getVisibleTitleHeightImpl());
+ r.right = viewToContentXf(mContentVisibleRect.right);
+ r.bottom = viewToContentYf(mContentVisibleRect.bottom);
}
static class ViewSizeData {
@@ -2992,8 +3024,8 @@ public class WebView extends AbsoluteLayout
/**
* Update the double-tap zoom.
*/
- /* package */ void updateDoubleTapZoom() {
- mZoomManager.updateDoubleTapZoom();
+ /* package */ void updateDoubleTapZoom(int doubleTapZoom) {
+ mZoomManager.updateDoubleTapZoom(doubleTapZoom);
}
private int computeRealHorizontalScrollRange() {
@@ -3079,10 +3111,7 @@ public class WebView extends AbsoluteLayout
// Special-case layer scrolling so that we do not trigger normal scroll
// updating.
if (mTouchMode == TOUCH_DRAG_LAYER_MODE) {
- nativeScrollLayer(mScrollingLayer, scrollX, scrollY);
- mScrollingLayerRect.left = scrollX;
- mScrollingLayerRect.top = scrollY;
- invalidate();
+ scrollLayerTo(scrollX, scrollY);
return;
}
mInOverScrollMode = false;
@@ -3236,6 +3265,26 @@ public class WebView extends AbsoluteLayout
if (mHTML5VideoViewProxy != null) {
mHTML5VideoViewProxy.pauseAndDispatch();
}
+ if (mNativeClass != 0) {
+ nativeSetPauseDrawing(mNativeClass, true);
+ }
+ }
+ }
+
+ @Override
+ protected void onWindowVisibilityChanged(int visibility) {
+ super.onWindowVisibilityChanged(visibility);
+ updateDrawingState();
+ }
+
+ void updateDrawingState() {
+ if (mNativeClass == 0 || mIsPaused) return;
+ if (getWindowVisibility() != VISIBLE) {
+ nativeSetPauseDrawing(mNativeClass, true);
+ } else if (getVisibility() != VISIBLE) {
+ nativeSetPauseDrawing(mNativeClass, true);
+ } else {
+ nativeSetPauseDrawing(mNativeClass, false);
}
}
@@ -3247,6 +3296,9 @@ public class WebView extends AbsoluteLayout
if (mIsPaused) {
mIsPaused = false;
mWebViewCore.sendMessage(EventHub.ON_RESUME);
+ if (mNativeClass != 0) {
+ nativeSetPauseDrawing(mNativeClass, false);
+ }
}
}
@@ -3371,6 +3423,7 @@ public class WebView extends AbsoluteLayout
// Could not start the action mode, so end Find on page
return false;
}
+ mCachedOverlappingActionModeHeight = -1;
mFindCallback = callback;
setFindIsUp(true);
mFindCallback.setWebView(this);
@@ -3488,6 +3541,7 @@ public class WebView extends AbsoluteLayout
*/
void notifyFindDialogDismissed() {
mFindCallback = null;
+ mCachedOverlappingActionModeHeight = -1;
if (mWebViewCore == null) {
return;
}
@@ -3560,12 +3614,9 @@ public class WebView extends AbsoluteLayout
mScrollY = y;
} else {
// Update the layer position instead of WebView.
- nativeScrollLayer(mScrollingLayer, x, y);
- mScrollingLayerRect.left = x;
- mScrollingLayerRect.top = y;
+ scrollLayerTo(x, y);
}
abortAnimation();
- mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
nativeSetIsScrolling(false);
if (!mBlockWebkitViewMessages) {
WebViewCore.resumePriority();
@@ -3582,6 +3633,17 @@ public class WebView extends AbsoluteLayout
}
}
+ private void scrollLayerTo(int x, int y) {
+ if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) {
+ return;
+ }
+ nativeScrollLayer(mScrollingLayer, x, y);
+ mScrollingLayerRect.left = x;
+ mScrollingLayerRect.top = y;
+ onScrollChanged(mScrollX, mScrollY, mScrollX, mScrollY);
+ invalidate();
+ }
+
private static int computeDuration(int dx, int dy) {
int distance = Math.max(Math.abs(dx), Math.abs(dy));
int duration = distance * 1000 / STD_SPEED;
@@ -4115,20 +4177,6 @@ public class WebView extends AbsoluteLayout
}
private void drawContent(Canvas canvas, boolean drawRings) {
- // Update the buttons in the picture, so when we draw the picture
- // to the screen, they are in the correct state.
- // Tell the native side if user is a) touching the screen,
- // b) pressing the trackball down, or c) pressing the enter key
- // If the cursor is on a button, we need to draw it in the pressed
- // state.
- // If mNativeClass is 0, we should not reach here, so we do not
- // need to check it again.
- boolean pressed = (mTouchMode == TOUCH_SHORTPRESS_START_MODE
- || mTouchMode == TOUCH_INIT_MODE
- || mTouchMode == TOUCH_SHORTPRESS_MODE);
- recordButtons(canvas,
- hasFocus() && hasWindowFocus(), (pressed && !USE_WEBKIT_RINGS)
- || mTrackballDown || mGotCenterDown, false);
drawCoreAndCursorRing(canvas, mBackgroundColor,
mDrawCursorRing && drawRings);
}
@@ -4185,6 +4233,13 @@ public class WebView extends AbsoluteLayout
}
if (canvas.isHardwareAccelerated()) {
+ if (mIncrementEGLContextHack == false) {
+ mIncrementEGLContextHack = true;
+ EGL10 egl = (EGL10) EGLContext.getEGL();
+ EGLDisplay eglDisplay = egl.eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ int[] version = new int[2];
+ egl.eglInitialize(eglDisplay, version);
+ }
mZoomManager.setHardwareAccelerated();
}
@@ -4352,6 +4407,7 @@ public class WebView extends AbsoluteLayout
@Override
protected void onConfigurationChanged(Configuration newConfig) {
+ mCachedOverlappingActionModeHeight = -1;
if (mSelectingText && mOrientation != newConfig.orientation) {
selectionDone();
}
@@ -4504,6 +4560,9 @@ public class WebView extends AbsoluteLayout
if (mHeldMotionless == MOTIONLESS_FALSE) {
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME);
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(AWAKEN_SCROLL_BARS),
+ ViewConfiguration.getScrollDefaultDelay());
mHeldMotionless = MOTIONLESS_PENDING;
}
}
@@ -4531,7 +4590,7 @@ public class WebView extends AbsoluteLayout
int extras = DRAW_EXTRAS_NONE;
if (mFindIsUp) {
extras = DRAW_EXTRAS_FIND;
- } else if (mSelectingText && !USE_JAVA_TEXT_SELECTION) {
+ } else if (mSelectingText && (!USE_JAVA_TEXT_SELECTION || DEBUG_TEXT_HANDLES)) {
extras = DRAW_EXTRAS_SELECTION;
nativeSetSelectionPointer(mNativeClass,
mDrawSelectionPointer,
@@ -4603,33 +4662,29 @@ public class WebView extends AbsoluteLayout
mTextSelectionPaint.setColor(HIGHLIGHT_COLOR);
}
mTextSelectionRegion.setEmpty();
- nativeGetTextSelectionRegion(mTextSelectionRegion);
+ nativeGetTextSelectionRegion(mNativeClass, mTextSelectionRegion);
Rect r = new Rect();
RegionIterator iter = new RegionIterator(mTextSelectionRegion);
- int start_x = -1;
- int start_y = -1;
- int end_x = -1;
- int end_y = -1;
+ Rect clip = canvas.getClipBounds();
while (iter.next(r)) {
- r = new Rect(
- contentToViewDimension(r.left),
+ r.set(contentToViewDimension(r.left),
contentToViewDimension(r.top),
contentToViewDimension(r.right),
contentToViewDimension(r.bottom));
- // Regions are in order. First one is where selection starts,
- // last one is where it ends
- if (start_x < 0 || start_y < 0) {
- start_x = r.left;
- start_y = r.bottom;
+ if (r.intersect(clip)) {
+ canvas.drawRect(r, mTextSelectionPaint);
}
- end_x = r.right;
- end_y = r.bottom;
- canvas.drawRect(r, mTextSelectionPaint);
}
if (mSelectHandleLeft == null) {
mSelectHandleLeft = mContext.getResources().getDrawable(
com.android.internal.R.drawable.text_select_handle_left);
}
+ int[] handles = new int[4];
+ nativeGetSelectionHandles(mNativeClass, handles);
+ int start_x = contentToViewDimension(handles[0]);
+ int start_y = contentToViewDimension(handles[1]);
+ int end_x = contentToViewDimension(handles[2]);
+ int end_y = contentToViewDimension(handles[3]);
// Magic formula copied from TextView
start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4;
mSelectHandleLeft.setBounds(start_x, start_y,
@@ -4643,6 +4698,10 @@ public class WebView extends AbsoluteLayout
mSelectHandleRight.setBounds(end_x, end_y,
end_x + mSelectHandleRight.getIntrinsicWidth(),
end_y + mSelectHandleRight.getIntrinsicHeight());
+ if (DEBUG_TEXT_HANDLES) {
+ mSelectHandleLeft.setAlpha(125);
+ mSelectHandleRight.setAlpha(125);
+ }
mSelectHandleLeft.draw(canvas);
mSelectHandleRight.draw(canvas);
}
@@ -5186,9 +5245,6 @@ public class WebView extends AbsoluteLayout
mGotCenterDown = true;
mPrivateHandler.sendMessageDelayed(mPrivateHandler
.obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT);
- // Already checked mNativeClass, so we do not need to check it
- // again.
- recordButtons(null, hasFocus() && hasWindowFocus(), true, true);
if (!wantsKeyEvents) return true;
}
// Bubble up the key event as WebView doesn't handle it
@@ -5403,6 +5459,10 @@ public class WebView extends AbsoluteLayout
}
mExtendSelection = false;
mSelectingText = mDrawSelectionPointer = true;
+ if (DEBUG_TEXT_HANDLES) {
+ // Debugging text handles requires running in software mode
+ setLayerType(LAYER_TYPE_SOFTWARE, null);
+ }
// don't let the picture change during text selection
WebViewCore.pauseUpdatePicture(mWebViewCore);
if (nativeHasCursorNode()) {
@@ -5481,6 +5541,11 @@ public class WebView extends AbsoluteLayout
void selectionDone() {
if (mSelectingText) {
mSelectingText = false;
+ if (DEBUG_TEXT_HANDLES) {
+ // Debugging text handles required running in software mode, set
+ // back to default now
+ setLayerType(LAYER_TYPE_NONE, null);
+ }
// finish is idempotent, so this is fine even if selectionDone was
// called by mSelectCallback.onDestroyActionMode
mSelectCallback.finish();
@@ -5584,6 +5649,7 @@ public class WebView extends AbsoluteLayout
if (visibility != View.VISIBLE && mZoomManager != null) {
mZoomManager.dismissZoomPicker();
}
+ updateDrawingState();
}
/**
@@ -5613,16 +5679,13 @@ public class WebView extends AbsoluteLayout
if (hasFocus()) {
// If our window regained focus, and we have focus, then begin
// drawing the cursor ring
- mDrawCursorRing = true;
+ mDrawCursorRing = !inEditingMode();
setFocusControllerActive(true);
- if (mNativeClass != 0) {
- recordButtons(null, true, false, true);
- }
} else {
+ mDrawCursorRing = false;
if (!inEditingMode()) {
// If our window gained focus, but we do not have it, do not
// draw the cursor ring.
- mDrawCursorRing = false;
setFocusControllerActive(false);
}
// We do not call recordButtons here because we assume
@@ -5643,9 +5706,6 @@ public class WebView extends AbsoluteLayout
mKeysPressed.clear();
mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS);
mTouchMode = TOUCH_DONE_MODE;
- if (mNativeClass != 0) {
- recordButtons(null, false, false, true);
- }
setFocusControllerActive(false);
}
invalidate();
@@ -5700,10 +5760,7 @@ public class WebView extends AbsoluteLayout
// When we regain focus, if we have window focus, resume drawing
// the cursor ring
if (hasWindowFocus()) {
- mDrawCursorRing = true;
- if (mNativeClass != 0) {
- recordButtons(null, true, false, true);
- }
+ mDrawCursorRing = !inEditingMode();
setFocusControllerActive(true);
//} else {
// The WebView has gained focus while we do not have
@@ -5713,11 +5770,8 @@ public class WebView extends AbsoluteLayout
} else {
// When we lost focus, unless focus went to the TextView (which is
// true if we are in editing mode), stop drawing the cursor ring.
+ mDrawCursorRing = false;
if (!inEditingMode()) {
- mDrawCursorRing = false;
- if (mNativeClass != 0) {
- recordButtons(null, false, false, true);
- }
setFocusControllerActive(false);
}
mKeysPressed.clear();
@@ -6005,7 +6059,7 @@ public class WebView extends AbsoluteLayout
mScroller.abortAnimation();
mTouchMode = TOUCH_DRAG_START_MODE;
mConfirmMove = true;
- mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
+ nativeSetIsScrolling(false);
} else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) {
mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP);
if (USE_WEBKIT_RINGS || getSettings().supportTouchOnly()) {
@@ -6288,9 +6342,16 @@ public class WebView extends AbsoluteLayout
}
mLastTouchX = x;
mLastTouchY = y;
- if ((deltaX | deltaY) != 0) {
+
+ if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) {
mHeldMotionless = MOTIONLESS_FALSE;
+ nativeSetIsScrolling(true);
+ } else {
+ mHeldMotionless = MOTIONLESS_TRUE;
+ nativeSetIsScrolling(false);
+ keepScrollBarsVisible = true;
}
+
mLastTouchTime = eventTime;
}
@@ -6306,9 +6367,15 @@ public class WebView extends AbsoluteLayout
// keep the scrollbar on the screen even there is no scroll
awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(),
false);
+ // Post a message so that we'll keep them alive while we're not scrolling.
+ mPrivateHandler.sendMessageDelayed(mPrivateHandler
+ .obtainMessage(AWAKEN_SCROLL_BARS),
+ ViewConfiguration.getScrollDefaultDelay());
// return false to indicate that we can't pan out of the
// view space
return !done;
+ } else {
+ mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS);
}
break;
}
@@ -6817,7 +6884,6 @@ public class WebView extends AbsoluteLayout
if (mNativeClass == 0) {
return false;
}
- recordButtons(null, hasFocus() && hasWindowFocus(), true, true);
if (time - mLastCursorTime <= TRACKBALL_TIMEOUT
&& !mLastCursorBounds.equals(nativeGetCursorRingBounds())) {
nativeSelectBestAt(mLastCursorBounds);
@@ -7329,7 +7395,6 @@ public class WebView extends AbsoluteLayout
mLastTouchTime = eventTime;
if (!mScroller.isFinished()) {
abortAnimation();
- mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY);
}
mSnapScrollMode = SNAP_NONE;
mVelocityTracker = VelocityTracker.obtain();
@@ -8271,12 +8336,8 @@ public class WebView extends AbsoluteLayout
if (mScrollingLayer == 0) {
pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0);
} else {
- mScrollingLayerRect.left += mAutoScrollX;
- mScrollingLayerRect.top += mAutoScrollY;
- nativeScrollLayer(mScrollingLayer,
- mScrollingLayerRect.left,
- mScrollingLayerRect.top);
- invalidate();
+ scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX,
+ mScrollingLayerRect.top + mAutoScrollY);
}
sendEmptyMessageDelayed(
SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL);
@@ -8389,6 +8450,9 @@ public class WebView extends AbsoluteLayout
setNewPicture(mDelaySetPicture, true);
mDelaySetPicture = null;
}
+ if (mIsPaused) {
+ nativeSetPauseDrawing(mNativeClass, true);
+ }
break;
case UPDATE_TEXTFIELD_TEXT_MSG_ID:
// Make sure that the textfield is currently focused
@@ -8461,10 +8525,6 @@ public class WebView extends AbsoluteLayout
mWebTextView.setAdapterCustom(adapter);
}
break;
- case RESUME_WEBCORE_PRIORITY:
- WebViewCore.resumePriority();
- WebViewCore.resumeUpdatePicture(mWebViewCore);
- break;
case LONG_PRESS_CENTER:
// as this is shared by keydown and trackballdown, reset all
@@ -8723,27 +8783,6 @@ public class WebView extends AbsoluteLayout
isPictureAfterFirstLayout, registerPageSwapCallback);
}
final Point viewSize = draw.mViewSize;
- if (isPictureAfterFirstLayout) {
- // Reset the last sent data here since dealing with new page.
- mLastWidthSent = 0;
- mZoomManager.onFirstLayout(draw);
- if (!mDrawHistory) {
- // Do not send the scroll event for this particular
- // scroll message. Note that a scroll event may
- // still be fired if the user scrolls before the
- // message can be handled.
- mSendScrollEvent = false;
- setContentScrollTo(viewState.mScrollX, viewState.mScrollY);
- mSendScrollEvent = true;
-
- // As we are on a new page, remove the WebTextView. This
- // is necessary for page loads driven by webkit, and in
- // particular when the user was on a password field, so
- // the WebTextView was visible.
- clearTextEntry();
- }
- }
-
// We update the layout (i.e. request a layout from the
// view system) if the last view size that we sent to
// WebCore matches the view size of the picture we just
@@ -8756,7 +8795,25 @@ public class WebView extends AbsoluteLayout
mSendScrollEvent = false;
recordNewContentSize(draw.mContentSize.x,
draw.mContentSize.y, updateLayout);
+
+ if (isPictureAfterFirstLayout) {
+ // Reset the last sent data here since dealing with new page.
+ mLastWidthSent = 0;
+ mZoomManager.onFirstLayout(draw);
+ int scrollX = viewState.mShouldStartScrolledRight
+ ? getContentWidth() : viewState.mScrollX;
+ int scrollY = viewState.mScrollY;
+ setContentScrollTo(scrollX, scrollY);
+ if (!mDrawHistory) {
+ // As we are on a new page, remove the WebTextView. This
+ // is necessary for page loads driven by webkit, and in
+ // particular when the user was on a password field, so
+ // the WebTextView was visible.
+ clearTextEntry();
+ }
+ }
mSendScrollEvent = true;
+
if (DebugFlags.WEB_VIEW) {
Rect b = draw.mInvalRegion.getBounds();
Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" +
@@ -9412,24 +9469,6 @@ public class WebView extends AbsoluteLayout
return nativeTileProfilingGetFloat(frame, tile, key);
}
- /**
- * Helper method to deal with differences between hardware and software rendering
- */
- private void recordButtons(Canvas canvas, boolean focus, boolean pressed,
- boolean inval) {
- boolean isHardwareAccel = canvas != null
- ? canvas.isHardwareAccelerated()
- : isHardwareAccelerated();
- if (isHardwareAccel) {
- // We never want to change button state if we are hardware accelerated,
- // but we DO want to invalidate as necessary so that the GL ring
- // can be drawn
- nativeRecordButtons(mNativeClass, false, false, inval);
- } else {
- nativeRecordButtons(mNativeClass, focus, pressed, inval);
- }
- }
-
private native int nativeCacheHitFramePointer();
private native boolean nativeCacheHitIsPlugin();
private native Rect nativeCacheHitNodeBounds();
@@ -9526,8 +9565,6 @@ public class WebView extends AbsoluteLayout
private native boolean nativePointInNavCache(int x, int y, int slop);
// Like many other of our native methods, you must make sure that
// mNativeClass is not null before calling this method.
- private native void nativeRecordButtons(int nativeInstance,
- boolean focused, boolean pressed, boolean invalidate);
private native void nativeResetSelection();
private native Point nativeSelectableText();
private native void nativeSelectAll();
@@ -9588,9 +9625,11 @@ public class WebView extends AbsoluteLayout
private native int nativeGetBackgroundColor();
native boolean nativeSetProperty(String key, String value);
native String nativeGetProperty(String key);
- private native void nativeGetTextSelectionRegion(Region region);
+ private native void nativeGetTextSelectionRegion(int instance, Region region);
+ private native void nativeGetSelectionHandles(int instance, int[] handles);
/**
* See {@link ComponentCallbacks2} for the trim levels and descriptions
*/
private static native void nativeOnTrimMemory(int level);
+ private static native void nativeSetPauseDrawing(int instance, boolean pause);
}
diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java
index 1294a28..a97f4dd 100644
--- a/core/java/android/webkit/WebViewCore.java
+++ b/core/java/android/webkit/WebViewCore.java
@@ -124,6 +124,7 @@ public final class WebViewCore {
*/
private int mViewportDensityDpi = -1;
+ private boolean mIsRestored = false;
private float mRestoredScale = 0;
private float mRestoredTextWrapScale = 0;
private int mRestoredX = 0;
@@ -1981,6 +1982,7 @@ public final class WebViewCore {
int mScrollY;
boolean mMobileSite;
boolean mIsRestored;
+ boolean mShouldStartScrolledRight;
}
static class DrawData {
@@ -2254,7 +2256,7 @@ public final class WebViewCore {
if (mWebView == null) return;
- boolean updateViewState = standardLoad || mRestoredScale > 0;
+ boolean updateViewState = standardLoad || mIsRestored;
setupViewport(updateViewState);
// if updateRestoreState is true, ViewManager.postReadyToDrawAll() will
// be called after the WebView updates its state. If updateRestoreState
@@ -2271,6 +2273,7 @@ public final class WebViewCore {
// reset the scroll position, the restored offset and scales
mRestoredX = mRestoredY = 0;
+ mIsRestored = false;
mRestoredScale = mRestoredTextWrapScale = 0;
}
@@ -2288,6 +2291,18 @@ public final class WebViewCore {
// set the viewport settings from WebKit
setViewportSettingsFromNative();
+ // clamp initial scale
+ if (mViewportInitialScale > 0) {
+ if (mViewportMinimumScale > 0) {
+ mViewportInitialScale = Math.max(mViewportInitialScale,
+ mViewportMinimumScale);
+ }
+ if (mViewportMaximumScale > 0) {
+ mViewportInitialScale = Math.min(mViewportInitialScale,
+ mViewportMaximumScale);
+ }
+ }
+
if (mSettings.forceUserScalable()) {
mViewportUserScalable = true;
if (mViewportInitialScale > 0) {
@@ -2321,6 +2336,9 @@ public final class WebViewCore {
adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi
/ mViewportDensityDpi;
}
+ if (adjust != mWebView.getDefaultZoomScale()) {
+ mWebView.updateDefaultZoomDensity(adjust);
+ }
int defaultScale = (int) (adjust * 100);
if (mViewportInitialScale > 0) {
@@ -2368,6 +2386,7 @@ public final class WebViewCore {
viewState.mMobileSite = false;
// for non-mobile site, we don't need minPrefWidth, set it as 0
viewState.mScrollX = 0;
+ viewState.mShouldStartScrolledRight = false;
Message.obtain(mWebView.mPrivateHandler,
WebView.UPDATE_ZOOM_RANGE, viewState).sendToTarget();
return;
@@ -2398,8 +2417,13 @@ public final class WebViewCore {
mInitialViewState.mDefaultScale = adjust;
mInitialViewState.mScrollX = mRestoredX;
mInitialViewState.mScrollY = mRestoredY;
+ mInitialViewState.mShouldStartScrolledRight = (mRestoredX == 0)
+ && (mRestoredY == 0)
+ && (mBrowserFrame != null)
+ && mBrowserFrame.getShouldStartScrolledRight();
+
mInitialViewState.mMobileSite = (0 == mViewportWidth);
- if (mRestoredScale > 0) {
+ if (mIsRestored) {
mInitialViewState.mIsRestored = true;
mInitialViewState.mViewScale = mRestoredScale;
if (mRestoredTextWrapScale > 0) {
@@ -2525,13 +2549,10 @@ public final class WebViewCore {
// called by JNI
private void restoreScale(float scale, float textWrapScale) {
if (mBrowserFrame.firstLayoutDone() == false) {
- // If restored scale and textWrapScale are 0, set them to
- // overview and reading level scale respectively.
- mRestoredScale = (scale <= 0.0)
- ? mWebView.getZoomOverviewScale() : scale;
+ mIsRestored = true;
+ mRestoredScale = scale;
if (mSettings.getUseWideViewPort()) {
- mRestoredTextWrapScale = (textWrapScale <= 0.0)
- ? mWebView.getReadingLevelScale() : textWrapScale;
+ mRestoredTextWrapScale = textWrapScale;
}
}
}
diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java
index e1392ae..695c154 100644
--- a/core/java/android/webkit/WebViewDatabase.java
+++ b/core/java/android/webkit/WebViewDatabase.java
@@ -42,7 +42,7 @@ public class WebViewDatabase {
// log tag
protected static final String LOGTAG = "webviewdatabase";
- private static final int DATABASE_VERSION = 10;
+ private static final int DATABASE_VERSION = 11;
// 2 -> 3 Modified Cache table to allow cache of redirects
// 3 -> 4 Added Oma-Downloads table
// 4 -> 5 Modified Cache table to support persistent contentLength
@@ -52,6 +52,9 @@ public class WebViewDatabase {
// 7 -> 8 Move cache to its own db
// 8 -> 9 Store both scheme and host when storing passwords
// 9 -> 10 Update httpauth table UNIQUE
+ // 10 -> 11 Drop cookies and cache now managed by the chromium stack,
+ // and update the form data table to use the new format
+ // implemented for b/5265606.
private static final int CACHE_DATABASE_VERSION = 4;
// 1 -> 2 Add expires String
// 2 -> 3 Add content-disposition
@@ -204,7 +207,9 @@ public class WebViewDatabase {
}
initDatabase(context);
- if (!JniUtil.useChromiumHttpStack()) {
+ if (JniUtil.useChromiumHttpStack()) {
+ context.deleteDatabase(CACHE_DATABASE_FILE);
+ } else {
initCacheDatabase(context);
}
@@ -327,15 +332,59 @@ public class WebViewDatabase {
}
private static void upgradeDatabase() {
+ upgradeDatabaseToV10();
+ upgradeDatabaseFromV10ToV11();
+ // Add future database upgrade functions here, one version at a
+ // time.
+ mDatabase.setVersion(DATABASE_VERSION);
+ }
+
+ private static void upgradeDatabaseFromV10ToV11() {
+ int oldVersion = mDatabase.getVersion();
+
+ if (oldVersion >= 11) {
+ // Nothing to do.
+ return;
+ }
+
+ if (JniUtil.useChromiumHttpStack()) {
+ // Clear out old java stack cookies - this data is now stored in
+ // a separate database managed by the Chrome stack.
+ mDatabase.execSQL("DROP TABLE IF EXISTS " + mTableNames[TABLE_COOKIES_ID]);
+
+ // Likewise for the old cache table.
+ mDatabase.execSQL("DROP TABLE IF EXISTS cache");
+ }
+
+ // Update form autocomplete URLs to match new ICS formatting.
+ Cursor c = mDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null,
+ null, null, null, null);
+ while (c.moveToNext()) {
+ String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL)));
+ String url = c.getString(c.getColumnIndex(FORMURL_URL_COL));
+ ContentValues cv = new ContentValues(1);
+ cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url));
+ mDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?",
+ new String[] { urlId });
+ }
+ c.close();
+ }
+
+ private static void upgradeDatabaseToV10() {
int oldVersion = mDatabase.getVersion();
+
+ if (oldVersion >= 10) {
+ // Nothing to do.
+ return;
+ }
+
if (oldVersion != 0) {
Log.i(LOGTAG, "Upgrading database from version "
+ oldVersion + " to "
+ DATABASE_VERSION + ", which will destroy old data");
}
- boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION;
- boolean justAuth = 9 == oldVersion && 10 == DATABASE_VERSION;
- if (justAuth) {
+
+ if (9 == oldVersion) {
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_HTTPAUTH_ID]);
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
@@ -348,55 +397,49 @@ public class WebViewDatabase {
return;
}
- if (!justPasswords) {
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_COOKIES_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS cache");
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMURL_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_FORMDATA_ID]);
- mDatabase.execSQL("DROP TABLE IF EXISTS "
- + mTableNames[TABLE_HTTPAUTH_ID]);
- }
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_COOKIES_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS cache");
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_FORMURL_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_FORMDATA_ID]);
+ mDatabase.execSQL("DROP TABLE IF EXISTS "
+ + mTableNames[TABLE_HTTPAUTH_ID]);
mDatabase.execSQL("DROP TABLE IF EXISTS "
+ mTableNames[TABLE_PASSWORD_ID]);
- mDatabase.setVersion(DATABASE_VERSION);
-
- if (!justPasswords) {
- // cookies
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL
- + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, "
- + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL
- + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");");
- mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
- + mTableNames[TABLE_COOKIES_ID] + " (path)");
-
- // formurl
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
- + " TEXT" + ");");
-
- // formdata
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
- + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
- + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
- + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
+ // cookies
+ mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID]
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL
+ + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, "
+ + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL
+ + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");");
+ mDatabase.execSQL("CREATE INDEX cookiesIndex ON "
+ + mTableNames[TABLE_COOKIES_ID] + " (path)");
+
+ // formurl
+ mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID]
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL
+ + " TEXT" + ");");
+
+ // formdata
+ mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID]
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL
+ + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE ("
+ + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", "
+ + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);");
- // httpauth
- mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
- + " (" + ID_COL + " INTEGER PRIMARY KEY, "
- + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
- + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
- + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
- + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
- + ") ON CONFLICT REPLACE);");
- }
+ // httpauth
+ mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID]
+ + " (" + ID_COL + " INTEGER PRIMARY KEY, "
+ + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL
+ + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, "
+ + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE ("
+ + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL
+ + ") ON CONFLICT REPLACE);");
// passwords
mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID]
+ " (" + ID_COL + " INTEGER PRIMARY KEY, "
@@ -411,7 +454,7 @@ public class WebViewDatabase {
if (oldVersion != 0) {
Log.i(LOGTAG, "Upgrading cache database from version "
+ oldVersion + " to "
- + DATABASE_VERSION + ", which will destroy all old data");
+ + CACHE_DATABASE_VERSION + ", which will destroy all old data");
}
mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache");
mCacheDatabase.setVersion(CACHE_DATABASE_VERSION);
@@ -1150,7 +1193,7 @@ public class WebViewDatabase {
cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID],
ID_PROJECTION, urlSelection, new String[] { url }, null,
null, null);
- if (cursor.moveToFirst()) {
+ while (cursor.moveToNext()) {
long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL));
Cursor dataCursor = null;
try {
diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java
index 9151fdd..84d00c9 100644
--- a/core/java/android/webkit/ZoomManager.java
+++ b/core/java/android/webkit/ZoomManager.java
@@ -152,6 +152,12 @@ class ZoomManager {
private float mDisplayDensity;
/*
+ * The factor that is used to tweak the zoom scale on a double-tap,
+ * and can be changed via WebSettings. Range is from 0.75f to 1.25f.
+ */
+ private float mDoubleTapZoomFactor = 1.0f;
+
+ /*
* The scale factor that is used as the minimum increment when going from
* overview to reading level on a double tap.
*/
@@ -163,11 +169,7 @@ class ZoomManager {
/*
* The initial scale for the WebView. 0 means default. If initial scale is
- * greater than 0 the WebView starts with this value as its initial scale. The
- * value is converted from an integer percentage so it is guarenteed to have
- * no more than 2 significant digits after the decimal. This restriction
- * allows us to convert the scale back to the original percentage by simply
- * multiplying the value by 100.
+ * greater than 0, the WebView starts with this value as its initial scale.
*/
private float mInitialScale;
@@ -307,17 +309,14 @@ class ZoomManager {
}
public final float getDefaultScale() {
- return mInitialScale > 0 ? mInitialScale : mDefaultScale;
+ return mDefaultScale;
}
/**
* Returns the zoom scale used for reading text on a double-tap.
*/
public final float getReadingLevelScale() {
- WebSettings settings = mWebView.getSettings();
- final float doubleTapZoomFactor = settings != null
- ? settings.getDoubleTapZoom() / 100.f : 1.0f;
- return mDisplayDensity * doubleTapZoomFactor;
+ return mDisplayDensity * mDoubleTapZoomFactor;
}
public final float getInvDefaultScale() {
@@ -351,8 +350,6 @@ class ZoomManager {
public final void setInitialScaleInPercent(int scaleInPercent) {
mInitialScale = scaleInPercent * 0.01f;
- mActualScale = mInitialScale > 0 ? mInitialScale : mDefaultScale;
- mInvActualScale = 1 / mActualScale;
}
public final float computeScaleWithLimits(float scale) {
@@ -516,8 +513,9 @@ class ZoomManager {
return mZoomScale != 0 || mInHWAcceleratedZoom;
}
- public void updateDoubleTapZoom() {
+ public void updateDoubleTapZoom(int doubleTapZoom) {
if (mInZoomOverview) {
+ mDoubleTapZoomFactor = doubleTapZoom / 100.0f;
mTextWrapScale = getReadingLevelScale();
refreshZoomScale(true);
}
@@ -1116,10 +1114,11 @@ class ZoomManager {
float scale;
if (mInitialScale > 0) {
scale = mInitialScale;
- mTextWrapScale = scale;
- } else if (viewState.mViewScale > 0) {
- mTextWrapScale = viewState.mTextWrapScale;
- scale = viewState.mViewScale;
+ } else if (viewState.mIsRestored || viewState.mViewScale > 0) {
+ scale = (viewState.mViewScale > 0)
+ ? viewState.mViewScale : overviewScale;
+ mTextWrapScale = (viewState.mTextWrapScale > 0)
+ ? viewState.mTextWrapScale : getReadingLevelScale();
} else {
scale = overviewScale;
if (!settings.getUseWideViewPort()
@@ -1135,7 +1134,7 @@ class ZoomManager {
}
boolean reflowText = false;
if (!viewState.mIsRestored) {
- if (settings.getUseFixedViewport() && mInitialScale == 0) {
+ if (settings.getUseFixedViewport()) {
// Override the scale only in case of fixed viewport.
scale = Math.max(scale, overviewScale);
mTextWrapScale = Math.max(mTextWrapScale, overviewScale);
diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java
index 9cbe8db..e0403ff 100644
--- a/core/java/android/widget/CalendarView.java
+++ b/core/java/android/widget/CalendarView.java
@@ -16,8 +16,6 @@
package android.widget;
-import com.android.internal.R;
-
import android.annotation.Widget;
import android.app.Service;
import android.content.Context;
@@ -31,7 +29,6 @@ import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
-import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
@@ -44,6 +41,8 @@ import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView.OnScrollListener;
+import com.android.internal.R;
+
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
@@ -121,11 +120,6 @@ public class CalendarView extends FrameLayout {
private static final int SCROLL_CHANGE_DELAY = 40;
/**
- * String for formatting the month name in the title text view.
- */
- private static final String FORMAT_MONTH_NAME = "MMMM, yyyy";
-
- /**
* String for parsing dates.
*/
private static final String DATE_FORMAT = "MM/dd/yyyy";
@@ -940,11 +934,17 @@ public class CalendarView extends FrameLayout {
* @param calendar A day in the new focus month.
*/
private void setMonthDisplayed(Calendar calendar) {
- mMonthName.setText(DateFormat.format(FORMAT_MONTH_NAME, calendar));
- mMonthName.invalidate();
- mCurrentMonthDisplayed = calendar.get(Calendar.MONTH);
- mAdapter.setFocusMonth(mCurrentMonthDisplayed);
- // TODO Send Accessibility Event
+ final int newMonthDisplayed = calendar.get(Calendar.MONTH);
+ if (mCurrentMonthDisplayed != newMonthDisplayed) {
+ mCurrentMonthDisplayed = newMonthDisplayed;
+ mAdapter.setFocusMonth(mCurrentMonthDisplayed);
+ final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY
+ | DateUtils.FORMAT_SHOW_YEAR;
+ final long millis = calendar.getTimeInMillis();
+ String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags);
+ mMonthName.setText(newMonthName);
+ mMonthName.invalidate();
+ }
}
/**
diff --git a/core/java/android/widget/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java
index fd2abc2..326587e 100644
--- a/core/java/android/widget/EdgeEffect.java
+++ b/core/java/android/widget/EdgeEffect.java
@@ -129,7 +129,7 @@ public class EdgeEffect {
mEdge = res.getDrawable(R.drawable.overscroll_edge);
mGlow = res.getDrawable(R.drawable.overscroll_glow);
- mMinWidth = (int) (context.getResources().getDisplayMetrics().density * MIN_WIDTH + 0.5f);
+ mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f);
mInterpolator = new DecelerateInterpolator();
}
diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java
index fb9047b..7cf5168 100644
--- a/core/java/android/widget/GridLayout.java
+++ b/core/java/android/widget/GridLayout.java
@@ -867,16 +867,18 @@ public class GridLayout extends ViewGroup {
if (firstPass) {
measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height);
} else {
- Spec spec = (orientation == HORIZONTAL) ? lp.columnSpec : lp.rowSpec;
+ boolean horizontal = (orientation == HORIZONTAL);
+ Spec spec = horizontal ? lp.columnSpec : lp.rowSpec;
if (spec.alignment == FILL) {
Interval span = spec.span;
- Axis axis = (orientation == HORIZONTAL) ? horizontalAxis : verticalAxis;
+ Axis axis = horizontal ? horizontalAxis : verticalAxis;
int[] locations = axis.getLocations();
- int size = locations[span.max] - locations[span.min];
- if (orientation == HORIZONTAL) {
- measureChildWithMargins2(c, widthSpec, heightSpec, size, lp.height);
+ int cellSize = locations[span.max] - locations[span.min];
+ int viewSize = cellSize - getTotalMargin(c, horizontal);
+ if (horizontal) {
+ measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height);
} else {
- measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, size);
+ measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize);
}
}
}
diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java
index 57701ae..1683d20 100644
--- a/core/java/android/widget/HorizontalScrollView.java
+++ b/core/java/android/widget/HorizontalScrollView.java
@@ -697,6 +697,11 @@ public class HorizontalScrollView extends FrameLayout {
}
@Override
+ public boolean shouldDelayChildPressedState() {
+ return true;
+ }
+
+ @Override
protected void onOverScrolled(int scrollX, int scrollY,
boolean clampedX, boolean clampedY) {
// Treat animating scrolls differently; see #computeScroll() for why.
diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java
index b24dd69..73e1273 100644
--- a/core/java/android/widget/ImageView.java
+++ b/core/java/android/widget/ImageView.java
@@ -1035,4 +1035,29 @@ public class ImageView extends View {
mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8);
}
}
+
+ @RemotableViewMethod
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+ if (mDrawable != null) {
+ mDrawable.setVisible(visibility == VISIBLE, false);
+ }
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ if (mDrawable != null) {
+ mDrawable.setVisible(getVisibility() == VISIBLE, false);
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ if (mDrawable != null) {
+ mDrawable.setVisible(false, false);
+ }
+ }
}
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 9ef1aa1..7f7a3a7 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3361,6 +3361,7 @@ public class ListView extends AbsListView {
final ListAdapter adapter = mAdapter;
int closetChildIndex = -1;
+ int closestChildTop = 0;
if (adapter != null && gainFocus && previouslyFocusedRect != null) {
previouslyFocusedRect.offset(mScrollX, mScrollY);
@@ -3392,12 +3393,13 @@ public class ListView extends AbsListView {
if (distance < minDistance) {
minDistance = distance;
closetChildIndex = i;
+ closestChildTop = other.getTop();
}
}
}
if (closetChildIndex >= 0) {
- setSelection(closetChildIndex + mFirstPosition);
+ setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop);
} else {
requestLayout();
}
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index cf015c4..1a1b8d0 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -203,6 +203,31 @@ public class NumberPicker extends LinearLayout {
private final EditText mInputText;
/**
+ * The min height of this widget.
+ */
+ private final int mMinHeight;
+
+ /**
+ * The max height of this widget.
+ */
+ private final int mMaxHeight;
+
+ /**
+ * The max width of this widget.
+ */
+ private final int mMinWidth;
+
+ /**
+ * The max width of this widget.
+ */
+ private int mMaxWidth;
+
+ /**
+ * Flag whether to compute the max width.
+ */
+ private final boolean mComputeMaxWidth;
+
+ /**
* The height of the text.
*/
private final int mTextSize;
@@ -517,6 +542,19 @@ public class NumberPicker extends LinearLayout {
getResources().getDisplayMetrics());
mSelectionDividerHeight = attributesArray.getDimensionPixelSize(
R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight);
+ mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight, 0);
+ mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight,
+ Integer.MAX_VALUE);
+ if (mMinHeight > mMaxHeight) {
+ throw new IllegalArgumentException("minHeight > maxHeight");
+ }
+ mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth, 0);
+ mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth,
+ Integer.MAX_VALUE);
+ if (mMinWidth > mMaxWidth) {
+ throw new IllegalArgumentException("minWidth > maxWidth");
+ }
+ mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE);
attributesArray.recycle();
mShowInputControlsAnimimationDuration = getResources().getInteger(
@@ -665,7 +703,34 @@ public class NumberPicker extends LinearLayout {
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
+ final int msrdWdth = getMeasuredWidth();
+ final int msrdHght = getMeasuredHeight();
+
+ // Increment button at the top.
+ final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
+ final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2;
+ final int incrBtnTop = 0;
+ final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth;
+ final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight();
+ mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom);
+
+ // Input text centered horizontally.
+ final int inptTxtMsrdWdth = mInputText.getMeasuredWidth();
+ final int inptTxtMsrdHght = mInputText.getMeasuredHeight();
+ final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2;
+ final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2;
+ final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth;
+ final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght;
+ mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom);
+
+ // Decrement button at the top.
+ final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth();
+ final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2;
+ final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight();
+ final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth;
+ final int decrBtnBottom = msrdHght;
+ mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom);
+
if (!mScrollWheelAndFadingEdgesInitialized) {
mScrollWheelAndFadingEdgesInitialized = true;
// need to do all this when we know our size
@@ -675,6 +740,20 @@ public class NumberPicker extends LinearLayout {
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Try greedily to fit the max width and height.
+ final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth);
+ final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight);
+ super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec);
+ // Flag if we are measured with width or height less than the respective min.
+ final int desiredWidth = Math.max(mMinWidth, getMeasuredWidth());
+ final int desiredHeight = Math.max(mMinHeight, getMeasuredHeight());
+ final int widthSize = resolveSizeAndState(desiredWidth, newWidthMeasureSpec, 0);
+ final int heightSize = resolveSizeAndState(desiredHeight, newHeightMeasureSpec, 0);
+ setMeasuredDimension(widthSize, heightSize);
+ }
+
+ @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!isEnabled() || !mFlingable) {
return false;
@@ -700,17 +779,14 @@ public class NumberPicker extends LinearLayout {
hideInputControls();
return true;
}
- if (isEventInViewHitRect(event, mInputText)
- || (!mIncrementButton.isShown()
- && isEventInViewHitRect(event, mIncrementButton))
- || (!mDecrementButton.isShown()
- && isEventInViewHitRect(event, mDecrementButton))) {
- mAdjustScrollerOnUpEvent = false;
- setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
- hideInputControls();
- return true;
+ if (isEventInVisibleViewHitRect(event, mIncrementButton)
+ || isEventInVisibleViewHitRect(event, mDecrementButton)) {
+ return false;
}
- break;
+ mAdjustScrollerOnUpEvent = false;
+ setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE);
+ hideInputControls();
+ return true;
case MotionEvent.ACTION_MOVE:
float currentMoveY = event.getY();
int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY);
@@ -976,6 +1052,49 @@ public class NumberPicker extends LinearLayout {
}
/**
+ * Computes the max width if no such specified as an attribute.
+ */
+ private void tryComputeMaxWidth() {
+ if (!mComputeMaxWidth) {
+ return;
+ }
+ int maxTextWidth = 0;
+ if (mDisplayedValues == null) {
+ float maxDigitWidth = 0;
+ for (int i = 0; i <= 9; i++) {
+ final float digitWidth = mSelectorWheelPaint.measureText(String.valueOf(i));
+ if (digitWidth > maxDigitWidth) {
+ maxDigitWidth = digitWidth;
+ }
+ }
+ int numberOfDigits = 0;
+ int current = mMaxValue;
+ while (current > 0) {
+ numberOfDigits++;
+ current = current / 10;
+ }
+ maxTextWidth = (int) (numberOfDigits * maxDigitWidth);
+ } else {
+ final int valueCount = mDisplayedValues.length;
+ for (int i = 0; i < valueCount; i++) {
+ final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]);
+ if (textWidth > maxTextWidth) {
+ maxTextWidth = (int) textWidth;
+ }
+ }
+ }
+ maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight();
+ if (mMaxWidth != maxTextWidth) {
+ if (maxTextWidth > mMinWidth) {
+ mMaxWidth = maxTextWidth;
+ } else {
+ mMaxWidth = mMinWidth;
+ }
+ invalidate();
+ }
+ }
+
+ /**
* Gets whether the selector wheel wraps when reaching the min/max value.
*
* @return True if the selector wheel wraps.
@@ -1061,6 +1180,7 @@ public class NumberPicker extends LinearLayout {
setWrapSelectorWheel(wrapSelectorWheel);
initializeSelectorWheelIndices();
updateInputTextView();
+ tryComputeMaxWidth();
}
/**
@@ -1092,6 +1212,7 @@ public class NumberPicker extends LinearLayout {
setWrapSelectorWheel(wrapSelectorWheel);
initializeSelectorWheelIndices();
updateInputTextView();
+ tryComputeMaxWidth();
}
/**
@@ -1240,6 +1361,28 @@ public class NumberPicker extends LinearLayout {
}
/**
+ * Makes a measure spec that tries greedily to use the max value.
+ *
+ * @param measureSpec The measure spec.
+ * @param maxSize The max value for the size.
+ * @return A measure spec greedily imposing the max size.
+ */
+ private int makeMeasureSpec(int measureSpec, int maxSize) {
+ final int size = MeasureSpec.getSize(measureSpec);
+ final int mode = MeasureSpec.getMode(measureSpec);
+ switch (mode) {
+ case MeasureSpec.EXACTLY:
+ return measureSpec;
+ case MeasureSpec.AT_MOST:
+ return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY);
+ case MeasureSpec.UNSPECIFIED:
+ return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY);
+ default:
+ throw new IllegalArgumentException("Unknown measure mode: " + mode);
+ }
+ }
+
+ /**
* Resets the selector indices and clear the cached
* string representation of these indices.
*/
@@ -1335,11 +1478,14 @@ public class NumberPicker extends LinearLayout {
}
/**
- * @return If the <code>event</code> is in the <code>view</code>.
+ * @return If the <code>event</code> is in the visible <code>view</code>.
*/
- private boolean isEventInViewHitRect(MotionEvent event, View view) {
- view.getHitRect(mTempRect);
- return mTempRect.contains((int) event.getX(), (int) event.getY());
+ private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) {
+ if (view.getVisibility() == VISIBLE) {
+ view.getHitRect(mTempRect);
+ return mTempRect.contains((int) event.getX(), (int) event.getY());
+ }
+ return false;
}
/**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 9cf2718..1592061 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1546,6 +1546,16 @@ public class RemoteViews implements Parcelable, Filter {
}
/**
+ * Equivalent to calling View.setContentDescription
+ *
+ * @param viewId The id of the view whose content description should change
+ * @param contentDescription The new content description for the view
+ */
+ public void setContentDescription(int viewId, CharSequence contentDescription) {
+ setCharSequence(viewId, "setContentDescription", contentDescription);
+ }
+
+ /**
* Inflates the view hierarchy represented by this object and applies
* all of the actions.
*
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 6df80e4..f524ef0 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -151,6 +151,14 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
};
+ private Runnable mReleaseCursorRunnable = new Runnable() {
+ public void run() {
+ if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
+ mSuggestionsAdapter.changeCursor(null);
+ }
+ }
+ };
+
// For voice searching
private final Intent mVoiceWebSearchIntent;
private final Intent mVoiceAppSearchIntent;
@@ -720,7 +728,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
private void updateSubmitButton(boolean hasText) {
int visibility = GONE;
- if (isSubmitAreaEnabled() && hasFocus() && (hasText || !mVoiceButtonEnabled)) {
+ if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus()
+ && (hasText || !mVoiceButtonEnabled)) {
visibility = VISIBLE;
}
mSubmitButton.setVisibility(visibility);
@@ -759,6 +768,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
@Override
protected void onDetachedFromWindow() {
removeCallbacks(mUpdateDrawableStateRunnable);
+ post(mReleaseCursorRunnable);
super.onDetachedFromWindow();
}
@@ -1028,7 +1038,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
}
}
mQueryTextView.setInputType(inputType);
-
+ if (mSuggestionsAdapter != null) {
+ mSuggestionsAdapter.changeCursor(null);
+ }
// attach the suggestions adapter, if suggestions are available
// The existence of a suggestions authority is the proxy for "suggestions available here"
if (mSearchable.getSuggestAuthority() != null) {
@@ -1071,9 +1083,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
CharSequence text = mQueryTextView.getText();
mUserQuery = text;
boolean hasText = !TextUtils.isEmpty(text);
- if (isSubmitButtonEnabled()) {
- updateSubmitButton(hasText);
- }
+ updateSubmitButton(hasText);
updateVoiceButton(!hasText);
updateCloseButton();
updateSubmitArea();
@@ -1177,7 +1187,6 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
public void onActionViewCollapsed() {
clearFocus();
updateViewsVisibility(true);
- mQueryTextView.setText("");
mQueryTextView.setImeOptions(mCollapsedImeOptions);
mExpandedInActionView = false;
}
@@ -1187,9 +1196,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView {
*/
@Override
public void onActionViewExpanded() {
+ if (mExpandedInActionView) return;
+
mExpandedInActionView = true;
mCollapsedImeOptions = mQueryTextView.getImeOptions();
mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
+ mQueryTextView.setText("");
setIconified(false);
}
diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java
index 510e2d4..ebb2604 100644
--- a/core/java/android/widget/SpellChecker.java
+++ b/core/java/android/widget/SpellChecker.java
@@ -32,6 +32,7 @@ import android.view.textservice.TextServicesManager;
import com.android.internal.util.ArrayUtils;
import java.text.BreakIterator;
+import java.util.Locale;
/**
@@ -45,7 +46,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
private final TextView mTextView;
- final SpellCheckerSession mSpellCheckerSession;
+ SpellCheckerSession mSpellCheckerSession;
final int mCookie;
// Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated
@@ -61,23 +62,55 @@ public class SpellChecker implements SpellCheckerSessionListener {
private int mSpanSequenceCounter = 0;
+ private Locale mCurrentLocale;
+
+ // Shared by all SpellParsers. Cannot be shared with TextView since it may be used
+ // concurrently due to the asynchronous nature of onGetSuggestions.
+ private WordIterator mWordIterator;
+
public SpellChecker(TextView textView) {
mTextView = textView;
- final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext().
- getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
- mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
- null /* not currently used by the textServicesManager */,
- null /* null locale means use the languages defined in Settings
- if referToSpellCheckerLanguageSettings is true */,
- this, true /* means use the languages defined in Settings */);
- mCookie = hashCode();
-
- // Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand
+ // Arbitrary: these arrays will automatically double their sizes on demand
final int size = ArrayUtils.idealObjectArraySize(1);
mIds = new int[size];
mSpellCheckSpans = new SpellCheckSpan[size];
+
+ setLocale(mTextView.getTextServicesLocale());
+
+ mCookie = hashCode();
+ }
+
+ private void setLocale(Locale locale) {
+ closeSession();
+
+ final TextServicesManager textServicesManager = (TextServicesManager)
+ mTextView.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+ if (!textServicesManager.isSpellCheckerEnabled()) {
+ mSpellCheckerSession = null;
+ } else {
+ mSpellCheckerSession = textServicesManager.newSpellCheckerSession(
+ null /* Bundle not currently used by the textServicesManager */,
+ locale, this,
+ false /* means any available languages from current spell checker */);
+ }
+ mCurrentLocale = locale;
+
+ // Restore SpellCheckSpans in pool
+ for (int i = 0; i < mLength; i++) {
+ mSpellCheckSpans[i].setSpellCheckInProgress(false);
+ mIds[i] = -1;
+ }
mLength = 0;
+
+ // Change SpellParsers' wordIterator locale
+ mWordIterator = new WordIterator(locale);
+
+ // Remove existing misspelled SuggestionSpans
+ mTextView.removeMisspelledSpans((Editable) mTextView.getText());
+
+ // This class is the listener for locale change: warn other locale-aware objects
+ mTextView.onLocaleChanged();
}
/**
@@ -95,7 +128,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
final int length = mSpellParsers.length;
for (int i = 0; i < length; i++) {
- mSpellParsers[i].close();
+ mSpellParsers[i].finish();
}
}
@@ -140,12 +173,20 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
public void spellCheck(int start, int end) {
+ final Locale locale = mTextView.getTextServicesLocale();
+ if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) {
+ setLocale(locale);
+ // Re-check the entire text
+ start = 0;
+ end = mTextView.getText().length();
+ }
+
if (!isSessionActive()) return;
final int length = mSpellParsers.length;
for (int i = 0; i < length; i++) {
final SpellParser spellParser = mSpellParsers[i];
- if (spellParser.isDone()) {
+ if (spellParser.isFinished()) {
spellParser.init(start, end);
spellParser.parse();
return;
@@ -229,7 +270,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
final int length = mSpellParsers.length;
for (int i = 0; i < length; i++) {
final SpellParser spellParser = mSpellParsers[i];
- if (!spellParser.isDone()) {
+ if (!spellParser.isFinished()) {
spellParser.parse();
}
}
@@ -239,6 +280,7 @@ public class SpellChecker implements SpellCheckerSessionListener {
SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) {
final int start = editable.getSpanStart(spellCheckSpan);
final int end = editable.getSpanEnd(spellCheckSpan);
+ if (start < 0 || end < 0) return; // span was removed in the meantime
// Other suggestion spans may exist on that region, with identical suggestions, filter
// them out to avoid duplicates. First, filter suggestion spans on that exact region.
@@ -249,7 +291,6 @@ public class SpellChecker implements SpellCheckerSessionListener {
final int spanEnd = editable.getSpanEnd(suggestionSpans[i]);
if (spanStart != start || spanEnd != end) {
suggestionSpans[i] = null;
- break;
}
}
@@ -301,7 +342,6 @@ public class SpellChecker implements SpellCheckerSessionListener {
}
private class SpellParser {
- private WordIterator mWordIterator = new WordIterator(/*TODO Locale*/);
private Object mRange = new Object();
public void init(int start, int end) {
@@ -309,11 +349,11 @@ public class SpellChecker implements SpellCheckerSessionListener {
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
- public void close() {
+ public void finish() {
((Editable) mTextView.getText()).removeSpan(mRange);
}
- public boolean isDone() {
+ public boolean isFinished() {
return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0;
}
diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java
index 27d44bf..ec3790e 100644
--- a/core/java/android/widget/Spinner.java
+++ b/core/java/android/widget/Spinner.java
@@ -68,6 +68,7 @@ public class Spinner extends AbsSpinner implements OnClickListener {
int mDropDownWidth;
private int mGravity;
+ private boolean mDisableChildrenWhenDisabled;
private Rect mTempRect = new Rect();
@@ -186,6 +187,9 @@ public class Spinner extends AbsSpinner implements OnClickListener {
mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt));
+ mDisableChildrenWhenDisabled = a.getBoolean(
+ com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false);
+
a.recycle();
// Base constructor can call setAdapter before we initialize mPopup.
@@ -196,6 +200,17 @@ public class Spinner extends AbsSpinner implements OnClickListener {
}
}
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ if (mDisableChildrenWhenDisabled) {
+ final int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ getChildAt(i).setEnabled(enabled);
+ }
+ }
+ }
+
/**
* Describes how the selected item view is positioned. Currently only the horizontal component
* is used. The default is determined by the current theme.
@@ -398,6 +413,9 @@ public class Spinner extends AbsSpinner implements OnClickListener {
addViewInLayout(child, 0, lp);
child.setSelected(hasFocus());
+ if (mDisableChildrenWhenDisabled) {
+ child.setEnabled(isEnabled());
+ }
// Get measure specs
int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec,
diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java
index 9e32c9a..c44d431 100644
--- a/core/java/android/widget/SuggestionsAdapter.java
+++ b/core/java/android/widget/SuggestionsAdapter.java
@@ -29,9 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.database.Cursor;
-import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.text.Spannable;
@@ -39,7 +37,6 @@ import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.TextAppearanceSpan;
import android.util.Log;
-import android.util.SparseArray;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
@@ -113,7 +110,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
mOutsideDrawablesCache = outsideDrawablesCache;
-
// mStartSpinnerRunnable = new Runnable() {
// public void run() {
// // mSearchView.setWorking(true); // TODO:
@@ -185,6 +181,10 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene
* the results.
*/
Cursor cursor = null;
+ if (mSearchView.getVisibility() != View.VISIBLE
+ || mSearchView.getWindowVisibility() != View.VISIBLE) {
+ return null;
+ }
//mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO:
try {
cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 0a2365e..185cfa9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -24,6 +24,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
+import android.content.res.CompatibilityInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -36,7 +37,6 @@ import android.graphics.RectF;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.inputmethodservice.ExtractEditText;
-import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@@ -101,7 +101,6 @@ import android.util.Log;
import android.util.TypedValue;
import android.view.ActionMode;
import android.view.ActionMode.Callback;
-import android.view.ContextMenu;
import android.view.DragEvent;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
@@ -132,6 +131,8 @@ import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
+import android.view.textservice.SpellCheckerSubtype;
+import android.view.textservice.TextServicesManager;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.RemoteViews.RemoteView;
@@ -147,6 +148,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.Locale;
/**
* Displays text to the user and optionally allows them to edit it. A TextView
@@ -269,7 +271,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private static final int SIGNED = 2;
private static final int DECIMAL = 4;
- class Drawables {
+ static class Drawables {
final Rect mCompoundRect = new Rect();
Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight,
mDrawableStart, mDrawableEnd;
@@ -302,7 +304,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private int mMarqueeRepeatLimit = 3;
- class InputContentType {
+ static class InputContentType {
int imeOptions = EditorInfo.IME_NULL;
String privateImeOptions;
CharSequence imeActionLabel;
@@ -313,7 +315,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
InputContentType mInputContentType;
- class InputMethodState {
+ static class InputMethodState {
Rect mCursorRectInWindow = new Rect();
RectF mTmpRectF = new RectF();
float[] mTmpOffset = new float[2];
@@ -357,6 +359,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private SpellChecker mSpellChecker;
+ private boolean mSoftInputShownOnFocus = true;
+
// The alignment to pass to Layout, or null if not resolved.
private Layout.Alignment mLayoutAlignment;
@@ -445,18 +449,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
super(context, attrs, defStyle);
mText = "";
+ final Resources res = getResources();
+ final CompatibilityInfo compat = res.getCompatibilityInfo();
+
mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
- mTextPaint.density = getResources().getDisplayMetrics().density;
- mTextPaint.setCompatibilityScaling(
- getResources().getCompatibilityInfo().applicationScale);
+ mTextPaint.density = res.getDisplayMetrics().density;
+ mTextPaint.setCompatibilityScaling(compat.applicationScale);
// If we get the paint from the skin, we should set it to left, since
// the layout always wants it to be left.
// mTextPaint.setTextAlign(Paint.Align.LEFT);
mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
- mHighlightPaint.setCompatibilityScaling(
- getResources().getCompatibilityInfo().applicationScale);
+ mHighlightPaint.setCompatibilityScaling(compat.applicationScale);
mMovement = getDefaultMovementMethod();
mTransformation = null;
@@ -605,6 +610,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
mLinksClickable = a.getBoolean(attr, true);
break;
+// TODO uncomment when this attribute is made public in the next release
+// also add TextView_showSoftInputOnFocus to the list of attributes above
+// case com.android.internal.R.styleable.TextView_showSoftInputOnFocus:
+// setShowSoftInputOnFocus(a.getBoolean(attr, true));
+// break;
+
case com.android.internal.R.styleable.TextView_drawableLeft:
drawableLeft = a.getDrawable(attr);
break;
@@ -2368,6 +2379,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
/**
+ * Sets whether the soft input method will be made visible when this
+ * TextView gets focused. The default is true.
+ *
+ * @attr ref android.R.styleable#TextView_softInputShownOnFocus
+ * @hide
+ */
+ @android.view.RemotableViewMethod
+ public final void setSoftInputShownOnFocus(boolean show) {
+ mSoftInputShownOnFocus = show;
+ }
+
+ /**
+ * Returns whether the soft input method will be made visible when this
+ * TextView gets focused. The default is true.
+ *
+ * @attr ref android.R.styleable#TextView_softInputShownOnFocus
+ * @hide
+ */
+ public final boolean getSoftInputShownOnFocus() {
+ return mSoftInputShownOnFocus;
+ }
+
+ /**
* Returns the list of URLSpans attached to the text
* (by {@link Linkify} or otherwise) if any. You can call
* {@link URLSpan#getURL} on them to find where they link to
@@ -2942,15 +2976,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sp.removeSpan(cw);
}
- SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class);
- for (int i = 0; i < suggestionSpans.length; i++) {
- int flags = suggestionSpans[i].getFlags();
- if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
- && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
- sp.removeSpan(suggestionSpans[i]);
- }
- }
-
+ removeMisspelledSpans(sp);
sp.removeSpan(mSuggestionRangeSpan);
ss.text = sp;
@@ -2970,6 +2996,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return superState;
}
+ void removeMisspelledSpans(Spannable spannable) {
+ SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(),
+ SuggestionSpan.class);
+ for (int i = 0; i < suggestionSpans.length; i++) {
+ int flags = suggestionSpans[i].getFlags();
+ if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0
+ && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) {
+ spannable.removeSpan(suggestionSpans[i]);
+ }
+ }
+ }
+
@Override
public void onRestoreInstanceState(Parcelable state) {
if (!(state instanceof SavedState)) {
@@ -3747,7 +3785,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return;
}
}
-
+
// This is the handling for some default action.
// Note that for backwards compatibility we don't do this
// default handling if explicit ime options have not been given,
@@ -5303,7 +5341,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
- mEnterKeyIsDown = true;
if (event.hasNoModifiers()) {
// When mInputContentType is set, we know that we are
// running in a "modern" cupcake environment, so don't need
@@ -5326,7 +5363,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// don't let it be inserted into the text.
if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0
|| shouldAdvanceFocusOnEnter()) {
- if (mOnClickListener != null) {
+ if (hasOnClickListeners()) {
return 0;
}
return -1;
@@ -5335,7 +5372,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
break;
case KeyEvent.KEYCODE_DPAD_CENTER:
- mDPadCenterIsDown = true;
if (event.hasNoModifiers()) {
if (shouldAdvanceFocusOnEnter()) {
return 0;
@@ -5449,7 +5485,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_CENTER:
- mDPadCenterIsDown = false;
if (event.hasNoModifiers()) {
/*
* If there is a click listener, just call through to
@@ -5460,12 +5495,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* call performClick(), but that won't do anything in
* this case.)
*/
- if (mOnClickListener == null) {
+ if (hasOnClickListeners()) {
if (mMovement != null && mText instanceof Editable
&& mLayout != null && onCheckIsTextEditor()) {
InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
- if (imm != null) {
+ if (imm != null && mSoftInputShownOnFocus) {
imm.showSoftInput(this, 0);
}
}
@@ -5474,7 +5509,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return super.onKeyUp(keyCode, event);
case KeyEvent.KEYCODE_ENTER:
- mEnterKeyIsDown = false;
if (event.hasNoModifiers()) {
if (mInputContentType != null
&& mInputContentType.onEditorActionListener != null
@@ -5498,7 +5532,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* call performClick(), but that won't do anything in
* this case.)
*/
- if (mOnClickListener == null) {
+ if (hasOnClickListeners()) {
View v = focusSearch(FOCUS_DOWN);
if (v != null) {
@@ -5560,6 +5594,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
outAttrs.extras = mInputContentType.extras;
} else {
outAttrs.imeOptions = EditorInfo.IME_NULL;
+ // May not be defined otherwise and needed by onEditorAction
+ mInputContentType = new InputContentType();
}
if (focusSearch(FOCUS_DOWN) != null) {
outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT;
@@ -8207,6 +8243,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
hideControllers();
+ if (mSuggestionsPopupWindow != null) {
+ mSuggestionsPopupWindow.onParentLostFocus();
+ }
}
startStopMarquee(hasWindowFocus);
@@ -8304,7 +8343,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// Show the IME, except when selecting in read-only text.
final InputMethodManager imm = InputMethodManager.peekInstance();
viewClicked(imm);
- if (!mTextIsSelectable) {
+ if (!mTextIsSelectable && mSoftInputShownOnFocus) {
handled |= imm != null && imm.showSoftInput(this, 0);
}
@@ -8315,10 +8354,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
// When the cursor moves, the word that was typed may need spell check
mSpellChecker.onSelectionChanged();
}
- if (isCursorInsideEasyCorrectionSpan()) {
- showSuggestions();
- } else if (hasInsertionController()) {
- getInsertionController().show();
+ if (!extractedTextModeWillBeStarted()) {
+ if (isCursorInsideEasyCorrectionSpan()) {
+ showSuggestions();
+ } else if (hasInsertionController()) {
+ getInsertionController().show();
+ }
}
}
@@ -8840,15 +8881,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
selectionStart = ((Spanned) mText).getSpanStart(urlSpan);
selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan);
} else {
- if (mWordIterator == null) {
- mWordIterator = new WordIterator();
- }
- mWordIterator.setCharSequence(mText, minOffset, maxOffset);
+ final WordIterator wordIterator = getWordIterator();
+ wordIterator.setCharSequence(mText, minOffset, maxOffset);
- selectionStart = mWordIterator.getBeginning(minOffset);
+ selectionStart = wordIterator.getBeginning(minOffset);
if (selectionStart == BreakIterator.DONE) return false;
- selectionEnd = mWordIterator.getEnd(maxOffset);
+ selectionEnd = wordIterator.getEnd(maxOffset);
if (selectionEnd == BreakIterator.DONE) return false;
if (selectionStart == selectionEnd) {
@@ -8863,6 +8902,40 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
return selectionEnd > selectionStart;
}
+ /**
+ * This is a temporary method. Future versions may support multi-locale text.
+ *
+ * @return The locale that should be used for a word iterator and a spell checker
+ * in this TextView, based on the current spell checker settings,
+ * the current IME's locale, or the system default locale.
+ * @hide
+ */
+ public Locale getTextServicesLocale() {
+ Locale locale = Locale.getDefault();
+ final TextServicesManager textServicesManager = (TextServicesManager)
+ mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE);
+ final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true);
+ if (subtype != null) {
+ locale = new Locale(subtype.getLocale());
+ }
+ return locale;
+ }
+
+ void onLocaleChanged() {
+ // Will be re-created on demand in getWordIterator with the proper new locale
+ mWordIterator = null;
+ }
+
+ /**
+ * @hide
+ */
+ public WordIterator getWordIterator() {
+ if (mWordIterator == null) {
+ mWordIterator = new WordIterator(getTextServicesLocale());
+ }
+ return mWordIterator;
+ }
+
private long getCharRange(int offset) {
final int textLength = mText.length();
if (offset + 1 < textLength) {
@@ -8896,17 +8969,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private long getLastTouchOffsets() {
- int minOffset, maxOffset;
-
- if (mContextMenuTriggeredByKey) {
- minOffset = getSelectionStart();
- maxOffset = getSelectionEnd();
- } else {
- SelectionModifierCursorController selectionController = getSelectionController();
- minOffset = selectionController.getMinTouchOffset();
- maxOffset = selectionController.getMaxTouchOffset();
- }
-
+ SelectionModifierCursorController selectionController = getSelectionController();
+ final int minOffset = selectionController.getMinTouchOffset();
+ final int maxOffset = selectionController.getMaxTouchOffset();
return packRangeInLong(minOffset, maxOffset);
}
@@ -8984,51 +9049,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
sendAccessibilityEventUnchecked(event);
}
- @Override
- protected void onCreateContextMenu(ContextMenu menu) {
- super.onCreateContextMenu(menu);
- boolean added = false;
- mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown;
- // Problem with context menu on long press: the menu appears while the key in down and when
- // the key is released, the view does not receive the key_up event.
- // We need two layers of flags: mDPadCenterIsDown and mEnterKeyIsDown are set in key down/up
- // events. We cannot simply clear these flags in onTextContextMenuItem since
- // it may not be called (if the user/ discards the context menu with the back key).
- // We clear these flags here and mContextMenuTriggeredByKey saves that state so that it is
- // available in onTextContextMenuItem.
- mDPadCenterIsDown = mEnterKeyIsDown = false;
-
- MenuHandler handler = new MenuHandler();
-
- if (mText instanceof Spanned && hasSelectionController()) {
- long lastTouchOffset = getLastTouchOffsets();
- final int selStart = extractRangeStartFromLong(lastTouchOffset);
- final int selEnd = extractRangeEndFromLong(lastTouchOffset);
-
- URLSpan[] urls = ((Spanned) mText).getSpans(selStart, selEnd, URLSpan.class);
- if (urls.length > 0) {
- menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl).
- setOnMenuItemClickListener(handler);
-
- added = true;
- }
- }
-
- // The context menu is not empty, which will prevent the selection mode from starting.
- // Add a entry to start it in the context menu.
- // TODO Does not handle the case where a subclass does not call super.thisMethod or
- // populates the menu AFTER this call.
- if (menu.size() > 0) {
- menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode).
- setOnMenuItemClickListener(handler);
- added = true;
- }
-
- if (added) {
- menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle);
- }
- }
-
/**
* Returns whether this text view is a current input method target. The
* default implementation just checks with {@link InputMethodManager}.
@@ -9043,21 +9063,11 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private static final int ID_CUT = android.R.id.cut;
private static final int ID_COPY = android.R.id.copy;
private static final int ID_PASTE = android.R.id.paste;
- // Context menu entries
- private static final int ID_COPY_URL = android.R.id.copyUrl;
- private static final int ID_SELECTION_MODE = android.R.id.selectTextMode;
-
- private class MenuHandler implements MenuItem.OnMenuItemClickListener {
- public boolean onMenuItemClick(MenuItem item) {
- return onTextContextMenuItem(item.getItemId());
- }
- }
/**
* Called when a context menu option for the text view is selected. Currently
- * this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode},
- * {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut}
- * or {@link android.R.id#copy}.
+ * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut},
+ * {@link android.R.id#copy} or {@link android.R.id#paste}.
*
* @return true if the context menu item action was performed.
*/
@@ -9074,34 +9084,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
switch (id) {
- case ID_COPY_URL:
- URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class);
- if (urls.length >= 1) {
- ClipData clip = null;
- for (int i=0; i<urls.length; i++) {
- Uri uri = Uri.parse(urls[0].getURL());
- if (clip == null) {
- clip = ClipData.newRawUri(null, uri);
- } else {
- clip.addItem(new ClipData.Item(uri));
- }
- }
- if (clip != null) {
- setPrimaryClip(clip);
- }
- }
- stopSelectionActionMode();
- return true;
-
- case ID_SELECTION_MODE:
- if (mSelectionActionMode != null) {
- // Selection mode is already started, simply change selected part.
- selectCurrentWord();
- } else {
- startSelectionActionMode();
- }
- return true;
-
case ID_SELECT_ALL:
// This does not enter text selection mode. Text is highlighted, so that it can be
// bulk edited, like selectAllOnFocus does. Returns true even if text is empty.
@@ -9548,6 +9530,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private SuggestionInfo[] mSuggestionInfos;
private int mNumberOfSuggestions;
private boolean mCursorWasVisibleBeforeSuggestions;
+ private boolean mIsShowingUp = false;
private SuggestionAdapter mSuggestionsAdapter;
private final Comparator<SuggestionSpan> mSuggestionSpanComparator;
private final HashMap<SuggestionSpan, Integer> mSpansLengths;
@@ -9603,6 +9586,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
+ public boolean isShowingUp() {
+ return mIsShowingUp;
+ }
+
+ public void onParentLostFocus() {
+ mIsShowingUp = false;
+ }
+
private class SuggestionInfo {
int suggestionStart, suggestionEnd; // range of actual suggestion within text
SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents
@@ -9610,15 +9601,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
SpannableStringBuilder text = new SpannableStringBuilder();
TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext,
android.R.style.TextAppearance_SuggestionHighlight);
-
- void removeMisspelledFlag() {
- int suggestionSpanFlags = suggestionSpan.getFlags();
- if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
- suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
- suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
- suggestionSpan.setFlags(suggestionSpanFlags);
- }
- }
}
private class SuggestionAdapter extends BaseAdapter {
@@ -9715,6 +9697,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
updateSuggestions();
mCursorWasVisibleBeforeSuggestions = mCursorVisible;
setCursorVisible(false);
+ mIsShowingUp = true;
super.show();
}
@@ -9936,6 +9919,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan);
suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan);
suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan);
+
+ // Remove potential misspelled flags
+ int suggestionSpanFlags = suggestionSpan.getFlags();
+ if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) {
+ suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED;
+ suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT;
+ suggestionSpan.setFlags(suggestionSpanFlags);
+ }
}
final int suggestionStart = suggestionInfo.suggestionStart;
@@ -9944,8 +9935,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
suggestionStart, suggestionEnd).toString();
editable.replace(spanStart, spanEnd, suggestion);
- suggestionInfo.removeMisspelledFlag();
-
// Notify source IME of the suggestion pick. Do this before swaping texts.
if (!TextUtils.isEmpty(
suggestionInfo.suggestionSpan.getNotificationTargetClassName())) {
@@ -10108,27 +10097,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
}
- final InputMethodManager imm = InputMethodManager.peekInstance();
- boolean extractedTextModeWillBeStartedFullScreen = !(this instanceof ExtractEditText) &&
- imm != null && imm.isFullscreenMode();
+ boolean willExtract = extractedTextModeWillBeStarted();
// Do not start the action mode when extracted text will show up full screen, thus
// immediately hiding the newly created action bar, which would be visually distracting.
- if (!extractedTextModeWillBeStartedFullScreen) {
+ if (!willExtract) {
ActionMode.Callback actionModeCallback = new SelectionActionModeCallback();
mSelectionActionMode = startActionMode(actionModeCallback);
}
- final boolean selectionStarted = mSelectionActionMode != null ||
- extractedTextModeWillBeStartedFullScreen;
- if (selectionStarted && !mTextIsSelectable && imm != null) {
+ final boolean selectionStarted = mSelectionActionMode != null || willExtract;
+ if (selectionStarted && !mTextIsSelectable && mSoftInputShownOnFocus) {
// Show the IME to be able to replace text, except when selecting non editable text.
- imm.showSoftInput(this, 0, null);
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ if (imm != null) {
+ imm.showSoftInput(this, 0, null);
+ }
}
return selectionStarted;
}
+ private boolean extractedTextModeWillBeStarted() {
+ if (!(this instanceof ExtractEditText)) {
+ final InputMethodManager imm = InputMethodManager.peekInstance();
+ return imm != null && imm.isFullscreenMode();
+ }
+ return false;
+ }
+
private void stopSelectionActionMode() {
if (mSelectionActionMode != null) {
// This will hide the mSelectionModifierCursorController
@@ -11121,6 +11118,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private void hideCursorControllers() {
+ if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) {
+ // Should be done before hide insertion point controller since it triggers a show of it
+ mSuggestionsPopupWindow.hide();
+ }
hideInsertionPointCursorController();
stopSelectionActionMode();
}
@@ -11462,12 +11463,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
private boolean mSelectionControllerEnabled;
private boolean mInBatchEditControllers;
- // These are needed to desambiguate a long click. If the long click comes from ones of these, we
- // select from the current cursor position. Otherwise, select from long pressed position.
- private boolean mDPadCenterIsDown = false;
- private boolean mEnterKeyIsDown = false;
- private boolean mContextMenuTriggeredByKey = false;
-
private boolean mSelectAllOnFocus = false;
private int mGravity = Gravity.TOP | Gravity.START;
diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java
index c5fa18c..eb372ca 100644
--- a/core/java/android/widget/ZoomButton.java
+++ b/core/java/android/widget/ZoomButton.java
@@ -29,8 +29,8 @@ public class ZoomButton extends ImageButton implements OnLongClickListener {
private final Handler mHandler;
private final Runnable mRunnable = new Runnable() {
public void run() {
- if ((mOnClickListener != null) && mIsInLongpress && isEnabled()) {
- mOnClickListener.onClick(ZoomButton.this);
+ if (hasOnClickListeners() && mIsInLongpress && isEnabled()) {
+ callOnClick();
mHandler.postDelayed(this, mZoomSpeed);
}
}
diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java
index ee3f23b..41993c4 100644
--- a/core/java/com/android/internal/net/NetworkStatsFactory.java
+++ b/core/java/com/android/internal/net/NetworkStatsFactory.java
@@ -25,12 +25,14 @@ import android.net.NetworkStats;
import android.os.SystemClock;
import android.util.Slog;
+import com.android.internal.util.ProcFileReader;
import com.google.android.collect.Lists;
import com.google.android.collect.Maps;
import com.google.android.collect.Sets;
import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
@@ -107,6 +109,7 @@ public class NetworkStatsFactory {
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6);
final NetworkStats.Entry entry = new NetworkStats.Entry();
+ // TODO: transition to ProcFileReader
// TODO: read directly from proc once headers are added
final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES,
KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES,
@@ -257,71 +260,58 @@ public class NetworkStatsFactory {
final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24);
final NetworkStats.Entry entry = new NetworkStats.Entry();
- // TODO: remove knownLines check once 5087722 verified
- final HashSet<String> knownLines = Sets.newHashSet();
- // TODO: remove lastIdx check once 5270106 verified
- int lastIdx;
+ int idx = 1;
+ int lastIdx = 1;
- final ArrayList<String> keys = Lists.newArrayList();
- final ArrayList<String> values = Lists.newArrayList();
- final HashMap<String, String> parsed = Maps.newHashMap();
-
- BufferedReader reader = null;
- String line = null;
+ ProcFileReader reader = null;
try {
- reader = new BufferedReader(new FileReader(mStatsXtUid));
-
- // parse first line as header
- line = reader.readLine();
- splitLine(line, keys);
- lastIdx = 1;
-
- // parse remaining lines
- while ((line = reader.readLine()) != null) {
- splitLine(line, values);
- parseLine(keys, values, parsed);
+ // open and consume header line
+ reader = new ProcFileReader(new FileInputStream(mStatsXtUid));
+ reader.finishLine();
- if (!knownLines.add(line)) {
- throw new IllegalStateException("duplicate proc entry: " + line);
- }
-
- final int idx = getParsedInt(parsed, KEY_IDX);
+ while (reader.hasMoreData()) {
+ idx = reader.nextInt();
if (idx != lastIdx + 1) {
throw new IllegalStateException(
"inconsistent idx=" + idx + " after lastIdx=" + lastIdx);
}
lastIdx = idx;
- entry.iface = parsed.get(KEY_IFACE);
- entry.uid = getParsedInt(parsed, KEY_UID);
- entry.set = getParsedInt(parsed, KEY_COUNTER_SET);
- entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX));
- entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES);
- entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS);
- entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES);
- entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS);
+ entry.iface = reader.nextString();
+ entry.tag = kernelToTag(reader.nextString());
+ entry.uid = reader.nextInt();
+ entry.set = reader.nextInt();
+ entry.rxBytes = reader.nextLong();
+ entry.rxPackets = reader.nextLong();
+ entry.txBytes = reader.nextLong();
+ entry.txPackets = reader.nextLong();
if (limitUid == UID_ALL || limitUid == entry.uid) {
stats.addValues(entry);
}
+
+ reader.finishLine();
}
} catch (NullPointerException e) {
- throw new IllegalStateException("problem parsing line: " + line, e);
+ throw new IllegalStateException("problem parsing idx " + idx, e);
} catch (NumberFormatException e) {
- throw new IllegalStateException("problem parsing line: " + line, e);
+ throw new IllegalStateException("problem parsing idx " + idx, e);
} catch (IOException e) {
- throw new IllegalStateException("problem parsing line: " + line, e);
+ throw new IllegalStateException("problem parsing idx " + idx, e);
} finally {
IoUtils.closeQuietly(reader);
}
+
return stats;
}
+ @Deprecated
private static int getParsedInt(HashMap<String, String> parsed, String key) {
final String value = parsed.get(key);
return value != null ? Integer.parseInt(value) : 0;
}
+ @Deprecated
private static long getParsedLong(HashMap<String, String> parsed, String key) {
final String value = parsed.get(key);
return value != null ? Long.parseLong(value) : 0;
@@ -330,6 +320,7 @@ public class NetworkStatsFactory {
/**
* Split given line into {@link ArrayList}.
*/
+ @Deprecated
private static void splitLine(String line, ArrayList<String> outSplit) {
outSplit.clear();
@@ -343,6 +334,7 @@ public class NetworkStatsFactory {
* Zip the two given {@link ArrayList} as key and value pairs into
* {@link HashMap}.
*/
+ @Deprecated
private static void parseLine(
ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) {
outParsed.clear();
diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
index 5a00603..3c61968 100644
--- a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
+++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl
@@ -25,4 +25,5 @@ oneway interface ISpellCheckerSession {
void onGetSuggestionsMultiple(
in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords);
void onCancel();
+ void onClose();
}
diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java
new file mode 100644
index 0000000..72e1f0f
--- /dev/null
+++ b/core/java/com/android/internal/util/ProcFileReader.java
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2011 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.internal.util;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charsets;
+
+/**
+ * Reader that specializes in parsing {@code /proc/} files quickly. Walks
+ * through the stream using a single space {@code ' '} as token separator, and
+ * requires each line boundary to be explicitly acknowledged using
+ * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding.
+ * <p>
+ * Currently doesn't support formats based on {@code \0}, tabs, or repeated
+ * delimiters.
+ */
+public class ProcFileReader implements Closeable {
+ private final InputStream mStream;
+ private final byte[] mBuffer;
+
+ /** Write pointer in {@link #mBuffer}. */
+ private int mTail;
+ /** Flag when last read token finished current line. */
+ private boolean mLineFinished;
+
+ public ProcFileReader(InputStream stream) throws IOException {
+ this(stream, 4096);
+ }
+
+ public ProcFileReader(InputStream stream, int bufferSize) throws IOException {
+ mStream = stream;
+ mBuffer = new byte[bufferSize];
+
+ // read enough to answer hasMoreData
+ fillBuf();
+ }
+
+ /**
+ * Read more data from {@link #mStream} into internal buffer.
+ */
+ private int fillBuf() throws IOException {
+ final int length = mBuffer.length - mTail;
+ if (length == 0) {
+ throw new IOException("attempting to fill already-full buffer");
+ }
+
+ final int read = mStream.read(mBuffer, mTail, length);
+ if (read != -1) {
+ mTail += read;
+ }
+ return read;
+ }
+
+ /**
+ * Consume number of bytes from beginning of internal buffer. If consuming
+ * all remaining bytes, will attempt to {@link #fillBuf()}.
+ */
+ private void consumeBuf(int count) throws IOException {
+ // TODO: consider moving to read pointer, but for now traceview says
+ // these copies aren't a bottleneck.
+ System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count);
+ mTail -= count;
+ if (mTail == 0) {
+ fillBuf();
+ }
+ }
+
+ /**
+ * Find buffer index of next token delimiter, usually space or newline. Will
+ * fill buffer as needed.
+ */
+ private int nextTokenIndex() throws IOException {
+ if (mLineFinished) {
+ throw new IOException("no tokens remaining on current line");
+ }
+
+ int i = 0;
+ do {
+ // scan forward for token boundary
+ for (; i < mTail; i++) {
+ final byte b = mBuffer[i];
+ if (b == '\n') {
+ mLineFinished = true;
+ return i;
+ }
+ if (b == ' ') {
+ return i;
+ }
+ }
+ } while (fillBuf() > 0);
+
+ throw new IOException("end of stream while looking for token boundary");
+ }
+
+ /**
+ * Check if stream has more data to be parsed.
+ */
+ public boolean hasMoreData() {
+ return mTail > 0;
+ }
+
+ /**
+ * Finish current line, skipping any remaining data.
+ */
+ public void finishLine() throws IOException {
+ // last token already finished line; reset silently
+ if (mLineFinished) {
+ mLineFinished = false;
+ return;
+ }
+
+ int i = 0;
+ do {
+ // scan forward for line boundary and consume
+ for (; i < mTail; i++) {
+ if (mBuffer[i] == '\n') {
+ consumeBuf(i + 1);
+ return;
+ }
+ }
+ } while (fillBuf() > 0);
+
+ throw new IOException("end of stream while looking for line boundary");
+ }
+
+ /**
+ * Parse and return next token as {@link String}.
+ */
+ public String nextString() throws IOException {
+ final int tokenIndex = nextTokenIndex();
+ final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII);
+ consumeBuf(tokenIndex + 1);
+ return s;
+ }
+
+ /**
+ * Parse and return next token as base-10 encoded {@code long}.
+ */
+ public long nextLong() throws IOException {
+ final int tokenIndex = nextTokenIndex();
+ final boolean negative = mBuffer[0] == '-';
+
+ // TODO: refactor into something like IntegralToString
+ long result = 0;
+ for (int i = negative ? 1 : 0; i < tokenIndex; i++) {
+ final int digit = mBuffer[i] - '0';
+ if (digit < 0 || digit > 9) {
+ throw invalidLong(tokenIndex);
+ }
+
+ // always parse as negative number and apply sign later; this
+ // correctly handles MIN_VALUE which is "larger" than MAX_VALUE.
+ final long next = result * 10 - digit;
+ if (next > result) {
+ throw invalidLong(tokenIndex);
+ }
+ result = next;
+ }
+
+ consumeBuf(tokenIndex + 1);
+ return negative ? result : -result;
+ }
+
+ private NumberFormatException invalidLong(int tokenIndex) {
+ return new NumberFormatException(
+ "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII));
+ }
+
+ /**
+ * Parse and return next token as base-10 encoded {@code int}.
+ */
+ public int nextInt() throws IOException {
+ final long value = nextLong();
+ if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) {
+ throw new NumberFormatException("parsed value larger than integer");
+ }
+ return (int) value;
+ }
+
+ public void close() throws IOException {
+ mStream.close();
+ }
+}
diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
index e245960..a10d241 100644
--- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java
@@ -18,6 +18,7 @@ package com.android.internal.view.menu;
import android.content.Context;
import android.content.res.Resources;
+import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
@@ -46,8 +47,8 @@ public class ActionMenuItemView extends LinearLayout
private ImageButton mImageButton;
private Button mTextButton;
private boolean mAllowTextWithIcon;
- private boolean mShowTextAllCaps;
private boolean mExpandedFormat;
+ private int mMinWidth;
public ActionMenuItemView(Context context) {
this(context, null);
@@ -62,7 +63,11 @@ public class ActionMenuItemView extends LinearLayout
final Resources res = context.getResources();
mAllowTextWithIcon = res.getBoolean(
com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon);
- mShowTextAllCaps = res.getBoolean(com.android.internal.R.bool.config_actionMenuItemAllCaps);
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ com.android.internal.R.styleable.ActionMenuItemView, 0, 0);
+ mMinWidth = a.getDimensionPixelSize(
+ com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0);
+ a.recycle();
}
@Override
@@ -228,4 +233,21 @@ public class ActionMenuItemView extends LinearLayout
cheatSheet.show();
return true;
}
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
+ final int specSize = MeasureSpec.getSize(widthMeasureSpec);
+ final int oldMeasuredWidth = getMeasuredWidth();
+ final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth)
+ : mMinWidth;
+
+ if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) {
+ // Remeasure at exactly the minimum width.
+ super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY),
+ heightMeasureSpec);
+ }
+ }
}
diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
index 1e06b5a..db0d6dd 100644
--- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
+++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java
@@ -91,7 +91,14 @@ public abstract class BaseMenuPresenter implements MenuPresenter {
MenuItemImpl item = visibleItems.get(i);
if (shouldIncludeItem(childIndex, item)) {
final View convertView = parent.getChildAt(childIndex);
+ final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ?
+ ((MenuView.ItemView) convertView).getItemData() : null;
final View itemView = getItemView(item, convertView, parent);
+ if (item != oldItem) {
+ // Don't let old states linger with new data.
+ itemView.setPressed(false);
+ itemView.jumpDrawablesToCurrentState();
+ }
if (itemView != convertView) {
addItemView(itemView, childIndex);
}
diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
index 723ece4..47058ad 100644
--- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java
+++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java
@@ -63,11 +63,6 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men
setChildrenDrawingCacheEnabled(false);
}
- @Override
- protected boolean recycleOnMeasure() {
- return false;
- }
-
public boolean invokeItem(MenuItemImpl item) {
return mMenu.performItemAction(item, 0);
}
diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java
index a1e16d4..df579c6 100644
--- a/core/java/com/android/internal/view/menu/ListMenuItemView.java
+++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java
@@ -34,6 +34,7 @@ import android.widget.TextView;
* The item view for each item in the ListView-based MenuViews.
*/
public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
+ private static final String TAG = "ListMenuItemView";
private MenuItemImpl mItemData;
private ImageView mIconView;
@@ -121,27 +122,25 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
public void setCheckable(boolean checkable) {
-
if (!checkable && mRadioButton == null && mCheckBox == null) {
return;
}
- if (mRadioButton == null) {
- insertRadioButton();
- }
- if (mCheckBox == null) {
- insertCheckBox();
- }
-
// Depending on whether its exclusive check or not, the checkbox or
// radio button will be the one in use (and the other will be otherCompoundButton)
final CompoundButton compoundButton;
final CompoundButton otherCompoundButton;
if (mItemData.isExclusiveCheckable()) {
+ if (mRadioButton == null) {
+ insertRadioButton();
+ }
compoundButton = mRadioButton;
otherCompoundButton = mCheckBox;
} else {
+ if (mCheckBox == null) {
+ insertCheckBox();
+ }
compoundButton = mCheckBox;
otherCompoundButton = mRadioButton;
}
@@ -155,12 +154,12 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView
}
// Make sure the other compound button isn't visible
- if (otherCompoundButton.getVisibility() != GONE) {
+ if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) {
otherCompoundButton.setVisibility(GONE);
}
} else {
- mCheckBox.setVisibility(GONE);
- mRadioButton.setVisibility(GONE);
+ if (mCheckBox != null) mCheckBox.setVisibility(GONE);
+ if (mRadioButton != null) mRadioButton.setVisibility(GONE);
}
}
diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java
index e131242..b689f53 100644
--- a/core/java/com/android/internal/widget/ActionBarView.java
+++ b/core/java/com/android/internal/widget/ActionBarView.java
@@ -518,6 +518,7 @@ public class ActionBarView extends AbsActionBarView {
public void setHomeButtonEnabled(boolean enable) {
mHomeLayout.setEnabled(enable);
+ mHomeLayout.setFocusable(enable);
// Make sure the home button has an accurate content description for accessibility.
if (!enable) {
mHomeLayout.setContentDescription(null);
@@ -536,7 +537,7 @@ public class ActionBarView extends AbsActionBarView {
if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) {
final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0;
- final int vis = showHome ? VISIBLE : GONE;
+ final int vis = showHome && mExpandedActionView == null ? VISIBLE : GONE;
mHomeLayout.setVisibility(vis);
if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java
index 18a4794..daefc9a 100644
--- a/core/java/com/android/internal/widget/DigitalClock.java
+++ b/core/java/com/android/internal/widget/DigitalClock.java
@@ -106,7 +106,8 @@ public class DigitalClock extends RelativeLayout {
private String mAmString, mPmString;
AmPm(View parent, Typeface tf) {
- mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm);
+ // No longer used, uncomment if we decide to use AM/PM indicator again
+ // mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm);
if (mAmPmTextView != null && tf != null) {
mAmPmTextView.setTypeface(tf);
}
@@ -168,6 +169,8 @@ public class DigitalClock extends RelativeLayout {
/* The time display consists of two tones. That's why we have two overlapping text views. */
mTimeDisplayBackground = (TextView) findViewById(R.id.timeDisplayBackground);
mTimeDisplayBackground.setTypeface(sBackgroundFont);
+ mTimeDisplayBackground.setVisibility(View.INVISIBLE);
+
mTimeDisplayForeground = (TextView) findViewById(R.id.timeDisplayForeground);
mTimeDisplayForeground.setTypeface(sForegroundFont);
mAmPm = new AmPm(this, null);
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 17b8acf..89f9d4e 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -25,14 +25,11 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
-import android.hardware.Camera;
-import android.hardware.Camera.CameraInfo;
import android.os.FileObserver;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
-import android.os.SystemProperties;
import android.os.storage.IMountService;
import android.provider.Settings;
import android.security.KeyStore;
@@ -41,7 +38,6 @@ import android.text.TextUtils;
import android.util.Log;
import android.view.View;
import android.widget.Button;
-import android.widget.TextView;
import java.io.File;
import java.io.FileNotFoundException;
@@ -968,6 +964,11 @@ public class LockPatternUtils {
com.android.internal.R.bool.config_enable_puk_unlock_screen);
}
+ public boolean isEmergencyCallEnabledWhileSimLocked() {
+ return mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked);
+ }
+
/**
* @return A formatted string of the next alarm (for showing on the lock screen),
* or null if there is no next alarm.
@@ -1031,12 +1032,10 @@ public class LockPatternUtils {
* {@link TelephonyManager#CALL_STATE_IDLE}
* {@link TelephonyManager#CALL_STATE_RINGING}
* {@link TelephonyManager#CALL_STATE_OFFHOOK}
- * @param showIfCapable indicates whether the button should be shown if emergency calls are
- * possible on the device
+ * @param shown indicates whether the given screen wants the emergency button to show at all
*/
- public void updateEmergencyCallButtonState(Button button, int phoneState,
- boolean showIfCapable) {
- if (isEmergencyCallCapable() && showIfCapable) {
+ public void updateEmergencyCallButtonState(Button button, int phoneState, boolean shown) {
+ if (isEmergencyCallCapable() && shown) {
button.setVisibility(View.VISIBLE);
} else {
button.setVisibility(View.GONE);
diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java
index a2fc6e2..0d9cf9a 100644
--- a/core/java/com/android/internal/widget/LockPatternView.java
+++ b/core/java/com/android/internal/widget/LockPatternView.java
@@ -463,7 +463,7 @@ public class LockPatternView extends View {
result = desired;
break;
case MeasureSpec.AT_MOST:
- result = Math.min(specSize, desired);
+ result = Math.max(specSize, desired);
break;
case MeasureSpec.EXACTLY:
default:
diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java
index 288865f..2d89234 100644
--- a/core/java/com/android/internal/widget/WaveView.java
+++ b/core/java/com/android/internal/widget/WaveView.java
@@ -26,10 +26,13 @@ import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.os.Vibrator;
+import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.R;
@@ -64,6 +67,18 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen
private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking
private static final long WAVE_DELAY = WAVE_DURATION / WAVE_COUNT; // initial propagation delay
+ /**
+ * The scale by which to multiply the unlock handle width to compute the radius
+ * in which it can be grabbed when accessibility is disabled.
+ */
+ private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED = 0.5f;
+
+ /**
+ * The scale by which to multiply the unlock handle width to compute the radius
+ * in which it can be grabbed when accessibility is enabled (more generous).
+ */
+ private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.0f;
+
private Vibrator mVibrator;
private OnTriggerListener mOnTriggerListener;
private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3);
@@ -451,6 +466,27 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen
};
@Override
+ public boolean onHoverEvent(MotionEvent event) {
+ if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) {
+ final int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_HOVER_ENTER:
+ event.setAction(MotionEvent.ACTION_DOWN);
+ break;
+ case MotionEvent.ACTION_HOVER_MOVE:
+ event.setAction(MotionEvent.ACTION_MOVE);
+ break;
+ case MotionEvent.ACTION_HOVER_EXIT:
+ event.setAction(MotionEvent.ACTION_UP);
+ break;
+ }
+ onTouchEvent(event);
+ event.setAction(action);
+ }
+ return super.onHoverEvent(event);
+ }
+
+ @Override
public boolean onTouchEvent(MotionEvent event) {
final int action = event.getAction();
mMouseX = event.getX();
@@ -460,21 +496,12 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen
case MotionEvent.ACTION_DOWN:
removeCallbacks(mLockTimerActions);
mFingerDown = true;
- setGrabbedState(OnTriggerListener.CENTER_HANDLE);
- {
- float x = mMouseX - mUnlockHalo.getX();
- float y = mMouseY - mUnlockHalo.getY();
- float dist = (float) Math.hypot(x, y);
- if (dist < mUnlockHalo.getWidth()*0.5f) {
- if (mLockState == STATE_READY) {
- mLockState = STATE_START_ATTEMPT;
- }
- }
- }
+ tryTransitionToStartAttemptState(event);
handled = true;
break;
case MotionEvent.ACTION_MOVE:
+ tryTransitionToStartAttemptState(event);
handled = true;
break;
@@ -502,6 +529,47 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen
}
/**
+ * Tries to transition to start attempt state.
+ *
+ * @param event A motion event.
+ */
+ private void tryTransitionToStartAttemptState(MotionEvent event) {
+ final float dx = event.getX() - mUnlockHalo.getX();
+ final float dy = event.getY() - mUnlockHalo.getY();
+ float dist = (float) Math.hypot(dx, dy);
+ if (dist <= getScaledGrabHandleRadius()) {
+ setGrabbedState(OnTriggerListener.CENTER_HANDLE);
+ if (mLockState == STATE_READY) {
+ mLockState = STATE_START_ATTEMPT;
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ announceUnlockHandle();
+ }
+ }
+ }
+ }
+
+ /**
+ * @return The radius in which the handle is grabbed scaled based on
+ * whether accessibility is enabled.
+ */
+ private float getScaledGrabHandleRadius() {
+ if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+ return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth();
+ } else {
+ return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth();
+ }
+ }
+
+ /**
+ * Announces the unlock handle if accessibility is enabled.
+ */
+ private void announceUnlockHandle() {
+ setContentDescription(mContext.getString(R.string.description_target_unlock_tablet));
+ sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+ setContentDescription(null);
+ }
+
+ /**
* Triggers haptic feedback.
*/
private synchronized void vibrate(long duration) {