diff options
Diffstat (limited to 'core/java')
26 files changed, 885 insertions, 545 deletions
diff --git a/core/java/android/app/ActionBar.java b/core/java/android/app/ActionBar.java index 38086f0..29f2e30 100644 --- a/core/java/android/app/ActionBar.java +++ b/core/java/android/app/ActionBar.java @@ -123,72 +123,6 @@ public abstract class ActionBar { public abstract int getSelectedNavigationItem(); /** - * Set the action bar into standard navigation mode, supplying a title and subtitle. - * - * Standard navigation mode is default. The title is automatically set to the - * name of your Activity. Subtitles are displayed underneath the title, usually - * in a smaller font or otherwise less prominently than the title. Subtitles are - * good for extended descriptions of activity state. - * - * @param title The action bar's title. null is treated as an empty string. - * @param subtitle The action bar's subtitle. null will remove the subtitle entirely. - * - * @see #setStandardNavigationMode() - * @see #setStandardNavigationMode(CharSequence) - * @see #setStandardNavigationMode(int) - * @see #setStandardNavigationMode(int, int) - */ - public abstract void setStandardNavigationMode(CharSequence title, CharSequence subtitle); - - /** - * Set the action bar into standard navigation mode, supplying a title and subtitle. - * - * Standard navigation mode is default. The title is automatically set to the - * name of your Activity. Subtitles are displayed underneath the title, usually - * in a smaller font or otherwise less prominently than the title. Subtitles are - * good for extended descriptions of activity state. - * - * @param titleResId Resource ID of a title string - * @param subtitleResId Resource ID of a subtitle string - * - * @see #setStandardNavigationMode() - * @see #setStandardNavigationMode(CharSequence) - * @see #setStandardNavigationMode(CharSequence, CharSequence) - * @see #setStandardNavigationMode(int) - */ - public abstract void setStandardNavigationMode(int titleResId, int subtitleResId); - - /** - * Set the action bar into standard navigation mode, supplying a title and subtitle. - * - * Standard navigation mode is default. The title is automatically set to the - * name of your Activity on startup if an action bar is present. - * - * @param title The action bar's title. null is treated as an empty string. - * - * @see #setStandardNavigationMode() - * @see #setStandardNavigationMode(CharSequence, CharSequence) - * @see #setStandardNavigationMode(int) - * @see #setStandardNavigationMode(int, int) - */ - public abstract void setStandardNavigationMode(CharSequence title); - - /** - * Set the action bar into standard navigation mode, supplying a title and subtitle. - * - * Standard navigation mode is default. The title is automatically set to the - * name of your Activity on startup if an action bar is present. - * - * @param titleResId Resource ID of a title string - * - * @see #setStandardNavigationMode() - * @see #setStandardNavigationMode(CharSequence) - * @see #setStandardNavigationMode(CharSequence, CharSequence) - * @see #setStandardNavigationMode(int, int) - */ - public abstract void setStandardNavigationMode(int titleResId); - - /** * Set the action bar into standard navigation mode, using the currently set title * and/or subtitle. * @@ -324,18 +258,6 @@ public abstract class ActionBar { public abstract void setTabNavigationMode(); /** - * Set the action bar into tabbed navigation mode. - * - * @param containerViewId Id of the container view where tab content fragments should appear. - * - * @see #addTab(Tab) - * @see #insertTab(Tab, int) - * @see #removeTab(Tab) - * @see #removeTabAt(int) - */ - public abstract void setTabNavigationMode(int containerViewId); - - /** * Create and return a new {@link Tab}. * This tab will not be included in the action bar until it is added. * @@ -354,13 +276,13 @@ public abstract class ActionBar { public abstract void addTab(Tab tab); /** - * Insert a tab for use in tabbed navigation mode. The tab will be inserted at + * Add a tab for use in tabbed navigation mode. The tab will be inserted at * <code>position</code>. * * @param tab The tab to add * @param position The new position of the tab */ - public abstract void insertTab(Tab tab, int position); + public abstract void addTab(Tab tab, int position); /** * Remove a tab from the action bar. @@ -384,6 +306,14 @@ public abstract class ActionBar { public abstract void selectTab(Tab tab); /** + * Returns the currently selected tab if in tabbed navigation mode and there is at least + * one tab present. + * + * @return The currently selected tab or null + */ + public abstract Tab getSelectedTab(); + + /** * Retrieve the current height of the ActionBar. * * @return The ActionBar's height @@ -477,22 +407,68 @@ public abstract class ActionBar { public abstract void setText(CharSequence text); /** - * Returns the fragment that will be shown when this tab is selected. + * Set a custom view to be used for this tab. This overrides values set by + * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}. * - * @return Fragment associated with this tab + * @param view Custom view to be used as a tab. */ - public abstract Fragment getFragment(); + public abstract void setCustomView(View view); /** - * Set the fragment that will be shown when this tab is selected. + * Retrieve a previously set custom view for this tab. * - * @param fragment Fragment to associate with this tab + * @return The custom view set by {@link #setCustomView(View)}. */ - public abstract void setFragment(Fragment fragment); + public abstract View getCustomView(); + + /** + * Give this Tab an arbitrary object to hold for later use. + * + * @param obj Object to store + */ + public abstract void setTag(Object obj); + + /** + * @return This Tab's tag object. + */ + public abstract Object getTag(); + + /** + * Set the {@link TabListener} that will handle switching to and from this tab. + * All tabs must have a TabListener set before being added to the ActionBar. + * + * @param listener Listener to handle tab selection events + */ + public abstract void setTabListener(TabListener listener); /** * Select this tab. Only valid if the tab has been added to the action bar. */ public abstract void select(); } + + /** + * Callback interface invoked when a tab is focused, unfocused, added, or removed. + */ + public interface TabListener { + /** + * Called when a tab enters the selected state. + * + * @param tab The tab that was selected + * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute + * during a tab switch. The previous tab's unselect and this tab's select will be + * executed in a single transaction. + */ + public void onTabSelected(Tab tab, FragmentTransaction ft); + + /** + * Called when a tab exits the selected state. + * + * @param tab The tab that was unselected + * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute + * during a tab switch. This tab's unselect and the newly selected tab's select + * will be executed in a single transaction. + */ + public void onTabUnselected(Tab tab, FragmentTransaction ft); + } } diff --git a/core/java/android/app/BackStackEntry.java b/core/java/android/app/BackStackEntry.java index 00c2fc4..71fd5e5 100644 --- a/core/java/android/app/BackStackEntry.java +++ b/core/java/android/app/BackStackEntry.java @@ -490,4 +490,8 @@ final class BackStackEntry implements FragmentTransaction, Runnable { public int getTransitionStyle() { return mTransitionStyle; } + + public boolean isEmpty() { + return mNumOp == 0; + } } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 6886aba..7497136 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -323,54 +323,54 @@ class ContextImpl extends Context { @Override public SharedPreferences getSharedPreferences(String name, int mode) { SharedPreferencesImpl sp; + File prefsFile; + boolean needInitialLoad = false; synchronized (sSharedPrefs) { sp = sSharedPrefs.get(name); - if (sp != null && !sp.hasFileChanged()) { - //Log.i(TAG, "Returning existing prefs " + name + ": " + sp); + if (sp != null && !sp.hasFileChangedUnexpectedly()) { return sp; } - } - File f = getSharedPrefsFile(name); - FileInputStream str = null; - File backup = makeBackupFile(f); - if (backup.exists()) { - f.delete(); - backup.renameTo(f); + prefsFile = getSharedPrefsFile(name); + if (sp == null) { + sp = new SharedPreferencesImpl(prefsFile, mode, null); + sSharedPrefs.put(name, sp); + needInitialLoad = true; + } } - // Debugging - if (f.exists() && !f.canRead()) { - Log.w(TAG, "Attempt to read preferences file " + f + " without permission"); - } + synchronized (sp) { + if (needInitialLoad && sp.isLoaded()) { + // lost the race to load; another thread handled it + return sp; + } + File backup = makeBackupFile(prefsFile); + if (backup.exists()) { + prefsFile.delete(); + backup.renameTo(prefsFile); + } - Map map = null; - if (f.exists() && f.canRead()) { - try { - str = new FileInputStream(f); - map = XmlUtils.readMapXml(str); - str.close(); - } catch (org.xmlpull.v1.XmlPullParserException e) { - Log.w(TAG, "getSharedPreferences", e); - } catch (FileNotFoundException e) { - Log.w(TAG, "getSharedPreferences", e); - } catch (IOException e) { - Log.w(TAG, "getSharedPreferences", e); + // Debugging + if (prefsFile.exists() && !prefsFile.canRead()) { + Log.w(TAG, "Attempt to read preferences file " + prefsFile + " without permission"); } - } - synchronized (sSharedPrefs) { - if (sp != null) { - //Log.i(TAG, "Updating existing prefs " + name + " " + sp + ": " + map); - sp.replace(map); - } else { - sp = sSharedPrefs.get(name); - if (sp == null) { - sp = new SharedPreferencesImpl(f, mode, map); - sSharedPrefs.put(name, sp); + Map map = null; + if (prefsFile.exists() && prefsFile.canRead()) { + try { + FileInputStream str = new FileInputStream(prefsFile); + map = XmlUtils.readMapXml(str); + str.close(); + } catch (org.xmlpull.v1.XmlPullParserException e) { + Log.w(TAG, "getSharedPreferences", e); + } catch (FileNotFoundException e) { + Log.w(TAG, "getSharedPreferences", e); + } catch (IOException e) { + Log.w(TAG, "getSharedPreferences", e); } } - return sp; + sp.replace(map); } + return sp; } private File getPreferencesDir() { @@ -2718,6 +2718,10 @@ class ContextImpl extends Context { private static final class SharedPreferencesImpl implements SharedPreferences { + // Lock ordering rules: + // - acquire SharedPreferencesImpl.this before EditorImpl.this + // - acquire mWritingToDiskLock before EditorImpl.this + private final File mFile; private final File mBackupFile; private final int mMode; @@ -2725,6 +2729,7 @@ class ContextImpl extends Context { private Map<String, Object> mMap; // guarded by 'this' private long mTimestamp; // guarded by 'this' private int mDiskWritesInFlight = 0; // guarded by 'this' + private boolean mLoaded = false; // guarded by 'this' private final Object mWritingToDiskLock = new Object(); private static final Object mContent = new Object(); @@ -2735,6 +2740,7 @@ class ContextImpl extends Context { mFile = file; mBackupFile = makeBackupFile(file); mMode = mode; + mLoaded = initialContents != null; mMap = initialContents != null ? initialContents : new HashMap<String, Object>(); FileStatus stat = new FileStatus(); if (FileUtils.getFileStatus(file.getPath(), stat)) { @@ -2743,7 +2749,23 @@ class ContextImpl extends Context { mListeners = new WeakHashMap<OnSharedPreferenceChangeListener, Object>(); } - public boolean hasFileChanged() { + // Has this SharedPreferences ever had values assigned to it? + boolean isLoaded() { + synchronized (this) { + return mLoaded; + } + } + + // Has the file changed out from under us? i.e. writes that + // we didn't instigate. + public boolean hasFileChangedUnexpectedly() { + synchronized (this) { + if (mDiskWritesInFlight > 0) { + // If we know we caused it, it's not unexpected. + Log.d(TAG, "disk write in flight, not unexpected."); + return false; + } + } FileStatus stat = new FileStatus(); if (!FileUtils.getFileStatus(mFile.getPath(), stat)) { return true; @@ -2754,8 +2776,9 @@ class ContextImpl extends Context { } public void replace(Map newContents) { - if (newContents != null) { - synchronized (this) { + synchronized (this) { + mLoaded = true; + if (newContents != null) { mMap = newContents; } } diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java index 04598a3..9d44106 100644 --- a/core/java/android/app/FragmentTransaction.java +++ b/core/java/android/app/FragmentTransaction.java @@ -85,6 +85,12 @@ public interface FragmentTransaction { * @return Returns the same FragmentTransaction instance. */ public FragmentTransaction show(Fragment fragment); + + /** + * @return <code>true</code> if this transaction contains no operations, + * <code>false</code> otherwise. + */ + public boolean isEmpty(); /** * Bit mask that is set for all enter transitions. diff --git a/core/java/android/app/NativeActivity.java b/core/java/android/app/NativeActivity.java index 4dc88b3..d7a0412 100644 --- a/core/java/android/app/NativeActivity.java +++ b/core/java/android/app/NativeActivity.java @@ -3,8 +3,6 @@ package android.app; import com.android.internal.view.IInputMethodCallback; import com.android.internal.view.IInputMethodSession; -import dalvik.system.PathClassLoader; - import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; @@ -168,17 +166,14 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback2, // If the application does not have (Java) code, then no ClassLoader // has been set up for it. We will need to do our own search for // the native code. - path = ai.applicationInfo.dataDir + "/lib/" + System.mapLibraryName(libname); - if (!(new File(path)).exists()) { - path = null; + File libraryFile = new File(ai.applicationInfo.nativeLibraryDir, + System.mapLibraryName(libname)); + if (libraryFile.exists()) { + path = libraryFile.getPath(); } } if (path == null) { - path = ((PathClassLoader)getClassLoader()).findLibrary(libname); - } - - if (path == null) { throw new IllegalArgumentException("Unable to find native library: " + libname); } diff --git a/core/java/android/app/backup/SharedPreferencesBackupHelper.java b/core/java/android/app/backup/SharedPreferencesBackupHelper.java index 23b1703..213bd31 100644 --- a/core/java/android/app/backup/SharedPreferencesBackupHelper.java +++ b/core/java/android/app/backup/SharedPreferencesBackupHelper.java @@ -16,6 +16,7 @@ package android.app.backup; +import android.app.QueuedWork; import android.content.Context; import android.content.SharedPreferences; import android.os.ParcelFileDescriptor; @@ -94,7 +95,11 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen public void performBackup(ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) { Context context = mContext; - + + // If a SharedPreference has an outstanding write in flight, + // wait for it to finish flushing to disk. + QueuedWork.waitToFinish(); + // make filenames for the prefGroups String[] prefGroups = mPrefGroups; final int N = prefGroups.length; @@ -123,4 +128,3 @@ public class SharedPreferencesBackupHelper extends FileBackupHelperBase implemen } } } - diff --git a/core/java/android/database/sqlite/SQLiteClosable.java b/core/java/android/database/sqlite/SQLiteClosable.java index 1830f6c..21d2ab0 100644 --- a/core/java/android/database/sqlite/SQLiteClosable.java +++ b/core/java/android/database/sqlite/SQLiteClosable.java @@ -30,6 +30,7 @@ public abstract class SQLiteClosable { public void acquireReference() { synchronized(mLock) { + checkRefCount(); if (mReferenceCount <= 0) { throw new IllegalStateException( "attempt to re-open an already-closed object: " + getObjInfo()); @@ -40,6 +41,7 @@ public abstract class SQLiteClosable { public void releaseReference() { synchronized(mLock) { + checkRefCount(); mReferenceCount--; if (mReferenceCount == 0) { onAllReferencesReleased(); @@ -49,6 +51,7 @@ public abstract class SQLiteClosable { public void releaseReferenceFromContainer() { synchronized(mLock) { + checkRefCount(); mReferenceCount--; if (mReferenceCount == 0) { onAllReferencesReleasedFromContainer(); @@ -63,8 +66,7 @@ public abstract class SQLiteClosable { if (this instanceof SQLiteDatabase) { buff.append("database = "); buff.append(((SQLiteDatabase)this).getPath()); - } else if (this instanceof SQLiteProgram || this instanceof SQLiteStatement || - this instanceof SQLiteQuery) { + } else if (this instanceof SQLiteProgram) { buff.append("mSql = "); buff.append(((SQLiteProgram)this).mSql); } else if (this instanceof CursorWindow) { @@ -74,4 +76,13 @@ public abstract class SQLiteClosable { buff.append(") "); return buff.toString(); } + + private void checkRefCount() { + if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { + if (mReferenceCount > 1000) { + throw new IllegalStateException("refcount: " + mReferenceCount + ", " + + getObjInfo()); + } + } + } } diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java index aa0a57d..0b5a4df 100644 --- a/core/java/android/database/sqlite/SQLiteCompiledSql.java +++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java @@ -92,9 +92,6 @@ import android.util.Log; // Note that native_finalize() checks to make sure that nStatement is // non-null before destroying it. if (nStatement != 0) { - if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { - Log.v(TAG, "closed and deallocated DbObj (id#" + nStatement +")"); - } mDatabase.finalizeStatementLater(nStatement); nStatement = 0; } @@ -109,16 +106,10 @@ import android.util.Log; return false; } mInUse = true; - if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { - Log.v(TAG, "Acquired DbObj (id#" + nStatement + ") from DB cache"); - } return true; } /* package */ synchronized void release() { - if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { - Log.v(TAG, "Released DbObj (id#" + nStatement + ") back to DB cache"); - } mInUse = false; } @@ -144,6 +135,22 @@ import android.util.Log; } } + @Override public String toString() { + synchronized(this) { + StringBuilder buff = new StringBuilder(); + buff.append(" nStatement="); + buff.append(nStatement); + buff.append(", db="); + buff.append(mDatabase.getPath()); + buff.append(", db_connectionNum="); + buff.append(mDatabase.mConnectionNum); + buff.append(", sql="); + int len = mSqlStmt.length(); + buff.append(mSqlStmt.substring(0, (len > 100) ? 100 : len)); + return buff.toString(); + } + } + /** * Compiles SQL into a SQLite program. * diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 0137ea6..6937da0 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1066,7 +1066,7 @@ public class SQLiteDatabase extends SQLiteClosable { closePendingStatements(); releaseCustomFunctions(); // close this database instance - regardless of its reference count value - dbclose(); + closeDatabase(); if (mConnectionPool != null) { if (Log.isLoggable(TAG, Log.DEBUG)) { assert mConnectionPool != null; @@ -1075,7 +1075,7 @@ public class SQLiteDatabase extends SQLiteClosable { mConnectionPool.close(); } } finally { - unlock(); + unlock(); } } @@ -1100,6 +1100,47 @@ public class SQLiteDatabase extends SQLiteClosable { } /** + * package level access for testing purposes + */ + /* package */ void closeDatabase() throws SQLiteException { + try { + dbclose(); + } catch (SQLiteUnfinalizedObjectsException e) { + String msg = e.getMessage(); + String[] tokens = msg.split(",", 2); + int stmtId = Integer.parseInt(tokens[0]); + // get extra info about this statement, if it is still to be released by closeClosable() + Iterator<Map.Entry<SQLiteClosable, Object>> iter = mPrograms.entrySet().iterator(); + boolean found = false; + while (iter.hasNext()) { + Map.Entry<SQLiteClosable, Object> entry = iter.next(); + SQLiteClosable program = entry.getKey(); + if (program != null && program instanceof SQLiteProgram) { + SQLiteCompiledSql compiledSql = ((SQLiteProgram)program).mCompiledSql; + if (compiledSql.nStatement == stmtId) { + msg = compiledSql.toString(); + found = true; + } + } + } + if (!found) { + // the statement is already released by closeClosable(). is it waiting to be + // finalized? + if (mClosedStatementIds.contains(stmtId)) { + Log.w(TAG, "this shouldn't happen. finalizing the statement now: "); + closePendingStatements(); + // try to close the database again + closeDatabase(); + } + } else { + // the statement is not yet closed. most probably programming error in the app. + Log.w(TAG, "dbclose failed due to un-close()d SQL statements: " + msg); + throw e; + } + } + } + + /** * Native call to close the database. */ private native void dbclose(); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index 9b7d823..bcb0c48 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -52,7 +52,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { /** * the SQLiteCompiledSql object for the given sql statement. */ - private SQLiteCompiledSql mCompiledSql; + /* package */ SQLiteCompiledSql mCompiledSql; /** * SQLiteCompiledSql statement id is populated with the corresponding object from the above @@ -130,10 +130,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { // make sure it is acquired by me. mCompiledSql.acquire(); mDatabase.addToCompiledQueries(mSql, mCompiledSql); - if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { - Log.v(TAG, "Created DbObj (id#" + mCompiledSql.nStatement + - ") for sql: " + mSql); - } } else { // it is already in compiled-sql cache. // try to acquire the object. @@ -144,12 +140,6 @@ public abstract class SQLiteProgram extends SQLiteClosable { // CompiledSql object. create a new one. // finalize it when I am done with it in "this" object. mCompiledSql = new SQLiteCompiledSql(mDatabase, mSql); - if (SQLiteDebug.DEBUG_ACTIVE_CURSOR_FINALIZATION) { - Log.v(TAG, "** possible bug ** Created NEW DbObj (id#" + - mCompiledSql.nStatement + - ") because the previously created DbObj (id#" + last + - ") was not released for sql:" + mSql); - } // since it is not in the cache, no need to acquire() it. } } diff --git a/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java new file mode 100644 index 0000000..bcf95e2 --- /dev/null +++ b/core/java/android/database/sqlite/SQLiteUnfinalizedObjectsException.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.database.sqlite; + +/** + * Thrown if the database can't be closed because of some un-closed + * Cursor or SQLiteStatement objects. Could happen when a thread is trying to close + * the database while another thread still hasn't closed a Cursor on that database. + * @hide + */ +public class SQLiteUnfinalizedObjectsException extends SQLiteException { + public SQLiteUnfinalizedObjectsException() {} + + public SQLiteUnfinalizedObjectsException(String error) { + super(error); + } +} diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 9fe6e01..a857e58 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -216,6 +216,11 @@ public abstract class BatteryStats implements Parcelable { public abstract Map<Integer, ? extends Sensor> getSensorStats(); /** + * Returns a mapping containing active process data. + */ + public abstract SparseArray<? extends Pid> getPidStats(); + + /** * Returns a mapping containing process statistics. * * @return a Map from Strings to Uid.Proc objects. @@ -286,6 +291,11 @@ public abstract class BatteryStats implements Parcelable { public abstract Timer getSensorTime(); } + public class Pid { + public long mWakeSum; + public long mWakeStart; + } + /** * The statistics associated with a particular process. */ @@ -521,6 +531,11 @@ public abstract class BatteryStats implements Parcelable { public abstract HistoryItem getHistory(); /** + * Return the base time offset for the battery history. + */ + public abstract long getHistoryBaseTime(); + + /** * Returns the number of times the device has been started. */ public abstract int getStartCount(); @@ -1673,6 +1688,7 @@ public abstract class BatteryStats implements Parcelable { HistoryItem rec = getHistory(); if (rec != null) { pw.println("Battery History:"); + long now = getHistoryBaseTime() + SystemClock.elapsedRealtime(); int oldState = 0; int oldStatus = -1; int oldHealth = -1; @@ -1681,7 +1697,7 @@ public abstract class BatteryStats implements Parcelable { int oldVolt = -1; while (rec != null) { pw.print(" "); - pw.print(rec.time); + TimeUtils.formatDuration(rec.time-now, pw, TimeUtils.HUNDRED_DAY_FIELD_LEN); pw.print(" "); if (rec.cmd == HistoryItem.CMD_START) { pw.println(" START"); @@ -1784,6 +1800,35 @@ public abstract class BatteryStats implements Parcelable { oldState = rec.states; rec = rec.next; } + pw.println(""); + } + + SparseArray<? extends Uid> uidStats = getUidStats(); + final int NU = uidStats.size(); + boolean didPid = false; + long nowRealtime = SystemClock.elapsedRealtime(); + StringBuilder sb = new StringBuilder(64); + for (int i=0; i<NU; i++) { + Uid uid = uidStats.valueAt(i); + SparseArray<? extends Uid.Pid> pids = uid.getPidStats(); + if (pids != null) { + for (int j=0; j<pids.size(); j++) { + Uid.Pid pid = pids.valueAt(j); + if (!didPid) { + pw.println("Per-PID Stats:"); + didPid = true; + } + long time = pid.mWakeSum + (pid.mWakeStart != 0 + ? (nowRealtime - pid.mWakeStart) : 0); + pw.print(" PID "); pw.print(pids.keyAt(j)); + pw.print(" wake time: "); + TimeUtils.formatDuration(time, pw); + pw.println(""); + } + } + } + if (didPid) { + pw.println(""); } pw.println("Statistics since last charge:"); diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 3fe14f9..0408673 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -34,7 +34,7 @@ public class ArrowKeyMovementMethod implements MovementMethod { * An optional controller for the cursor. * Use {@link #setCursorController(CursorController)} to set this field. */ - protected CursorController mCursorController; + private CursorController mCursorController; private boolean isCap(Spannable buffer) { return ((MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_SHIFT_ON) == 1) || @@ -302,7 +302,17 @@ public class ArrowKeyMovementMethod implements MovementMethod { /** * Defines the cursor controller. * - * When set, this object can be used to handle events, that can be translated in cursor updates. + * When set, this object can be used to handle touch events, that can be translated into cursor + * updates. + * + * {@link MotionEvent#ACTION_MOVE} events will call back the + * {@link CursorController#updatePosition(int, int)} controller's method, passing the current + * finger coordinates (offset by {@link CursorController#getOffsetX()} and + * {@link CursorController#getOffsetY()}) as parameters. + * + * When the gesture is finished (on a {@link MotionEvent#ACTION_UP} or + * {@link MotionEvent#ACTION_CANCEL} event), the controller is reset to null. + * * @param cursorController A cursor controller implementation */ public void setCursorController(CursorController cursorController) { diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java index b01a71d..60ca384 100644 --- a/core/java/android/util/TimeUtils.java +++ b/core/java/android/util/TimeUtils.java @@ -132,75 +132,76 @@ public class TimeUtils { return ZoneInfoDB.getVersion(); } + /** @hide Field length that can hold 999 days of time */ + public static final int HUNDRED_DAY_FIELD_LEN = 19; + private static final int SECONDS_PER_MINUTE = 60; private static final int SECONDS_PER_HOUR = 60 * 60; private static final int SECONDS_PER_DAY = 24 * 60 * 60; - /** @hide Just for debugging; not internationalized. */ - public static void formatDuration(long duration, StringBuilder builder) { - if (duration == 0) { - builder.append("0"); - return; - } - if (duration > 0) { - builder.append("+"); - } else { - builder.append("-"); - duration = -duration; - } - - int millis = (int)(duration%1000); - int seconds = (int) Math.floor(duration / 1000); - int days = 0, hours = 0, minutes = 0; - - if (seconds > SECONDS_PER_DAY) { - days = seconds / SECONDS_PER_DAY; - seconds -= days * SECONDS_PER_DAY; - } - if (seconds > SECONDS_PER_HOUR) { - hours = seconds / SECONDS_PER_HOUR; - seconds -= hours * SECONDS_PER_HOUR; - } - if (seconds > SECONDS_PER_MINUTE) { - minutes = seconds / SECONDS_PER_MINUTE; - seconds -= minutes * SECONDS_PER_MINUTE; - } - - boolean doall = false; - if (days > 0) { - builder.append(days); - builder.append('d'); - doall = true; + private static final Object sFormatSync = new Object(); + private static char[] sFormatStr = new char[HUNDRED_DAY_FIELD_LEN+5]; + + static private int accumField(int amt, int suffix, boolean always, int zeropad) { + if (amt > 99 || (always && zeropad >= 3)) { + return 3+suffix; } - if (doall || hours > 0) { - builder.append(hours); - builder.append('h'); - doall = true; + if (amt > 9 || (always && zeropad >= 2)) { + return 2+suffix; } - if (doall || minutes > 0) { - builder.append(minutes); - builder.append('m'); - doall = true; + if (always || amt > 0) { + return 1+suffix; } - if (doall || seconds > 0) { - builder.append(seconds); - builder.append('s'); - doall = true; + return 0; + } + + static private int printField(char[] formatStr, int amt, char suffix, int pos, + boolean always, int zeropad) { + if (always || amt > 0) { + if ((always && zeropad >= 3) || amt > 99) { + int dig = amt/100; + formatStr[pos] = (char)(dig + '0'); + pos++; + always = true; + amt -= (dig*100); + } + if ((always && zeropad >= 2) || amt > 9) { + int dig = amt/10; + formatStr[pos] = (char)(dig + '0'); + pos++; + always = true; + amt -= (dig*10); + } + formatStr[pos] = (char)(amt + '0'); + pos++; + formatStr[pos] = suffix; + pos++; } - builder.append(millis); - builder.append("ms"); + return pos; } - - /** @hide Just for debugging; not internationalized. */ - public static void formatDuration(long duration, PrintWriter pw) { + + private static int formatDurationLocked(long duration, int fieldLen) { + if (sFormatStr.length < fieldLen) { + sFormatStr = new char[fieldLen]; + } + + char[] formatStr = sFormatStr; + if (duration == 0) { - pw.print("0"); - return; + int pos = 0; + fieldLen -= 1; + while (pos < fieldLen) { + formatStr[pos] = ' '; + } + formatStr[pos] = '0'; + return pos+1; } + + char prefix; if (duration > 0) { - pw.print("+"); + prefix = '+'; } else { - pw.print("-"); + prefix = '-'; duration = -duration; } @@ -221,38 +222,62 @@ public class TimeUtils { seconds -= minutes * SECONDS_PER_MINUTE; } - boolean doall = false; - if (days > 0) { - pw.print(days); - pw.print('d'); - doall = true; - } - if (doall || hours > 0) { - pw.print(hours); - pw.print('h'); - doall = true; - } - if (doall || minutes > 0) { - pw.print(minutes); - pw.print('m'); - doall = true; + int pos = 0; + + if (fieldLen != 0) { + int myLen = accumField(days, 1, false, 0); + myLen += accumField(hours, 1, myLen > 0, 2); + myLen += accumField(minutes, 1, myLen > 0, 2); + myLen += accumField(seconds, 1, myLen > 0, 2); + myLen += accumField(millis, 2, true, myLen > 0 ? 3 : 0) + 1; + while (myLen < fieldLen) { + formatStr[pos] = ' '; + pos++; + myLen++; + } } - if (doall || seconds > 0) { - pw.print(seconds); - pw.print('s'); - doall = true; + + formatStr[pos] = prefix; + pos++; + + int start = pos; + boolean zeropad = fieldLen != 0; + pos = printField(formatStr, days, 'd', pos, false, 0); + pos = printField(formatStr, hours, 'h', pos, pos != start, zeropad ? 2 : 0); + pos = printField(formatStr, minutes, 'm', pos, pos != start, zeropad ? 2 : 0); + pos = printField(formatStr, seconds, 's', pos, pos != start, zeropad ? 2 : 0); + pos = printField(formatStr, millis, 'm', pos, true, (zeropad && pos != start) ? 3 : 0); + formatStr[pos] = 's'; + return pos + 1; + } + + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, StringBuilder builder) { + synchronized (sFormatSync) { + int len = formatDurationLocked(duration, 0); + builder.append(sFormatStr, 0, len); } - pw.print(millis); - pw.print("ms"); } + /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, PrintWriter pw, int fieldLen) { + synchronized (sFormatSync) { + int len = formatDurationLocked(duration, fieldLen); + pw.print(new String(sFormatStr, 0, len)); + } + } /** @hide Just for debugging; not internationalized. */ + public static void formatDuration(long duration, PrintWriter pw) { + formatDuration(duration, pw, 0); + } + + /** @hide Just for debugging; not internationalized. */ public static void formatDuration(long time, long now, PrintWriter pw) { if (time == 0) { pw.print("--"); return; } - formatDuration(time-now, pw); + formatDuration(time-now, pw, 0); } } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 7355353..f8d79e5 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -7298,9 +7298,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility if (ViewDebug.TRACE_HIERARCHY) { ViewDebug.trace(this, ViewDebug.HierarchyTraceType.BUILD_CACHE); } - if (Config.DEBUG && ViewDebug.profileDrawing) { - EventLog.writeEvent(60002, hashCode()); - } int width = mRight - mLeft; int height = mBottom - mTop; @@ -7738,7 +7735,8 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility saveCount = canvas.getSaveCount(); int solidColor = getSolidColor(); - if (solidColor == 0) { + // TODO: Temporarily disable fading edges with hardware acceleration + if (solidColor == 0 && !canvas.isHardwareAccelerated()) { final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG; if (drawTop) { @@ -7946,6 +7944,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility * @param r Right position, relative to parent * @param b Bottom position, relative to parent */ + @SuppressWarnings({"unchecked"}) public final void layout(int l, int t, int r, int b) { int oldL = mLeft; int oldT = mTop; @@ -10129,11 +10128,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Accessibility final RectF mTmpTransformRect = new RectF(); /** - * Temporary for use in computing invalidation areas with transformed views - */ - final float[] mTmpTransformBounds = new float[8]; - - /** * Temporary list for use in collecting focusable descendents of a view. */ final ArrayList<View> mFocusablesTempList = new ArrayList<View>(24); diff --git a/core/java/android/view/ViewDebug.java b/core/java/android/view/ViewDebug.java index 22cc3a8..b1d5272 100644 --- a/core/java/android/view/ViewDebug.java +++ b/core/java/android/view/ViewDebug.java @@ -119,24 +119,21 @@ public class ViewDebug { * * @hide */ - @Debug.DebugProperty - public static boolean profileDrawing = false; + public static final boolean DEBUG_PROFILE_DRAWING = false; /** * Profiles layout times in the events log. * * @hide */ - @Debug.DebugProperty - public static boolean profileLayout = false; + public static final boolean DEBUG_PROFILE_LAYOUT = false; /** * Profiles real fps (times between draws) and displays the result. * * @hide */ - @Debug.DebugProperty - public static boolean showFps = false; + public static final boolean DEBUG_SHOW_FPS = false; /** * <p>Enables or disables views consistency check. Even when this property is enabled, diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index dee6e73..363ccd6 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -32,8 +32,6 @@ import android.graphics.Region; import android.os.Parcelable; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Config; -import android.util.EventLog; import android.util.Log; import android.util.SparseArray; import android.view.accessibility.AccessibilityEvent; @@ -2020,9 +2018,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager cachePaint.setAlpha(255); mGroupFlags &= ~FLAG_ALPHA_LOWER_THAN_ONE; } - if (Config.DEBUG && ViewDebug.profileDrawing) { - EventLog.writeEvent(60003, hashCode()); - } canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint); } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 5d4ac37..e5fc859 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -946,8 +946,8 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn mSurfaceHolder.mSurfaceLock.unlock(); } } - - if (hwIntialized) { + + if (hwIntialized || (windowShouldResize && mHwRenderer != null)) { mHwRenderer.setup(mWidth, mHeight); } @@ -1008,7 +1008,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn TAG, "Laying out " + host + " to (" + host.mMeasuredWidth + ", " + host.mMeasuredHeight + ")"); long startTime = 0L; - if (ViewDebug.profileLayout) { + if (ViewDebug.DEBUG_PROFILE_LAYOUT) { startTime = SystemClock.elapsedRealtime(); } host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); @@ -1021,7 +1021,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn } } - if (ViewDebug.profileLayout) { + if (ViewDebug.DEBUG_PROFILE_LAYOUT) { EventLog.writeEvent(60001, SystemClock.elapsedRealtime() - startTime); } @@ -1321,7 +1321,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn //canvas.drawARGB(255, 255, 0, 0); } - if (ViewDebug.profileDrawing) { + if (ViewDebug.DEBUG_PROFILE_DRAWING) { startTime = SystemClock.elapsedRealtime(); } @@ -1364,7 +1364,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn mView.dispatchConsistencyCheck(ViewDebug.CONSISTENCY_DRAWING); } - if (SHOW_FPS || ViewDebug.showFps) { + if (SHOW_FPS || ViewDebug.DEBUG_SHOW_FPS) { int now = (int)SystemClock.elapsedRealtime(); if (sDrawTime != 0) { nativeShowFPS(canvas, now - sDrawTime); @@ -1372,7 +1372,7 @@ public final class ViewRoot extends Handler implements ViewParent, View.AttachIn sDrawTime = now; } - if (ViewDebug.profileDrawing) { + if (ViewDebug.DEBUG_PROFILE_DRAWING) { EventLog.writeEvent(60000, SystemClock.elapsedRealtime() - startTime); } } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index 0ed5fc5..d81d7f2 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -211,6 +211,7 @@ public class WebSettings { private boolean mNavDump = false; private boolean mSupportZoom = true; private boolean mBuiltInZoomControls = false; + private boolean mDisplayZoomControls = true; private boolean mAllowFileAccess = true; private boolean mLoadWithOverviewMode = false; private boolean mEnableSmoothTransition = false; @@ -508,6 +509,26 @@ public class WebSettings { } /** + * Sets whether the on screen zoom buttons are used. + * A combination of built in zoom controls enabled + * and on screen zoom controls disabled allows for pinch to zoom + * to work without the on screen controls + * @hide + */ + public void setDisplayZoomControls(boolean enabled) { + mDisplayZoomControls = enabled; + mWebView.updateMultiTouchSupport(mContext); + } + + /** + * Returns true if the on screen zoom buttons are being used. + * @hide + */ + public boolean getDisplayZoomControls() { + return mDisplayZoomControls; + } + + /** * Enable or disable file access within WebView. File access is enabled by * default. */ diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 33ebcf5..1323217 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -856,7 +856,8 @@ class ZoomManager { private ZoomControlBase getCurrentZoomControl() { if (mWebView.getSettings() != null && mWebView.getSettings().supportZoom()) { if (mWebView.getSettings().getBuiltInZoomControls()) { - if (mEmbeddedZoomControl == null) { + if ((mEmbeddedZoomControl == null) + && mWebView.getSettings().getDisplayZoomControls()) { mEmbeddedZoomControl = new ZoomControlEmbedded(this, mWebView); } return mEmbeddedZoomControl; diff --git a/core/java/android/widget/AdapterViewAnimator.java b/core/java/android/widget/AdapterViewAnimator.java index 0636d72..830e899 100644 --- a/core/java/android/widget/AdapterViewAnimator.java +++ b/core/java/android/widget/AdapterViewAnimator.java @@ -31,6 +31,7 @@ import android.os.Parcelable; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; +import android.view.ViewGroup.LayoutParams; import android.view.animation.Animation; import android.view.animation.AnimationUtils; @@ -128,6 +129,13 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> boolean mShouldLoop = true; /** + * The width and height of some child, used as a size reference in-case our + * dimensions are unspecified by the parent. + */ + int mReferenceChildWidth = -1; + int mReferenceChildHeight = -1; + + /** * TODO: Animation stuff is still in flux, waiting on the new framework to settle a bit. */ Animation mInAnimation; @@ -414,7 +422,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> FrameLayout fl = new FrameLayout(mContext); fl.addView(newView); mActiveViews[index] = fl; - addViewInLayout(fl, -1, createOrReuseLayoutParams(fl)); + addChild(fl); applyTransformForChildAtIndex(fl, newRelativeIndex); animateViewForTransition(-1, newRelativeIndex, fl); } @@ -451,6 +459,64 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> } } + private void addChild(View child) { + addViewInLayout(child, -1, createOrReuseLayoutParams(child)); + + // This code is used to obtain a reference width and height of a child in case we need + // to decide our own size. TODO: Do we want to update the size of the child that we're + // using for reference size? If so, when? + if (mReferenceChildWidth == -1 || mReferenceChildHeight == -1) { + int measureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + child.measure(measureSpec, measureSpec); + mReferenceChildWidth = child.getMeasuredWidth(); + mReferenceChildHeight = child.getMeasuredHeight(); + } + } + + private void measureChildren() { + final int count = getChildCount(); + final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight; + final int childHeight = mMeasuredHeight - mPaddingTop - mPaddingBottom; + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + + boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); + + // We need to deal with the case where our parent hasn't told us how + // big we should be. In this case we try to use the desired size of the first + // child added. + if (heightSpecMode == MeasureSpec.UNSPECIFIED) { + heightSpecSize = haveChildRefSize ? mReferenceChildHeight + mPaddingTop + + mPaddingBottom : 0; + } else if (heightSpecMode == MeasureSpec.AT_MOST) { + heightSpecSize = haveChildRefSize ? Math.min(mReferenceChildHeight + mPaddingTop + + mPaddingBottom, heightSpecSize) : 0; + } + + if (widthSpecMode == MeasureSpec.UNSPECIFIED) { + widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + + mPaddingRight : 0; + } else if (heightSpecMode == MeasureSpec.AT_MOST) { + widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft + + mPaddingRight, widthSpecSize) : 0; + } + + setMeasuredDimension(widthSpecSize, heightSpecSize); + measureChildren(); + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { boolean dataChanged = mDataChanged; @@ -472,8 +538,7 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> int childRight = mPaddingLeft + child.getMeasuredWidth(); int childBottom = mPaddingTop + child.getMeasuredHeight(); - child.layout(mPaddingLeft, mPaddingTop, - childRight, childBottom); + child.layout(mPaddingLeft, mPaddingTop, childRight, childBottom); } mDataChanged = false; } @@ -538,31 +603,6 @@ public abstract class AdapterViewAnimator extends AdapterView<Adapter> setDisplayedChild(mWhichChild); } - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - final int count = getChildCount(); - - int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); - - for (int i = 0; i < count; i++) { - final View child = getChildAt(i); - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - lp.width = widthSpecSize - mPaddingLeft - mPaddingRight; - lp.height = heightSpecSize - mPaddingTop - mPaddingBottom; - - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(lp.width, - MeasureSpec.EXACTLY); - int childheightMeasureSpec = MeasureSpec.makeMeasureSpec(lp.height, - MeasureSpec.EXACTLY); - - child.measure(childWidthMeasureSpec, childheightMeasureSpec); - } - setMeasuredDimension(widthSpecSize, heightSpecSize); - } - /** * Shows only the specified child. The other displays Views exit the screen * with the {@link #getOutAnimation() out animation} and the specified child diff --git a/core/java/android/widget/QuickContactBadge.java b/core/java/android/widget/QuickContactBadge.java index 50fbb6b..5598c65 100644 --- a/core/java/android/widget/QuickContactBadge.java +++ b/core/java/android/widget/QuickContactBadge.java @@ -247,6 +247,7 @@ public class QuickContactBadge extends ImageView implements OnClickListener { trigger = true; createUri = Uri.fromParts("tel", (String)cookie, null); + //$FALL-THROUGH$ case TOKEN_PHONE_LOOKUP: { if (cursor != null && cursor.moveToFirst()) { long contactId = cursor.getLong(PHONE_ID_COLUMN_INDEX); @@ -260,12 +261,14 @@ public class QuickContactBadge extends ImageView implements OnClickListener { trigger = true; createUri = Uri.fromParts("mailto", (String)cookie, null); + //$FALL-THROUGH$ case TOKEN_EMAIL_LOOKUP: { if (cursor != null && cursor.moveToFirst()) { long contactId = cursor.getLong(EMAIL_ID_COLUMN_INDEX); String lookupKey = cursor.getString(EMAIL_LOOKUP_STRING_COLUMN_INDEX); lookupUri = Contacts.getLookupUri(contactId, lookupKey); } + break; } case TOKEN_CONTACT_LOOKUP_AND_TRIGGER: { diff --git a/core/java/android/widget/StackView.java b/core/java/android/widget/StackView.java index 9816b39..9025b83 100644 --- a/core/java/android/widget/StackView.java +++ b/core/java/android/widget/StackView.java @@ -16,11 +16,11 @@ package android.widget; -import java.util.WeakHashMap; - import android.animation.PropertyAnimator; +import android.animation.PropertyValuesHolder; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.BlurMaskFilter; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.Paint; @@ -28,6 +28,7 @@ import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.TableMaskFilter; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; @@ -35,6 +36,8 @@ import android.view.VelocityTracker; import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; +import android.view.View.MeasureSpec; +import android.view.ViewGroup.LayoutParams; import android.view.animation.LinearInterpolator; import android.widget.RemoteViews.RemoteView; @@ -53,6 +56,19 @@ public class StackView extends AdapterViewAnimator { private final int MINIMUM_ANIMATION_DURATION = 50; /** + * Parameters effecting the perspective visuals + */ + private static float PERSPECTIVE_SHIFT_FACTOR = 0.12f; + private static float PERSPECTIVE_SCALE_FACTOR = 0.35f; + + /** + * Represent the two possible stack modes, one where items slide up, and the other + * where items slide down. The perspective is also inverted between these two modes. + */ + private static final int ITEMS_SLIDE_UP = 0; + private static final int ITEMS_SLIDE_DOWN = 1; + + /** * These specify the different gesture states */ private static final int GESTURE_NONE = 0; @@ -66,8 +82,6 @@ public class StackView extends AdapterViewAnimator { private static final float SWIPE_THRESHOLD_RATIO = 0.35f; private static final float SLIDE_UP_RATIO = 0.7f; - private final WeakHashMap<View, Float> mRotations = new WeakHashMap<View, Float>(); - /** * Sentinel value for no current active pointer. * Used by {@link #mActivePointerId}. @@ -75,6 +89,12 @@ public class StackView extends AdapterViewAnimator { private static final int INVALID_POINTER = -1; /** + * Number of active views in the stack. One fewer view is actually visible, as one is hidden. + */ + private static final int NUM_ACTIVE_VIEWS = 5; + + + /** * These variables are all related to the current state of touch interaction * with the stack */ @@ -95,6 +115,7 @@ public class StackView extends AdapterViewAnimator { private boolean mFirstLayoutHappened = false; private ViewGroup mAncestorContainingAllChildren = null; private int mAncestorHeight = 0; + private int mStackMode; public StackView(Context context) { super(context); @@ -107,7 +128,7 @@ public class StackView extends AdapterViewAnimator { } private void initStackView() { - configureViewAnimator(4, 2, false); + configureViewAnimator(NUM_ACTIVE_VIEWS, NUM_ACTIVE_VIEWS - 2, false); setStaticTransformationsEnabled(true); final ViewConfiguration configuration = ViewConfiguration.get(getContext()); mTouchSlop = configuration.getScaledTouchSlop(); @@ -125,6 +146,11 @@ public class StackView extends AdapterViewAnimator { setClipChildren(false); setClipToPadding(false); + // This sets the form of the StackView, which is currently to have the perspective-shifted + // views above the active view, and have items slide down when sliding out. The opposite is + // available by using ITEMS_SLIDE_UP. + mStackMode = ITEMS_SLIDE_DOWN; + // This is a flag to indicate the the stack is loading for the first time mWhichChild = -1; } @@ -140,7 +166,7 @@ public class StackView extends AdapterViewAnimator { } view.setVisibility(VISIBLE); - PropertyAnimator fadeIn = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, + PropertyAnimator<Float> fadeIn = new PropertyAnimator<Float>(DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 1.0f); fadeIn.start(); } else if (fromIndex == mNumActiveViews - 1 && toIndex == mNumActiveViews - 2) { @@ -148,49 +174,32 @@ public class StackView extends AdapterViewAnimator { view.setVisibility(VISIBLE); LayoutParams lp = (LayoutParams) view.getLayoutParams(); - int largestDuration = - Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION); - - int duration = largestDuration; - if (mYVelocity != 0) { - duration = 1000*(0 - lp.verticalOffset)/Math.abs(mYVelocity); - } - - duration = Math.min(duration, largestDuration); - duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); + int duration = Math.round(mStackSlider.getDurationForNeutralPosition(mYVelocity)); StackSlider animationSlider = new StackSlider(mStackSlider); - PropertyAnimator slideInY = new PropertyAnimator(duration, animationSlider, - "YProgress", mStackSlider.getYProgress(), 0); - slideInY.setInterpolator(new LinearInterpolator()); - slideInY.start(); - PropertyAnimator slideInX = new PropertyAnimator(duration, animationSlider, - "XProgress", mStackSlider.getXProgress(), 0); - slideInX.setInterpolator(new LinearInterpolator()); - slideInX.start(); + PropertyValuesHolder<Float> slideInY = + new PropertyValuesHolder<Float>("YProgress", 0.0f); + PropertyValuesHolder<Float> slideInX = + new PropertyValuesHolder<Float>("XProgress", 0.0f); + PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + slideInX, slideInY); + pa.setInterpolator(new LinearInterpolator()); + pa.start(); } else if (fromIndex == mNumActiveViews - 2 && toIndex == mNumActiveViews - 1) { // Slide item out LayoutParams lp = (LayoutParams) view.getLayoutParams(); - int largestDuration = Math.round(mStackSlider.getDurationForOffscreenPosition()* - DEFAULT_ANIMATION_DURATION); - int duration = largestDuration; - if (mYVelocity != 0) { - duration = 1000*(lp.verticalOffset + mViewHeight)/Math.abs(mYVelocity); - } - - duration = Math.min(duration, largestDuration); - duration = Math.max(duration, MINIMUM_ANIMATION_DURATION); + int duration = Math.round(mStackSlider.getDurationForOffscreenPosition(mYVelocity)); StackSlider animationSlider = new StackSlider(mStackSlider); - PropertyAnimator slideOutY = new PropertyAnimator(duration, animationSlider, - "YProgress", mStackSlider.getYProgress(), 1); - slideOutY.setInterpolator(new LinearInterpolator()); - slideOutY.start(); - PropertyAnimator slideOutX = new PropertyAnimator(duration, animationSlider, - "XProgress", mStackSlider.getXProgress(), 0); - slideOutX.setInterpolator(new LinearInterpolator()); - slideOutX.start(); + PropertyValuesHolder<Float> slideOutY = + new PropertyValuesHolder<Float>("YProgress", 1.0f); + PropertyValuesHolder<Float> slideOutX = + new PropertyValuesHolder<Float>("XProgress", 0.0f); + PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + slideOutX, slideOutY); + pa.setInterpolator(new LinearInterpolator()); + pa.start(); } else if (fromIndex == -1 && toIndex == mNumActiveViews - 1) { // Make sure this view that is "waiting in the wings" is invisible view.setAlpha(0.0f); @@ -199,28 +208,41 @@ public class StackView extends AdapterViewAnimator { lp.setVerticalOffset(-mViewHeight); } else if (toIndex == -1) { // Fade item out - PropertyAnimator fadeOut = new PropertyAnimator(DEFAULT_ANIMATION_DURATION, - view, "alpha", view.getAlpha(), 0); + PropertyAnimator<Float> fadeOut = new PropertyAnimator<Float> + (DEFAULT_ANIMATION_DURATION, view, "alpha", view.getAlpha(), 0.0f); fadeOut.start(); } + + // Implement the faked perspective + if (toIndex != -1) { + float maxPerpectiveShift = mViewHeight * PERSPECTIVE_SHIFT_FACTOR; + int index = toIndex; + + if (toIndex == mNumActiveViews -1) index--; + + float r = (index * 1.0f) / (mNumActiveViews - 2); + + float scale = 1 - PERSPECTIVE_SCALE_FACTOR * (1 - r); + PropertyValuesHolder<Float> scaleX = new PropertyValuesHolder<Float>("scaleX", scale); + PropertyValuesHolder<Float> scaleY = new PropertyValuesHolder<Float>("scaleY", scale); + + r = (float) Math.pow(r, 2); + + int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; + float transY = -stackDirection * r * maxPerpectiveShift + + stackDirection * (1 - scale) * (mViewHeight / 2.0f); + + PropertyValuesHolder<Float> translationY = + new PropertyValuesHolder<Float>("translationY", transY); + PropertyAnimator pa = new PropertyAnimator(100, view, scaleX, scaleY, translationY); + pa.start(); + } } /** * Apply any necessary tranforms for the child that is being added. */ void applyTransformForChildAtIndex(View child, int relativeIndex) { - if (!mRotations.containsKey(child)) { - float rotation = (float) (Math.random()*26 - 13); - mRotations.put(child, rotation); - child.setRotation(rotation); - } - - // Child has been removed - if (relativeIndex == -1) { - if (mRotations.containsKey(child)) { - mRotations.remove(child); - } - } } @Override @@ -248,8 +270,8 @@ public class StackView extends AdapterViewAnimator { private void onLayout() { if (!mFirstLayoutHappened) { - mViewHeight = Math.round(SLIDE_UP_RATIO*getMeasuredHeight()); - mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO*mViewHeight); + mViewHeight = Math.round(SLIDE_UP_RATIO * getMeasuredHeight()); + mSwipeThreshold = Math.round(SWIPE_THRESHOLD_RATIO * mViewHeight); mFirstLayoutHappened = true; } } @@ -299,8 +321,14 @@ public class StackView extends AdapterViewAnimator { cancelLongPress(); requestDisallowInterceptTouchEvent(true); - int activeIndex = swipeGestureType == GESTURE_SLIDE_DOWN ? mNumActiveViews - 1 - : mNumActiveViews - 2; + int activeIndex; + if (mStackMode == ITEMS_SLIDE_UP) { + activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? + mNumActiveViews - 1 : mNumActiveViews - 2; + } else { + activeIndex = (swipeGestureType == GESTURE_SLIDE_DOWN) ? + mNumActiveViews - 2 : mNumActiveViews - 1; + } if (mAdapter == null) return; @@ -317,6 +345,8 @@ public class StackView extends AdapterViewAnimator { if (v == null) return; mHighlight.setImageBitmap(sHolographicHelper.createOutline(v)); + mHighlight.setRotation(v.getRotation()); + mHighlight.setTranslationY(v.getTranslationY()); mHighlight.bringToFront(); v.bringToFront(); mStackSlider.setView(v); @@ -352,14 +382,16 @@ public class StackView extends AdapterViewAnimator { case MotionEvent.ACTION_MOVE: { beginGestureIfNeeded(deltaY); - float rx = deltaX/(mViewHeight*1.0f); + float rx = deltaX / (mViewHeight * 1.0f); if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { - float r = (deltaY-mTouchSlop*1.0f)/mViewHeight*1.0f; + float r = (deltaY - mTouchSlop * 1.0f) / mViewHeight * 1.0f; + if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; mStackSlider.setYProgress(1 - r); mStackSlider.setXProgress(rx); return true; } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { - float r = -(deltaY + mTouchSlop*1.0f)/mViewHeight*1.0f; + float r = -(deltaY + mTouchSlop * 1.0f) / mViewHeight * 1.0f; + if (mStackMode == ITEMS_SLIDE_DOWN) r = 1 - r; mStackSlider.setYProgress(r); mStackSlider.setXProgress(rx); return true; @@ -447,41 +479,59 @@ public class StackView extends AdapterViewAnimator { if (deltaY > mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_DOWN && mStackSlider.mMode == StackSlider.NORMAL_MODE) { // Swipe threshold exceeded, swipe down - showNext(); + if (mStackMode == ITEMS_SLIDE_UP) { + showNext(); + } else { + showPrevious(); + } mHighlight.bringToFront(); } else if (deltaY < -mSwipeThreshold && mSwipeGestureType == GESTURE_SLIDE_UP && mStackSlider.mMode == StackSlider.NORMAL_MODE) { // Swipe threshold exceeded, swipe up - showPrevious(); + if (mStackMode == ITEMS_SLIDE_UP) { + showPrevious(); + } else { + showNext(); + } + mHighlight.bringToFront(); - } else if (mSwipeGestureType == GESTURE_SLIDE_UP) { + } else if (mSwipeGestureType == GESTURE_SLIDE_UP ) { // Didn't swipe up far enough, snap back down - int duration = - Math.round(mStackSlider.getDurationForNeutralPosition()*DEFAULT_ANIMATION_DURATION); + int duration; + float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 1 : 0; + if (mStackMode == ITEMS_SLIDE_UP || mStackSlider.mMode != StackSlider.NORMAL_MODE) { + duration = Math.round(mStackSlider.getDurationForNeutralPosition()); + } else { + duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); + } StackSlider animationSlider = new StackSlider(mStackSlider); - PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider, - "YProgress", mStackSlider.getYProgress(), 0); - snapBackY.setInterpolator(new LinearInterpolator()); - snapBackY.start(); - PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider, - "XProgress", mStackSlider.getXProgress(), 0); - snapBackX.setInterpolator(new LinearInterpolator()); - snapBackX.start(); + PropertyValuesHolder<Float> snapBackY = + new PropertyValuesHolder<Float>("YProgress", finalYProgress); + PropertyValuesHolder<Float> snapBackX = + new PropertyValuesHolder<Float>("XProgress", 0.0f); + PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + snapBackX, snapBackY); + pa.setInterpolator(new LinearInterpolator()); + pa.start(); } else if (mSwipeGestureType == GESTURE_SLIDE_DOWN) { // Didn't swipe down far enough, snap back up - int duration = Math.round(mStackSlider.getDurationForOffscreenPosition()* - DEFAULT_ANIMATION_DURATION); + float finalYProgress = (mStackMode == ITEMS_SLIDE_DOWN) ? 0 : 1; + int duration; + if (mStackMode == ITEMS_SLIDE_DOWN || mStackSlider.mMode != StackSlider.NORMAL_MODE) { + duration = Math.round(mStackSlider.getDurationForNeutralPosition()); + } else { + duration = Math.round(mStackSlider.getDurationForOffscreenPosition()); + } StackSlider animationSlider = new StackSlider(mStackSlider); - PropertyAnimator snapBackY = new PropertyAnimator(duration, animationSlider, - "YProgress", mStackSlider.getYProgress(), 1); - snapBackY.setInterpolator(new LinearInterpolator()); - snapBackY.start(); - PropertyAnimator snapBackX = new PropertyAnimator(duration, animationSlider, - "XProgress", mStackSlider.getXProgress(), 0); - snapBackX.setInterpolator(new LinearInterpolator()); - snapBackX.start(); + PropertyValuesHolder<Float> snapBackY = + new PropertyValuesHolder<Float>("YProgress", finalYProgress); + PropertyValuesHolder<Float> snapBackX = + new PropertyValuesHolder<Float>("XProgress", 0.0f); + PropertyAnimator pa = new PropertyAnimator(duration, animationSlider, + snapBackX, snapBackY); + pa.start(); } mActivePointerId = INVALID_POINTER; @@ -510,22 +560,22 @@ public class StackView extends AdapterViewAnimator { } private float cubic(float r) { - return (float) (Math.pow(2*r-1, 3) + 1)/2.0f; + return (float) (Math.pow(2 * r - 1, 3) + 1) / 2.0f; } private float highlightAlphaInterpolator(float r) { float pivot = 0.4f; if (r < pivot) { - return 0.85f*cubic(r/pivot); + return 0.85f * cubic(r / pivot); } else { - return 0.85f*cubic(1 - (r-pivot)/(1-pivot)); + return 0.85f * cubic(1 - (r - pivot) / (1 - pivot)); } } private float viewAlphaInterpolator(float r) { float pivot = 0.3f; if (r > pivot) { - return (r - pivot)/(1 - pivot); + return (r - pivot) / (1 - pivot); } else { return 0; } @@ -536,7 +586,7 @@ public class StackView extends AdapterViewAnimator { if (r < pivot) { return 0; } else { - return (r-pivot)/(1-pivot); + return (r - pivot) / (1 - pivot); } } @@ -553,13 +603,15 @@ public class StackView extends AdapterViewAnimator { final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); + int stackDirection = (mStackMode == ITEMS_SLIDE_UP) ? 1 : -1; + switch (mMode) { case NORMAL_MODE: - viewLp.setVerticalOffset(Math.round(-r*mViewHeight)); - highlightLp.setVerticalOffset(Math.round(-r*mViewHeight)); + viewLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight)); + highlightLp.setVerticalOffset(Math.round(-r * stackDirection * mViewHeight)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); - float alpha = viewAlphaInterpolator(1-r); + float alpha = viewAlphaInterpolator(1 - r); // We make sure that views which can't be seen (have 0 alpha) are also invisible // so that they don't interfere with click events. @@ -571,19 +623,19 @@ public class StackView extends AdapterViewAnimator { } mView.setAlpha(alpha); - mView.setRotationX(90.0f*rotationInterpolator(r)); - mHighlight.setRotationX(90.0f*rotationInterpolator(r)); + mView.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); + mHighlight.setRotationX(stackDirection * 90.0f * rotationInterpolator(r)); break; case BEGINNING_OF_STACK_MODE: - r = r*0.2f; - viewLp.setVerticalOffset(Math.round(-r*mViewHeight)); - highlightLp.setVerticalOffset(Math.round(-r*mViewHeight)); + r = r * 0.2f; + viewLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight)); + highlightLp.setVerticalOffset(Math.round(-stackDirection * r * mViewHeight)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); break; case END_OF_STACK_MODE: - r = (1-r)*0.2f; - viewLp.setVerticalOffset(Math.round(r*mViewHeight)); - highlightLp.setVerticalOffset(Math.round(r*mViewHeight)); + r = (1-r) * 0.2f; + viewLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight)); + highlightLp.setVerticalOffset(Math.round(stackDirection * r * mViewHeight)); mHighlight.setAlpha(highlightAlphaInterpolator(r)); break; } @@ -600,8 +652,8 @@ public class StackView extends AdapterViewAnimator { final LayoutParams highlightLp = (LayoutParams) mHighlight.getLayoutParams(); r *= 0.2f; - viewLp.setHorizontalOffset(Math.round(r*mViewHeight)); - highlightLp.setHorizontalOffset(Math.round(r*mViewHeight)); + viewLp.setHorizontalOffset(Math.round(r * mViewHeight)); + highlightLp.setHorizontalOffset(Math.round(r * mViewHeight)); } void setMode(int mode) { @@ -609,31 +661,51 @@ public class StackView extends AdapterViewAnimator { } float getDurationForNeutralPosition() { - return getDuration(false); + return getDuration(false, 0); } float getDurationForOffscreenPosition() { - return getDuration(mMode == END_OF_STACK_MODE ? false : true); + return getDuration(true, 0); + } + + float getDurationForNeutralPosition(float velocity) { + return getDuration(false, velocity); + } + + float getDurationForOffscreenPosition(float velocity) { + return getDuration(true, velocity); } - private float getDuration(boolean invert) { + private float getDuration(boolean invert, float velocity) { if (mView != null) { final LayoutParams viewLp = (LayoutParams) mView.getLayoutParams(); - float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset,2) + - Math.pow(viewLp.verticalOffset,2)); + float d = (float) Math.sqrt(Math.pow(viewLp.horizontalOffset, 2) + + Math.pow(viewLp.verticalOffset, 2)); float maxd = (float) Math.sqrt(Math.pow(mViewHeight, 2) + - Math.pow(0.4f*mViewHeight, 2)); - return invert ? (1-d/maxd) : d/maxd; + Math.pow(0.4f * mViewHeight, 2)); + + if (velocity == 0) { + return (invert ? (1 - d / maxd) : d / maxd) * DEFAULT_ANIMATION_DURATION; + } else { + float duration = invert ? d / Math.abs(velocity) : + (maxd - d) / Math.abs(velocity); + if (duration < MINIMUM_ANIMATION_DURATION || + duration > DEFAULT_ANIMATION_DURATION) { + return getDuration(invert, 0); + } else { + return duration; + } + } } return 0; } - float getYProgress() { + public float getYProgress() { return mYProgress; } - float getXProgress() { + public float getXProgress() { return mXProgress; } } @@ -654,6 +726,8 @@ public class StackView extends AdapterViewAnimator { LayoutParams lp = (LayoutParams) currentLp; lp.setHorizontalOffset(0); lp.setVerticalOffset(0); + lp.width = 0; + lp.width = 0; return lp; } return new LayoutParams(v); @@ -684,15 +758,59 @@ public class StackView extends AdapterViewAnimator { child.layout(mPaddingLeft + lp.horizontalOffset, mPaddingTop + lp.verticalOffset, childRight + lp.horizontalOffset, childBottom + lp.verticalOffset); - //TODO: temp until fix in View - child.setPivotX(child.getMeasuredWidth()/2); - child.setPivotY(child.getMeasuredHeight()/2); } mDataChanged = false; onLayout(); } + private void measureChildren() { + final int count = getChildCount(); + final int childWidth = mMeasuredWidth - mPaddingLeft - mPaddingRight; + final int childHeight = Math.round(mMeasuredHeight*(1-PERSPECTIVE_SHIFT_FACTOR)) + - mPaddingTop - mPaddingBottom; + + for (int i = 0; i < count; i++) { + final View child = getChildAt(i); + child.measure(MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)); + } + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec); + int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec); + final int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec); + final int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec); + + boolean haveChildRefSize = (mReferenceChildWidth != -1 && mReferenceChildHeight != -1); + + // We need to deal with the case where our parent hasn't told us how + // big we should be. In this case we should + float factor = 1/(1 - PERSPECTIVE_SHIFT_FACTOR); + if (heightSpecMode == MeasureSpec.UNSPECIFIED) { + heightSpecSize = haveChildRefSize ? + Math.round(mReferenceChildHeight * (1 + factor)) + + mPaddingTop + mPaddingBottom : 0; + } else if (heightSpecMode == MeasureSpec.AT_MOST) { + heightSpecSize = haveChildRefSize ? Math.min( + Math.round(mReferenceChildHeight * (1 + factor)) + mPaddingTop + + mPaddingBottom, heightSpecSize) : 0; + } + + if (widthSpecMode == MeasureSpec.UNSPECIFIED) { + widthSpecSize = haveChildRefSize ? mReferenceChildWidth + mPaddingLeft + + mPaddingRight : 0; + } else if (heightSpecMode == MeasureSpec.AT_MOST) { + widthSpecSize = haveChildRefSize ? Math.min(mReferenceChildWidth + mPaddingLeft + + mPaddingRight, widthSpecSize) : 0; + } + + setMeasuredDimension(widthSpecSize, heightSpecSize); + measureChildren(); + } + class LayoutParams extends ViewGroup.LayoutParams { int horizontalOffset; int verticalOffset; @@ -700,6 +818,8 @@ public class StackView extends AdapterViewAnimator { LayoutParams(View view) { super(0, 0); + width = 0; + height = 0; horizontalOffset = 0; verticalOffset = 0; mView = view; @@ -709,6 +829,8 @@ public class StackView extends AdapterViewAnimator { super(c, attrs); horizontalOffset = 0; verticalOffset = 0; + width = 0; + height = 0; } private Rect parentRect = new Rect(); @@ -731,6 +853,10 @@ public class StackView extends AdapterViewAnimator { gp = (View) p.getParent(); parentRect.set(p.getLeft() - gp.getScrollX(), p.getTop() - gp.getScrollY(), p.getRight() - gp.getScrollX(), p.getBottom() - gp.getScrollY()); + + // TODO: we need to stop early here if we've hit the edge of the screen + // so as to prevent us from walking too high in the hierarchy. A lot of this + // code might become a lot more straightforward. } if (depth > mAncestorHeight) { @@ -799,7 +925,7 @@ public class StackView extends AdapterViewAnimator { private static class HolographicHelper { private final Paint mHolographicPaint = new Paint(); private final Paint mErasePaint = new Paint(); - private final float STROKE_WIDTH = 3.0f; + private final Paint mBlurPaint = new Paint(); HolographicHelper() { initializePaints(); @@ -808,8 +934,10 @@ public class StackView extends AdapterViewAnimator { void initializePaints() { mHolographicPaint.setColor(0xff6699ff); mHolographicPaint.setFilterBitmap(true); + mHolographicPaint.setMaskFilter(TableMaskFilter.CreateClipTable(0, 30)); mErasePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_OUT)); mErasePaint.setFilterBitmap(true); + mBlurPaint.setMaskFilter(new BlurMaskFilter(2, BlurMaskFilter.Blur.NORMAL)); } Bitmap createOutline(View v) { @@ -822,31 +950,31 @@ public class StackView extends AdapterViewAnimator { Canvas canvas = new Canvas(bitmap); float rotationX = v.getRotationX(); + float rotation = v.getRotation(); + float translationY = v.getTranslationY(); v.setRotationX(0); + v.setRotation(0); + v.setTranslationY(0); canvas.concat(v.getMatrix()); v.draw(canvas); - v.setRotationX(rotationX); + v.setRotation(rotation); + v.setTranslationY(translationY); + canvas.setMatrix(id); drawOutline(canvas, bitmap); return bitmap; } final Matrix id = new Matrix(); - final Matrix scaleMatrix = new Matrix(); void drawOutline(Canvas dest, Bitmap src) { - Bitmap mask = src.extractAlpha(); - + int[] xy = new int[2]; + Bitmap mask = src.extractAlpha(mBlurPaint, xy); + Canvas maskCanvas = new Canvas(mask); + maskCanvas.drawBitmap(src, -xy[0], -xy[1], mErasePaint); dest.drawColor(0, PorterDuff.Mode.CLEAR); - - float xScale = STROKE_WIDTH*2/(dest.getWidth()); - float yScale = STROKE_WIDTH*2/(dest.getHeight()); - - scaleMatrix.reset(); - scaleMatrix.preScale(1+xScale, 1+yScale, dest.getWidth()/2, dest.getHeight()/2); dest.setMatrix(id); - dest.drawBitmap(mask, scaleMatrix, mHolographicPaint); - dest.drawBitmap(mask, id, mErasePaint); + dest.drawBitmap(mask, xy[0], xy[1], mHolographicPaint); mask.recycle(); } } diff --git a/core/java/com/android/internal/app/ActionBarImpl.java b/core/java/com/android/internal/app/ActionBarImpl.java index 1048352..cf029d2 100644 --- a/core/java/com/android/internal/app/ActionBarImpl.java +++ b/core/java/com/android/internal/app/ActionBarImpl.java @@ -122,17 +122,6 @@ public class ActionBarImpl extends ActionBar { } @Override - public void setStandardNavigationMode(int titleResId, int subtitleResId) { - setStandardNavigationMode(mContext.getString(titleResId), - mContext.getString(subtitleResId)); - } - - @Override - public void setStandardNavigationMode(int titleResId) { - setStandardNavigationMode(mContext.getString(titleResId)); - } - - @Override public void setTitle(int resId) { setTitle(mContext.getString(resId)); } @@ -169,19 +158,6 @@ public class ActionBarImpl extends ActionBar { mActionView.setCallback(null); } - public void setStandardNavigationMode(CharSequence title) { - cleanupTabs(); - setStandardNavigationMode(title, null); - } - - public void setStandardNavigationMode(CharSequence title, CharSequence subtitle) { - cleanupTabs(); - mActionView.setNavigationMode(NAVIGATION_MODE_STANDARD); - mActionView.setTitle(title); - mActionView.setSubtitle(subtitle); - mActionView.setCallback(null); - } - public void setSelectedNavigationItem(int position) { switch (mActionView.getNavigationMode()) { case NAVIGATION_MODE_TABS: @@ -211,17 +187,7 @@ public class ActionBarImpl extends ActionBar { if (mSelectedTab != null) { selectTab(null); } - if (!mTabs.isEmpty()) { - if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { - final FragmentTransaction trans = mActivity.openFragmentTransaction(); - final int tabCount = mTabs.size(); - for (int i = 0; i < tabCount; i++) { - trans.remove(mTabs.get(i).getFragment()); - } - trans.commit(); - } - mTabs.clear(); - } + mTabs.clear(); } public void setTitle(CharSequence title) { @@ -295,31 +261,23 @@ public class ActionBarImpl extends ActionBar { private void configureTab(Tab tab, int position) { final TabImpl tabi = (TabImpl) tab; final boolean isFirstTab = mTabs.isEmpty(); - final FragmentTransaction trans = mActivity.openFragmentTransaction(); - final Fragment frag = tabi.getFragment(); + final ActionBar.TabListener callback = tabi.getCallback(); + + if (callback == null) { + throw new IllegalStateException("Action Bar Tab must have a Callback"); + } tabi.setPosition(position); mTabs.add(position, tabi); - if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { - if (!frag.isAdded()) { - trans.add(mTabContainerViewId, frag); - } - } - if (isFirstTab) { - if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { - trans.show(frag); - } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) { - trans.add(mTabContainerViewId, frag); - } + final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction(); mSelectedTab = tabi; - } else { - if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { - trans.hide(frag); + callback.onTabSelected(tab, trans); + if (!trans.isEmpty()) { + trans.commit(); } } - trans.commit(); } @Override @@ -329,8 +287,8 @@ public class ActionBarImpl extends ActionBar { } @Override - public void insertTab(Tab tab, int position) { - mActionView.insertTab(tab, position); + public void addTab(Tab tab, int position) { + mActionView.addTab(tab, position); configureTab(tab, position); } @@ -367,35 +325,29 @@ public class ActionBarImpl extends ActionBar { } @Override - public void setTabNavigationMode(int containerViewId) { - mTabContainerViewId = containerViewId; - setTabNavigationMode(); - } - - @Override public void selectTab(Tab tab) { if (mSelectedTab == tab) { return; } mActionView.setTabSelected(tab != null ? tab.getPosition() : Tab.INVALID_POSITION); - final FragmentTransaction trans = mActivity.openFragmentTransaction(); + final FragmentTransaction trans = mActivity.getFragmentManager().openTransaction(); if (mSelectedTab != null) { - if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { - trans.hide(mSelectedTab.getFragment()); - } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) { - trans.remove(mSelectedTab.getFragment()); - } - } - if (tab != null) { - if (mTabSwitchMode == TAB_SWITCH_SHOW_HIDE) { - trans.show(tab.getFragment()); - } else if (mTabSwitchMode == TAB_SWITCH_ADD_REMOVE) { - trans.add(mTabContainerViewId, tab.getFragment()); - } + mSelectedTab.getCallback().onTabUnselected(mSelectedTab, trans); } mSelectedTab = (TabImpl) tab; - trans.commit(); + if (mSelectedTab != null) { + mSelectedTab.getCallback().onTabSelected(mSelectedTab, trans); + } + + if (!trans.isEmpty()) { + trans.commit(); + } + } + + @Override + public Tab getSelectedTab() { + return mSelectedTab; } @Override @@ -542,14 +494,40 @@ public class ActionBarImpl extends ActionBar { * @hide */ public class TabImpl extends ActionBar.Tab { - private Fragment mFragment; + private ActionBar.TabListener mCallback; + private Object mTag; private Drawable mIcon; private CharSequence mText; private int mPosition; + private View mCustomView; @Override - public Fragment getFragment() { - return mFragment; + public Object getTag() { + return mTag; + } + + @Override + public void setTag(Object tag) { + mTag = tag; + } + + public ActionBar.TabListener getCallback() { + return mCallback; + } + + @Override + public void setTabListener(ActionBar.TabListener callback) { + mCallback = callback; + } + + @Override + public View getCustomView() { + return mCustomView; + } + + @Override + public void setCustomView(View view) { + mCustomView = view; } @Override @@ -572,11 +550,6 @@ public class ActionBarImpl extends ActionBar { } @Override - public void setFragment(Fragment fragment) { - mFragment = fragment; - } - - @Override public void setIcon(Drawable icon) { mIcon = icon; } diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java index 0a1c8ff..a6a031a 100644 --- a/core/java/com/android/internal/os/BatteryStatsImpl.java +++ b/core/java/com/android/internal/os/BatteryStatsImpl.java @@ -3345,11 +3345,6 @@ public final class BatteryStatsImpl extends BatteryStats { } } - public class Pid { - long mWakeSum; - long mWakeStart; - } - /** * Retrieve the statistics object for a particular process, creating * if needed. @@ -3364,6 +3359,10 @@ public final class BatteryStatsImpl extends BatteryStats { return ps; } + public SparseArray<? extends Pid> getPidStats() { + return mPids; + } + public Pid getPidStatsLocked(int pid) { Pid p = mPids.get(pid); if (p == null) { @@ -3588,6 +3587,11 @@ public final class BatteryStatsImpl extends BatteryStats { } @Override + public long getHistoryBaseTime() { + return mHistoryBaseTime; + } + + @Override public int getStartCount() { return mStartCount; } diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index b9e4e46..6b3d353 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -41,6 +41,7 @@ import android.view.Menu; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; +import android.widget.HorizontalScrollView; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Spinner; @@ -87,6 +88,7 @@ public class ActionBarView extends ViewGroup { private TextView mTitleView; private TextView mSubtitleView; private Spinner mSpinner; + private HorizontalScrollView mTabScrollView; private LinearLayout mTabLayout; private View mCustomNavView; @@ -119,6 +121,8 @@ public class ActionBarView extends ViewGroup { private OnClickListener mHomeClickListener = null; + private OnClickListener mTabClickListener = null; + public ActionBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -346,8 +350,9 @@ public class ActionBarView extends ViewGroup { break; case ActionBar.NAVIGATION_MODE_TABS: if (mTabLayout != null) { - removeView(mTabLayout); + removeView(mTabScrollView); mTabLayout = null; + mTabScrollView = null; } } @@ -365,8 +370,10 @@ public class ActionBarView extends ViewGroup { addView(mCustomNavView); break; case ActionBar.NAVIGATION_MODE_TABS: + mTabScrollView = new HorizontalScrollView(getContext()); mTabLayout = new LinearLayout(getContext()); - addView(mTabLayout); + mTabScrollView.addView(mTabLayout); + addView(mTabScrollView); break; } mNavigationMode = mode; @@ -401,20 +408,24 @@ public class ActionBarView extends ViewGroup { private TabView createTabView(ActionBar.Tab tab) { final TabView tabView = new TabView(getContext(), tab); tabView.setFocusable(true); - tabView.setOnClickListener(new TabClickListener()); + + if (mTabClickListener == null) { + mTabClickListener = new TabClickListener(); + } + tabView.setOnClickListener(mTabClickListener); return tabView; } public void addTab(ActionBar.Tab tab) { final boolean isFirst = mTabLayout.getChildCount() == 0; - final TabView tabView = createTabView(tab); + View tabView = createTabView(tab); mTabLayout.addView(tabView); if (isFirst) { tabView.setSelected(true); } } - public void insertTab(ActionBar.Tab tab, int position) { + public void addTab(ActionBar.Tab tab, int position) { final boolean isFirst = mTabLayout.getChildCount() == 0; final TabView tabView = createTabView(tab); mTabLayout.addView(tabView, position); @@ -595,8 +606,8 @@ public class ActionBarView extends ViewGroup { } break; case ActionBar.NAVIGATION_MODE_TABS: - if (mTabLayout != null) { - mTabLayout.measure( + if (mTabScrollView != null) { + mTabScrollView.measure( MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST), MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); } @@ -663,8 +674,8 @@ public class ActionBarView extends ViewGroup { } break; case ActionBar.NAVIGATION_MODE_TABS: - if (mTabLayout != null) { - x += positionChild(mTabLayout, x, y, contentHeight) + mSpacing; + if (mTabScrollView != null) { + x += positionChild(mTabScrollView, x, y, contentHeight) + mSpacing; } } @@ -703,31 +714,36 @@ public class ActionBarView extends ViewGroup { super(context); mTab = tab; - // TODO Style tabs based on the theme - - final Drawable icon = tab.getIcon(); - final CharSequence text = tab.getText(); - - if (icon != null) { - ImageView iconView = new ImageView(context); - iconView.setImageDrawable(icon); - LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER_VERTICAL; - iconView.setLayoutParams(lp); - addView(iconView); - } + final View custom = tab.getCustomView(); + if (custom != null) { + addView(custom); + } else { + // TODO Style tabs based on the theme + + final Drawable icon = tab.getIcon(); + final CharSequence text = tab.getText(); + + if (icon != null) { + ImageView iconView = new ImageView(context); + iconView.setImageDrawable(icon); + LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + iconView.setLayoutParams(lp); + addView(iconView); + } - if (text != null) { - TextView textView = new TextView(context); - textView.setText(text); - textView.setSingleLine(); - textView.setEllipsize(TruncateAt.END); - LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER_VERTICAL; - textView.setLayoutParams(lp); - addView(textView); + if (text != null) { + TextView textView = new TextView(context); + textView.setText(text); + textView.setSingleLine(); + textView.setEllipsize(TruncateAt.END); + LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER_VERTICAL; + textView.setLayoutParams(lp); + addView(textView); + } } setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, |
