diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2013-12-05 13:10:46 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2013-12-05 13:10:46 -0800 |
commit | ebcb32f58a6220802ca129ea33f47b4b69931a10 (patch) | |
tree | 32b57c1d6ba9180ae63979e06d7421e107a9aa6c /packages | |
parent | 6e2d0c1d91f644ab50e0c0b7cae4306262a4ca41 (diff) | |
parent | bac61807d3bcfff957b358cb9ad77850bd373689 (diff) | |
download | frameworks_base-ebcb32f58a6220802ca129ea33f47b4b69931a10.zip frameworks_base-ebcb32f58a6220802ca129ea33f47b4b69931a10.tar.gz frameworks_base-ebcb32f58a6220802ca129ea33f47b4b69931a10.tar.bz2 |
Merge commit 'bac61807d3bcfff957b358cb9ad77850bd373689' into HEAD
Change-Id: I29374270c8e0c2f2859efaf1d55af9f73da0f8d7
Diffstat (limited to 'packages')
169 files changed, 3449 insertions, 891 deletions
diff --git a/packages/DocumentsUI/AndroidManifest.xml b/packages/DocumentsUI/AndroidManifest.xml index 6faf7f8..179bcd1 100644 --- a/packages/DocumentsUI/AndroidManifest.xml +++ b/packages/DocumentsUI/AndroidManifest.xml @@ -50,6 +50,14 @@ android:authorities="com.android.documentsui.recents" android:exported="false" /> + <receiver android:name=".PackageReceiver"> + <intent-filter> + <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" /> + <action android:name="android.intent.action.PACKAGE_DATA_CLEARED" /> + <data android:scheme="package" /> + </intent-filter> + </receiver> + <!-- TODO: remove when we have real clients --> <activity android:name=".TestActivity" android:enabled="false"> <intent-filter> diff --git a/packages/DocumentsUI/res/layout/fragment_save.xml b/packages/DocumentsUI/res/layout/fragment_save.xml index 891f0a0..d601194 100644 --- a/packages/DocumentsUI/res/layout/fragment_save.xml +++ b/packages/DocumentsUI/res/layout/fragment_save.xml @@ -51,15 +51,31 @@ android:singleLine="true" android:selectAllOnFocus="true" /> - <Button - android:id="@android:id/button1" + <FrameLayout android:layout_width="wrap_content" - android:layout_height="match_parent" - android:background="?android:attr/selectableItemBackground" - android:text="@string/menu_save" - android:textAllCaps="true" - android:textAppearance="?android:attr/textAppearanceSmall" - android:padding="8dp" /> + android:layout_height="match_parent"> + + <Button + android:id="@android:id/button1" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:background="?android:attr/selectableItemBackground" + android:text="@string/menu_save" + android:textAllCaps="true" + android:textAppearance="?android:attr/textAppearanceSmall" + android:padding="8dp" /> + + <ProgressBar + android:id="@android:id/progress" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:visibility="gone" + android:indeterminate="true" + android:padding="8dp" + style="?android:attr/progressBarStyle" /> + + </FrameLayout> </LinearLayout> diff --git a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java index 90be197..ba8c35f 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/CreateDirectoryFragment.java @@ -95,6 +95,11 @@ public class CreateDirectoryFragment extends DialogFragment { } @Override + protected void onPreExecute() { + mActivity.setPending(true); + } + + @Override protected DocumentInfo doInBackground(Void... params) { final ContentResolver resolver = mActivity.getContentResolver(); ContentProviderClient client = null; @@ -120,6 +125,8 @@ public class CreateDirectoryFragment extends DialogFragment { } else { Toast.makeText(mActivity, R.string.create_error, Toast.LENGTH_SHORT).show(); } + + mActivity.setPending(false); } } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java index 1f3901c..b2b2bd8 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java @@ -47,6 +47,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.OperationCanceledException; import android.os.Parcelable; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; @@ -76,6 +77,7 @@ import android.widget.TextView; import android.widget.Toast; import com.android.documentsui.DocumentsActivity.State; +import com.android.documentsui.ProviderExecutor.Preemptable; import com.android.documentsui.RecentsProvider.StateColumns; import com.android.documentsui.model.DocumentInfo; import com.android.documentsui.model.RootInfo; @@ -83,7 +85,6 @@ import com.google.android.collect.Lists; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; /** * Display the documents inside a single directory. @@ -126,9 +127,7 @@ public class DirectoryFragment extends Fragment { private static final String EXTRA_QUERY = "query"; private static final String EXTRA_IGNORE_STATE = "ignoreState"; - private static AtomicInteger sLoaderId = new AtomicInteger(4000); - - private final int mLoaderId = sLoaderId.incrementAndGet(); + private final int mLoaderId = 42; public static void showNormal(FragmentManager fm, RootInfo root, DocumentInfo doc, int anim) { show(fm, TYPE_NORMAL, root, doc, null, anim); @@ -528,7 +527,7 @@ public class DirectoryFragment extends Fragment { if (iconThumb != null) { final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag(); if (oldTask != null) { - oldTask.reallyCancel(); + oldTask.preempt(); iconThumb.setTag(null); } } @@ -794,7 +793,7 @@ public class DirectoryFragment extends Fragment { final ThumbnailAsyncTask oldTask = (ThumbnailAsyncTask) iconThumb.getTag(); if (oldTask != null) { - oldTask.reallyCancel(); + oldTask.preempt(); iconThumb.setTag(null); } @@ -818,7 +817,7 @@ public class DirectoryFragment extends Fragment { final ThumbnailAsyncTask task = new ThumbnailAsyncTask( uri, iconMime, iconThumb, mThumbSize); iconThumb.setTag(task); - task.executeOnExecutor(ProviderExecutor.forAuthority(docAuthority)); + ProviderExecutor.forAuthority(docAuthority).execute(task); } } @@ -988,7 +987,8 @@ public class DirectoryFragment extends Fragment { } } - private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> { + private static class ThumbnailAsyncTask extends AsyncTask<Uri, Void, Bitmap> + implements Preemptable { private final Uri mUri; private final ImageView mIconMime; private final ImageView mIconThumb; @@ -1004,7 +1004,8 @@ public class DirectoryFragment extends Fragment { mSignal = new CancellationSignal(); } - public void reallyCancel() { + @Override + public void preempt() { cancel(false); mSignal.cancel(); } @@ -1028,7 +1029,9 @@ public class DirectoryFragment extends Fragment { thumbs.put(mUri, result); } } catch (Exception e) { - Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e); + if (!(e instanceof OperationCanceledException)) { + Log.w(TAG, "Failed to load thumbnail for " + mUri + ": " + e); + } } finally { ContentProviderClient.releaseQuietly(client); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java index d675e8d..4212e96 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsActivity.java @@ -255,7 +255,9 @@ public class DocumentsActivity extends Activity { } mState.localOnly = intent.getBooleanExtra(Intent.EXTRA_LOCAL_ONLY, false); - mState.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(this); + mState.forceAdvanced = intent.getBooleanExtra(DocumentsContract.EXTRA_SHOW_ADVANCED, false); + mState.showAdvanced = mState.forceAdvanced + | SettingsActivity.getDisplayAdvancedDevices(this); } private class RestoreRootTask extends AsyncTask<Void, Void, RootInfo> { @@ -661,6 +663,13 @@ public class DocumentsActivity extends Activity { DirectoryFragment.get(getFragmentManager()).onUserModeChanged(); } + public void setPending(boolean pending) { + final SaveFragment save = SaveFragment.get(getFragmentManager()); + if (save != null) { + save.setPending(pending); + } + } + @Override public void onBackPressed() { if (!mState.stackTouched) { @@ -1051,6 +1060,11 @@ public class DocumentsActivity extends Activity { } @Override + protected void onPreExecute() { + setPending(true); + } + + @Override protected Uri doInBackground(Void... params) { final ContentResolver resolver = getContentResolver(); final DocumentInfo cwd = getCurrentDirectory(); @@ -1083,6 +1097,8 @@ public class DocumentsActivity extends Activity { Toast.makeText(DocumentsActivity.this, R.string.save_error, Toast.LENGTH_SHORT) .show(); } + + setPending(false); } } @@ -1122,6 +1138,7 @@ public class DocumentsActivity extends Activity { public boolean allowMultiple = false; public boolean showSize = false; public boolean localOnly = false; + public boolean forceAdvanced = false; public boolean showAdvanced = false; public boolean stackTouched = false; public boolean restored = false; @@ -1162,6 +1179,7 @@ public class DocumentsActivity extends Activity { out.writeInt(allowMultiple ? 1 : 0); out.writeInt(showSize ? 1 : 0); out.writeInt(localOnly ? 1 : 0); + out.writeInt(forceAdvanced ? 1 : 0); out.writeInt(showAdvanced ? 1 : 0); out.writeInt(stackTouched ? 1 : 0); out.writeInt(restored ? 1 : 0); @@ -1181,6 +1199,7 @@ public class DocumentsActivity extends Activity { state.allowMultiple = in.readInt() != 0; state.showSize = in.readInt() != 0; state.localOnly = in.readInt() != 0; + state.forceAdvanced = in.readInt() != 0; state.showAdvanced = in.readInt() != 0; state.stackTouched = in.readInt() != 0; state.restored = in.readInt() != 0; diff --git a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java index 6b46e3a..547e343 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java +++ b/packages/DocumentsUI/src/com/android/documentsui/DocumentsApplication.java @@ -75,6 +75,7 @@ public class DocumentsApplication extends Application { packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED); packageFilter.addDataScheme("package"); registerReceiver(mCacheReceiver, packageFilter); diff --git a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java index 52d816f..55d73f2 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/FilteringCursorWrapper.java @@ -17,6 +17,8 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.TAG; +import static com.android.documentsui.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; import android.database.AbstractCursor; import android.database.Cursor; @@ -50,10 +52,8 @@ public class FilteringCursorWrapper extends AbstractCursor { cursor.moveToPosition(-1); while (cursor.moveToNext()) { - final String mimeType = cursor.getString( - cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); - final long lastModified = cursor.getLong( - cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED)); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final long lastModified = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); if (rejectMimes != null && MimePredicate.mimeMatches(rejectMimes, mimeType)) { continue; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java new file mode 100644 index 0000000..aef63af --- /dev/null +++ b/packages/DocumentsUI/src/com/android/documentsui/PackageReceiver.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2013 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.documentsui; + +import android.content.BroadcastReceiver; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; + +/** + * Clean up {@link RecentsProvider} when packages are removed. + */ +public class PackageReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final ContentResolver resolver = context.getContentResolver(); + + final String action = intent.getAction(); + if (Intent.ACTION_PACKAGE_FULLY_REMOVED.equals(action)) { + resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE, null, null); + + } else if (Intent.ACTION_PACKAGE_DATA_CLEARED.equals(action)) { + final Uri data = intent.getData(); + if (data != null) { + final String packageName = data.getSchemeSpecificPart(); + resolver.call(RecentsProvider.buildRecent(), RecentsProvider.METHOD_PURGE_PACKAGE, + packageName, null); + } + } + } +} diff --git a/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java b/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java index 2105cb4..f94aebd 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java +++ b/packages/DocumentsUI/src/com/android/documentsui/ProviderExecutor.java @@ -16,10 +16,15 @@ package com.android.documentsui; +import android.os.AsyncTask; + import com.android.internal.annotations.GuardedBy; import com.android.internal.util.Preconditions; +import com.google.android.collect.Lists; import com.google.android.collect.Maps; +import java.lang.ref.WeakReference; +import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -29,7 +34,7 @@ public class ProviderExecutor extends Thread implements Executor { @GuardedBy("sExecutors") private static HashMap<String, ProviderExecutor> sExecutors = Maps.newHashMap(); - public static Executor forAuthority(String authority) { + public static ProviderExecutor forAuthority(String authority) { synchronized (sExecutors) { ProviderExecutor executor = sExecutors.get(authority); if (executor == null) { @@ -42,10 +47,54 @@ public class ProviderExecutor extends Thread implements Executor { } } + public interface Preemptable { + void preempt(); + } + private final LinkedBlockingQueue<Runnable> mQueue = new LinkedBlockingQueue<Runnable>(); + private final ArrayList<WeakReference<Preemptable>> mPreemptable = Lists.newArrayList(); + + private void preempt() { + synchronized (mPreemptable) { + int count = 0; + for (WeakReference<Preemptable> ref : mPreemptable) { + final Preemptable p = ref.get(); + if (p != null) { + count++; + p.preempt(); + } + } + mPreemptable.clear(); + } + } + + /** + * Execute the given task. If given task is not {@link Preemptable}, it will + * preempt all outstanding preemptable tasks. + */ + public <P> void execute(AsyncTask<P, ?, ?> task, P... params) { + if (task instanceof Preemptable) { + synchronized (mPreemptable) { + mPreemptable.add(new WeakReference<Preemptable>((Preemptable) task)); + } + task.executeOnExecutor(mNonPreemptingExecutor, params); + } else { + task.executeOnExecutor(this, params); + } + } + + private Executor mNonPreemptingExecutor = new Executor() { + @Override + public void execute(Runnable command) { + Preconditions.checkNotNull(command); + mQueue.add(command); + } + }; + @Override public void execute(Runnable command) { + preempt(); Preconditions.checkNotNull(command); mQueue.add(command); } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java index 4313fa7..f6e4349 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RecentsProvider.java @@ -16,24 +16,40 @@ package com.android.documentsui; +import static com.android.documentsui.model.DocumentInfo.getCursorString; + import android.content.ContentProvider; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; +import android.content.Intent; import android.content.UriMatcher; +import android.content.pm.ResolveInfo; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.net.Uri; +import android.os.Bundle; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.text.format.DateUtils; import android.util.Log; +import com.android.documentsui.model.DocumentStack; +import com.android.documentsui.model.DurableUtils; +import com.android.internal.util.Predicate; +import com.google.android.collect.Sets; + +import libcore.io.IoUtils; + +import java.io.IOException; +import java.util.Set; + public class RecentsProvider extends ContentProvider { private static final String TAG = "RecentsProvider"; - public static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS; + private static final long MAX_HISTORY_IN_MILLIS = 45 * DateUtils.DAY_IN_MILLIS; private static final String AUTHORITY = "com.android.documentsui.recents"; @@ -43,6 +59,9 @@ public class RecentsProvider extends ContentProvider { private static final int URI_STATE = 2; private static final int URI_RESUME = 3; + public static final String METHOD_PURGE = "purge"; + public static final String METHOD_PURGE_PACKAGE = "purgePackage"; + static { sMatcher.addURI(AUTHORITY, "recent", URI_RECENT); // state/authority/rootId/docId @@ -231,4 +250,116 @@ public class RecentsProvider extends ContentProvider { public int delete(Uri uri, String selection, String[] selectionArgs) { throw new UnsupportedOperationException("Unsupported Uri " + uri); } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + if (METHOD_PURGE.equals(method)) { + // Purge references to unknown authorities + final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); + final Set<String> knownAuth = Sets.newHashSet(); + for (ResolveInfo info : getContext() + .getPackageManager().queryIntentContentProviders(intent, 0)) { + knownAuth.add(info.providerInfo.authority); + } + + purgeByAuthority(new Predicate<String>() { + @Override + public boolean apply(String authority) { + // Purge unknown authorities + return !knownAuth.contains(authority); + } + }); + + return null; + + } else if (METHOD_PURGE_PACKAGE.equals(method)) { + // Purge references to authorities in given package + final Intent intent = new Intent(DocumentsContract.PROVIDER_INTERFACE); + intent.setPackage(arg); + final Set<String> packageAuth = Sets.newHashSet(); + for (ResolveInfo info : getContext() + .getPackageManager().queryIntentContentProviders(intent, 0)) { + packageAuth.add(info.providerInfo.authority); + } + + if (!packageAuth.isEmpty()) { + purgeByAuthority(new Predicate<String>() { + @Override + public boolean apply(String authority) { + // Purge authority matches + return packageAuth.contains(authority); + } + }); + } + + return null; + + } else { + return super.call(method, arg, extras); + } + } + + /** + * Purge all internal data whose authority matches the given + * {@link Predicate}. + */ + private void purgeByAuthority(Predicate<String> predicate) { + final SQLiteDatabase db = mHelper.getWritableDatabase(); + final DocumentStack stack = new DocumentStack(); + + Cursor cursor = db.query(TABLE_RECENT, null, null, null, null, null, null); + try { + while (cursor.moveToNext()) { + try { + final byte[] rawStack = cursor.getBlob( + cursor.getColumnIndex(RecentColumns.STACK)); + DurableUtils.readFromArray(rawStack, stack); + + if (stack.root != null && predicate.apply(stack.root.authority)) { + final String key = getCursorString(cursor, RecentColumns.KEY); + db.delete(TABLE_RECENT, RecentColumns.KEY + "=?", new String[] { key }); + } + } catch (IOException ignored) { + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + + cursor = db.query(TABLE_STATE, new String[] { + StateColumns.AUTHORITY }, null, null, StateColumns.AUTHORITY, null, null); + try { + while (cursor.moveToNext()) { + final String authority = getCursorString(cursor, StateColumns.AUTHORITY); + if (predicate.apply(authority)) { + db.delete(TABLE_STATE, StateColumns.AUTHORITY + "=?", new String[] { + authority }); + Log.d(TAG, "Purged state for " + authority); + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + + cursor = db.query(TABLE_RESUME, null, null, null, null, null, null); + try { + while (cursor.moveToNext()) { + try { + final byte[] rawStack = cursor.getBlob( + cursor.getColumnIndex(ResumeColumns.STACK)); + DurableUtils.readFromArray(rawStack, stack); + + if (stack.root != null && predicate.apply(stack.root.authority)) { + final String packageName = getCursorString( + cursor, ResumeColumns.PACKAGE_NAME); + db.delete(TABLE_RESUME, ResumeColumns.PACKAGE_NAME + "=?", + new String[] { packageName }); + } + } catch (IOException ignored) { + } + } + } finally { + IoUtils.closeQuietly(cursor); + } + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java index b98e1ee..f6b43c7 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsCache.java @@ -60,8 +60,8 @@ import java.util.concurrent.TimeUnit; public class RootsCache { private static final boolean LOGD = true; - // TODO: cache roots in local provider to avoid spinning up backends - // TODO: root updates should trigger UI refresh + public static final Uri sNotificationUri = Uri.parse( + "content://com.android.documentsui.roots/"); private final Context mContext; private final ContentObserver mObserver; @@ -201,6 +201,7 @@ public class RootsCache { mStoppedAuthorities = mTaskStoppedAuthorities; } mFirstLoad.countDown(); + resolver.notifyChange(sNotificationUri, null, false); return null; } diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java index fdbc3ab..931dac9 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsFragment.java @@ -16,6 +16,8 @@ package com.android.documentsui; +import static com.android.documentsui.DocumentsActivity.State.ACTION_GET_CONTENT; + import android.app.Fragment; import android.app.FragmentManager; import android.app.FragmentTransaction; @@ -25,7 +27,9 @@ import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.Bundle; +import android.provider.Settings; import android.text.TextUtils; import android.text.format.Formatter; import android.view.LayoutInflater; @@ -33,6 +37,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; +import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; @@ -131,7 +136,15 @@ public class RootsFragment extends Fragment { final Context context = getActivity(); final State state = ((DocumentsActivity) context).getDisplayState(); - state.showAdvanced = SettingsActivity.getDisplayAdvancedDevices(context); + state.showAdvanced = state.forceAdvanced + | SettingsActivity.getDisplayAdvancedDevices(context); + + if (state.action == ACTION_GET_CONTENT) { + mList.setOnItemLongClickListener(mItemLongClickListener); + } else { + mList.setOnItemLongClickListener(null); + mList.setLongClickable(false); + } getLoaderManager().restartLoader(2, null, mCallbacks); } @@ -152,6 +165,13 @@ public class RootsFragment extends Fragment { } } + private void showAppDetails(ResolveInfo ri) { + final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.fromParts("package", ri.activityInfo.packageName, null)); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + startActivity(intent); + } + private OnItemClickListener mItemListener = new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { @@ -167,6 +187,19 @@ public class RootsFragment extends Fragment { } }; + private OnItemLongClickListener mItemLongClickListener = new OnItemLongClickListener() { + @Override + public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { + final Item item = mAdapter.getItem(position); + if (item instanceof AppItem) { + showAppDetails(((AppItem) item).info); + return true; + } else { + return false; + } + } + }; + private static abstract class Item { private final int mLayoutId; diff --git a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java index 7108971..8d37cdf 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java +++ b/packages/DocumentsUI/src/com/android/documentsui/RootsLoader.java @@ -25,6 +25,8 @@ import com.android.documentsui.model.RootInfo; import java.util.Collection; public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> { + private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver(); + private final RootsCache mRoots; private final State mState; @@ -34,6 +36,9 @@ public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> { super(context); mRoots = roots; mState = state; + + getContext().getContentResolver() + .registerContentObserver(RootsCache.sNotificationUri, false, mObserver); } @Override @@ -77,5 +82,7 @@ public class RootsLoader extends AsyncTaskLoader<Collection<RootInfo>> { onStopLoading(); mResult = null; + + getContext().getContentResolver().unregisterContentObserver(mObserver); } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java index 23e047c..9d70c51 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SaveFragment.java @@ -30,6 +30,7 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.EditText; import android.widget.ImageView; +import android.widget.ProgressBar; import com.android.documentsui.model.DocumentInfo; @@ -42,6 +43,7 @@ public class SaveFragment extends Fragment { private DocumentInfo mReplaceTarget; private EditText mDisplayName; private Button mSave; + private ProgressBar mProgress; private boolean mIgnoreNextEdit; private static final String EXTRA_MIME_TYPE = "mime_type"; @@ -83,6 +85,8 @@ public class SaveFragment extends Fragment { mSave.setOnClickListener(mSaveListener); mSave.setEnabled(false); + mProgress = (ProgressBar) view.findViewById(android.R.id.progress); + return view; } @@ -92,7 +96,6 @@ public class SaveFragment extends Fragment { if (mIgnoreNextEdit) { mIgnoreNextEdit = false; } else { - Log.d(TAG, "onTextChanged!"); mReplaceTarget = null; } } @@ -140,4 +143,9 @@ public class SaveFragment extends Fragment { public void setSaveEnabled(boolean enabled) { mSave.setEnabled(enabled); } + + public void setPending(boolean pending) { + mSave.setVisibility(pending ? View.INVISIBLE : View.VISIBLE); + mProgress.setVisibility(pending ? View.VISIBLE : View.GONE); + } } diff --git a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java index 19ad2e2..a23dd15 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java +++ b/packages/DocumentsUI/src/com/android/documentsui/SortingCursorWrapper.java @@ -19,6 +19,8 @@ package com.android.documentsui; import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_DISPLAY_NAME; import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_LAST_MODIFIED; import static com.android.documentsui.DocumentsActivity.State.SORT_ORDER_SIZE; +import static com.android.documentsui.model.DocumentInfo.getCursorLong; +import static com.android.documentsui.model.DocumentInfo.getCursorString; import android.database.AbstractCursor; import android.database.Cursor; @@ -62,10 +64,9 @@ public class SortingCursorWrapper extends AbstractCursor { switch (sortOrder) { case SORT_ORDER_DISPLAY_NAME: - final String mimeType = cursor.getString( - cursor.getColumnIndex(Document.COLUMN_MIME_TYPE)); - final String displayName = cursor.getString( - cursor.getColumnIndex(Document.COLUMN_DISPLAY_NAME)); + final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); + final String displayName = getCursorString( + cursor, Document.COLUMN_DISPLAY_NAME); if (Document.MIME_TYPE_DIR.equals(mimeType)) { mValueString[i] = '\001' + displayName; } else { @@ -73,11 +74,10 @@ public class SortingCursorWrapper extends AbstractCursor { } break; case SORT_ORDER_LAST_MODIFIED: - mValueLong[i] = cursor.getLong( - cursor.getColumnIndex(Document.COLUMN_LAST_MODIFIED)); + mValueLong[i] = getCursorLong(cursor, Document.COLUMN_LAST_MODIFIED); break; case SORT_ORDER_SIZE: - mValueLong[i] = cursor.getLong(cursor.getColumnIndex(Document.COLUMN_SIZE)); + mValueLong[i] = getCursorLong(cursor, Document.COLUMN_SIZE); break; } } diff --git a/packages/ExternalStorageProvider/AndroidManifest.xml b/packages/ExternalStorageProvider/AndroidManifest.xml index 99a4260..edd6255 100644 --- a/packages/ExternalStorageProvider/AndroidManifest.xml +++ b/packages/ExternalStorageProvider/AndroidManifest.xml @@ -3,6 +3,7 @@ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <application android:label="@string/app_label"> <provider @@ -16,6 +17,14 @@ </intent-filter> </provider> + <receiver android:name=".MountReceiver"> + <intent-filter> + <action android:name="android.intent.action.MEDIA_MOUNTED" /> + <action android:name="android.intent.action.MEDIA_UNMOUNTED" /> + <data android:scheme="file" /> + </intent-filter> + </receiver> + <!-- TODO: find a better place for tests to live --> <provider android:name=".TestDocumentsProvider" diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java index 11ff2d8..559e052 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/ExternalStorageProvider.java @@ -16,21 +16,28 @@ package com.android.externalstorage; +import android.content.ContentResolver; +import android.content.Context; import android.content.res.AssetFileDescriptor; import android.database.Cursor; import android.database.MatrixCursor; import android.database.MatrixCursor.RowBuilder; import android.graphics.Point; -import android.media.ExifInterface; +import android.net.Uri; import android.os.CancellationSignal; import android.os.Environment; +import android.os.FileObserver; import android.os.ParcelFileDescriptor; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; -import android.provider.DocumentsContract; import android.provider.DocumentsProvider; +import android.util.Log; import android.webkit.MimeTypeMap; +import com.android.internal.annotations.GuardedBy; import com.google.android.collect.Lists; import com.google.android.collect.Maps; @@ -45,6 +52,10 @@ import java.util.Map; public class ExternalStorageProvider extends DocumentsProvider { private static final String TAG = "ExternalStorage"; + private static final boolean LOG_INOTIFY = false; + + public static final String AUTHORITY = "com.android.externalstorage.documents"; + // docId format: root:path/to/file private static final String[] DEFAULT_ROOT_PROJECTION = new String[] { @@ -64,42 +75,94 @@ public class ExternalStorageProvider extends DocumentsProvider { public String docId; } + private static final String ROOT_ID_PRIMARY_EMULATED = "primary"; + + private StorageManager mStorageManager; + + private final Object mRootsLock = new Object(); + + @GuardedBy("mRootsLock") private ArrayList<RootInfo> mRoots; + @GuardedBy("mRootsLock") private HashMap<String, RootInfo> mIdToRoot; + @GuardedBy("mRootsLock") private HashMap<String, File> mIdToPath; + @GuardedBy("mObservers") + private Map<File, DirectoryObserver> mObservers = Maps.newHashMap(); + @Override public boolean onCreate() { + mStorageManager = (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE); + mRoots = Lists.newArrayList(); mIdToRoot = Maps.newHashMap(); mIdToPath = Maps.newHashMap(); - // TODO: support multiple storage devices, requiring that volume serial - // number be burned into rootId so we can identify files from different - // volumes. currently we only use a static rootId for emulated storage, - // since that storage never changes. - if (!Environment.isExternalStorageEmulated()) return true; - - try { - final String rootId = "primary"; - final File path = Environment.getExternalStorageDirectory(); - mIdToPath.put(rootId, path); - - final RootInfo root = new RootInfo(); - root.rootId = rootId; - root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED - | Root.FLAG_SUPPORTS_SEARCH; - root.title = getContext().getString(R.string.root_internal_storage); - root.docId = getDocIdForFile(path); - mRoots.add(root); - mIdToRoot.put(rootId, root); - } catch (FileNotFoundException e) { - throw new IllegalStateException(e); - } + updateVolumes(); return true; } + public void updateVolumes() { + synchronized (mRootsLock) { + updateVolumesLocked(); + } + } + + private void updateVolumesLocked() { + mRoots.clear(); + mIdToPath.clear(); + mIdToRoot.clear(); + + final StorageVolume[] volumes = mStorageManager.getVolumeList(); + for (StorageVolume volume : volumes) { + final boolean mounted = Environment.MEDIA_MOUNTED.equals(volume.getState()) + || Environment.MEDIA_MOUNTED_READ_ONLY.equals(volume.getState()); + if (!mounted) continue; + + final String rootId; + if (volume.isPrimary() && volume.isEmulated()) { + rootId = ROOT_ID_PRIMARY_EMULATED; + } else if (volume.getUuid() != null) { + rootId = volume.getUuid(); + } else { + Log.d(TAG, "Missing UUID for " + volume.getPath() + "; skipping"); + continue; + } + + if (mIdToPath.containsKey(rootId)) { + Log.w(TAG, "Duplicate UUID " + rootId + "; skipping"); + continue; + } + + try { + final File path = volume.getPathFile(); + mIdToPath.put(rootId, path); + + final RootInfo root = new RootInfo(); + root.rootId = rootId; + root.flags = Root.FLAG_SUPPORTS_CREATE | Root.FLAG_LOCAL_ONLY | Root.FLAG_ADVANCED + | Root.FLAG_SUPPORTS_SEARCH; + if (ROOT_ID_PRIMARY_EMULATED.equals(rootId)) { + root.title = getContext().getString(R.string.root_internal_storage); + } else { + root.title = volume.getUserLabel(); + } + root.docId = getDocIdForFile(path); + mRoots.add(root); + mIdToRoot.put(rootId, root); + } catch (FileNotFoundException e) { + throw new IllegalStateException(e); + } + } + + Log.d(TAG, "After updating volumes, found " + mRoots.size() + " active roots"); + + getContext().getContentResolver() + .notifyChange(DocumentsContract.buildRootsUri(AUTHORITY), null, false); + } + private static String[] resolveRootProjection(String[] projection) { return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } @@ -113,11 +176,13 @@ public class ExternalStorageProvider extends DocumentsProvider { // Find the most-specific root path Map.Entry<String, File> mostSpecific = null; - for (Map.Entry<String, File> root : mIdToPath.entrySet()) { - final String rootPath = root.getValue().getPath(); - if (path.startsWith(rootPath) && (mostSpecific == null - || rootPath.length() > mostSpecific.getValue().getPath().length())) { - mostSpecific = root; + synchronized (mRootsLock) { + for (Map.Entry<String, File> root : mIdToPath.entrySet()) { + final String rootPath = root.getValue().getPath(); + if (path.startsWith(rootPath) && (mostSpecific == null + || rootPath.length() > mostSpecific.getValue().getPath().length())) { + mostSpecific = root; + } } } @@ -143,7 +208,10 @@ public class ExternalStorageProvider extends DocumentsProvider { final String tag = docId.substring(0, splitIndex); final String path = docId.substring(splitIndex + 1); - File target = mIdToPath.get(tag); + File target; + synchronized (mRootsLock) { + target = mIdToPath.get(tag); + } if (target == null) { throw new FileNotFoundException("No root for " + tag); } @@ -199,16 +267,18 @@ public class ExternalStorageProvider extends DocumentsProvider { @Override public Cursor queryRoots(String[] projection) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); - for (String rootId : mIdToPath.keySet()) { - final RootInfo root = mIdToRoot.get(rootId); - final File path = mIdToPath.get(rootId); - - final RowBuilder row = result.newRow(); - row.add(Root.COLUMN_ROOT_ID, root.rootId); - row.add(Root.COLUMN_FLAGS, root.flags); - row.add(Root.COLUMN_TITLE, root.title); - row.add(Root.COLUMN_DOCUMENT_ID, root.docId); - row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace()); + synchronized (mRootsLock) { + for (String rootId : mIdToPath.keySet()) { + final RootInfo root = mIdToRoot.get(rootId); + final File path = mIdToPath.get(rootId); + + final RowBuilder row = result.newRow(); + row.add(Root.COLUMN_ROOT_ID, root.rootId); + row.add(Root.COLUMN_FLAGS, root.flags); + row.add(Root.COLUMN_TITLE, root.title); + row.add(Root.COLUMN_DOCUMENT_ID, root.docId); + row.add(Root.COLUMN_AVAILABLE_BYTES, path.getFreeSpace()); + } } return result; } @@ -265,8 +335,9 @@ public class ExternalStorageProvider extends DocumentsProvider { public Cursor queryChildDocuments( String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { - final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); final File parent = getFileForDocId(parentDocumentId); + final MatrixCursor result = new DirectoryCursor( + resolveDocumentProjection(projection), parentDocumentId, parent); for (File file : parent.listFiles()) { includeFile(result, null, file); } @@ -277,7 +348,11 @@ public class ExternalStorageProvider extends DocumentsProvider { public Cursor querySearchDocuments(String rootId, String query, String[] projection) throws FileNotFoundException { final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); - final File parent = mIdToPath.get(rootId); + + final File parent; + synchronized (mRootsLock) { + parent = mIdToPath.get(rootId); + } final LinkedList<File> pending = new LinkedList<File>(); pending.add(parent); @@ -328,7 +403,7 @@ public class ExternalStorageProvider extends DocumentsProvider { private static String getTypeForName(String name) { final int lastDot = name.lastIndexOf('.'); if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1); + final String extension = name.substring(lastDot + 1).toLowerCase(); final String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (mime != null) { return mime; @@ -345,7 +420,7 @@ public class ExternalStorageProvider extends DocumentsProvider { private static String removeExtension(String mimeType, String name) { final int lastDot = name.lastIndexOf('.'); if (lastDot >= 0) { - final String extension = name.substring(lastDot + 1); + final String extension = name.substring(lastDot + 1).toLowerCase(); final String nameMime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); if (mimeType.equals(nameMime)) { return name.substring(0, lastDot); @@ -365,4 +440,86 @@ public class ExternalStorageProvider extends DocumentsProvider { } return name; } + + private void startObserving(File file, Uri notifyUri) { + synchronized (mObservers) { + DirectoryObserver observer = mObservers.get(file); + if (observer == null) { + observer = new DirectoryObserver( + file, getContext().getContentResolver(), notifyUri); + observer.startWatching(); + mObservers.put(file, observer); + } + observer.mRefCount++; + + if (LOG_INOTIFY) Log.d(TAG, "after start: " + observer); + } + } + + private void stopObserving(File file) { + synchronized (mObservers) { + DirectoryObserver observer = mObservers.get(file); + if (observer == null) return; + + observer.mRefCount--; + if (observer.mRefCount == 0) { + mObservers.remove(file); + observer.stopWatching(); + } + + if (LOG_INOTIFY) Log.d(TAG, "after stop: " + observer); + } + } + + private static class DirectoryObserver extends FileObserver { + private static final int NOTIFY_EVENTS = ATTRIB | CLOSE_WRITE | MOVED_FROM | MOVED_TO + | CREATE | DELETE | DELETE_SELF | MOVE_SELF; + + private final File mFile; + private final ContentResolver mResolver; + private final Uri mNotifyUri; + + private int mRefCount = 0; + + public DirectoryObserver(File file, ContentResolver resolver, Uri notifyUri) { + super(file.getAbsolutePath(), NOTIFY_EVENTS); + mFile = file; + mResolver = resolver; + mNotifyUri = notifyUri; + } + + @Override + public void onEvent(int event, String path) { + if ((event & NOTIFY_EVENTS) != 0) { + if (LOG_INOTIFY) Log.d(TAG, "onEvent() " + event + " at " + path); + mResolver.notifyChange(mNotifyUri, null, false); + } + } + + @Override + public String toString() { + return "DirectoryObserver{file=" + mFile.getAbsolutePath() + ", ref=" + mRefCount + "}"; + } + } + + private class DirectoryCursor extends MatrixCursor { + private final File mFile; + + public DirectoryCursor(String[] columnNames, String docId, File file) { + super(columnNames); + + final Uri notifyUri = DocumentsContract.buildChildDocumentsUri( + AUTHORITY, docId); + setNotificationUri(getContext().getContentResolver(), notifyUri); + + mFile = file; + startObserving(mFile, notifyUri); + } + + @Override + public void close() { + super.close(); + stopObserving(mFile); + } + } } diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java new file mode 100644 index 0000000..8a6c7d6 --- /dev/null +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/MountReceiver.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2013 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.externalstorage; + +import android.content.BroadcastReceiver; +import android.content.ContentProviderClient; +import android.content.Context; +import android.content.Intent; + +public class MountReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + final ContentProviderClient client = context.getContentResolver() + .acquireContentProviderClient(ExternalStorageProvider.AUTHORITY); + try { + ((ExternalStorageProvider) client.getLocalContentProvider()).updateVolumes(); + } finally { + ContentProviderClient.releaseQuietly(client); + } + } +} diff --git a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java index 0caddcc..8eb70e9 100644 --- a/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java +++ b/packages/ExternalStorageProvider/src/com/android/externalstorage/TestDocumentsProvider.java @@ -33,6 +33,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.CancellationSignal; +import android.os.CancellationSignal.OnCancelListener; import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.provider.DocumentsContract; @@ -54,8 +55,12 @@ import java.lang.ref.WeakReference; public class TestDocumentsProvider extends DocumentsProvider { private static final String TAG = "TestDocuments"; + private static final boolean LAG = false; + + private static final boolean ROOT_LAME_PROJECTION = false; + private static final boolean DOCUMENT_LAME_PROJECTION = false; + private static final boolean ROOTS_WEDGE = false; - private static final boolean ROOTS_LAG = false; private static final boolean ROOTS_CRASH = false; private static final boolean ROOTS_REFRESH = false; @@ -86,10 +91,12 @@ public class TestDocumentsProvider extends DocumentsProvider { }; private static String[] resolveRootProjection(String[] projection) { + if (ROOT_LAME_PROJECTION) return new String[0]; return projection != null ? projection : DEFAULT_ROOT_PROJECTION; } private static String[] resolveDocumentProjection(String[] projection) { + if (DOCUMENT_LAME_PROJECTION) return new String[0]; return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION; } @@ -105,8 +112,8 @@ public class TestDocumentsProvider extends DocumentsProvider { public Cursor queryRoots(String[] projection) throws FileNotFoundException { Log.d(TAG, "Someone asked for our roots!"); - if (ROOTS_WEDGE) SystemClock.sleep(Integer.MAX_VALUE); - if (ROOTS_LAG) SystemClock.sleep(3000); + if (LAG) lagUntilCanceled(null); + if (ROOTS_WEDGE) wedgeUntilCanceled(null); if (ROOTS_CRASH) System.exit(12); if (ROOTS_REFRESH) { @@ -125,7 +132,7 @@ public class TestDocumentsProvider extends DocumentsProvider { final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection)); final RowBuilder row = result.newRow(); row.add(Root.COLUMN_ROOT_ID, MY_ROOT_ID); - row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS); + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_RECENTS | Root.FLAG_SUPPORTS_CREATE); row.add(Root.COLUMN_TITLE, "_Test title which is really long"); row.add(Root.COLUMN_SUMMARY, SystemClock.elapsedRealtime() + " summary which is also super long text"); @@ -137,6 +144,7 @@ public class TestDocumentsProvider extends DocumentsProvider { @Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { + if (LAG) lagUntilCanceled(null); if (DOCUMENT_CRASH) System.exit(12); final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection)); @@ -144,6 +152,14 @@ public class TestDocumentsProvider extends DocumentsProvider { return result; } + @Override + public String createDocument(String parentDocumentId, String mimeType, String displayName) + throws FileNotFoundException { + if (LAG) lagUntilCanceled(null); + + return super.createDocument(parentDocumentId, mimeType, displayName); + } + /** * Holds any outstanding or finished "network" fetching. */ @@ -209,6 +225,7 @@ public class TestDocumentsProvider extends DocumentsProvider { String parentDocumentId, String[] projection, String sortOrder) throws FileNotFoundException { + if (LAG) lagUntilCanceled(null); if (CHILD_WEDGE) SystemClock.sleep(Integer.MAX_VALUE); if (CHILD_CRASH) System.exit(12); @@ -228,7 +245,7 @@ public class TestDocumentsProvider extends DocumentsProvider { if (THUMB_HUNDREDS) { for (int i = 0; i < 256; i++) { - includeFile(result, "i maded u an picshure", Document.FLAG_SUPPORTS_THUMBNAIL); + includeFile(result, "i maded u an picshure" + i, Document.FLAG_SUPPORTS_THUMBNAIL); } } @@ -278,7 +295,8 @@ public class TestDocumentsProvider extends DocumentsProvider { public Cursor queryRecentDocuments(String rootId, String[] projection) throws FileNotFoundException { - if (RECENT_WEDGE) SystemClock.sleep(Integer.MAX_VALUE); + if (LAG) lagUntilCanceled(null); + if (RECENT_WEDGE) wedgeUntilCanceled(null); // Pretend to take a super long time to respond SystemClock.sleep(3000); @@ -292,6 +310,7 @@ public class TestDocumentsProvider extends DocumentsProvider { @Override public ParcelFileDescriptor openDocument(String docId, String mode, CancellationSignal signal) throws FileNotFoundException { + if (LAG) lagUntilCanceled(null); throw new FileNotFoundException(); } @@ -299,6 +318,7 @@ public class TestDocumentsProvider extends DocumentsProvider { public AssetFileDescriptor openDocumentThumbnail( String docId, Point sizeHint, CancellationSignal signal) throws FileNotFoundException { + if (LAG) lagUntilCanceled(signal); if (THUMB_WEDGE) wedgeUntilCanceled(signal); if (THUMB_CRASH) System.exit(12); @@ -339,15 +359,34 @@ public class TestDocumentsProvider extends DocumentsProvider { return true; } + private static void lagUntilCanceled(CancellationSignal signal) { + waitForCancelOrTimeout(signal, 1500); + } + private static void wedgeUntilCanceled(CancellationSignal signal) { + waitForCancelOrTimeout(signal, Integer.MAX_VALUE); + } + + private static void waitForCancelOrTimeout( + final CancellationSignal signal, long timeoutMillis) { if (signal != null) { - while (true) { - signal.throwIfCanceled(); - SystemClock.sleep(500); - } - } else { - Log.w(TAG, "WEDGING WITHOUT A CANCELLATIONSIGNAL"); - SystemClock.sleep(Integer.MAX_VALUE); + final Thread blocked = Thread.currentThread(); + signal.setOnCancelListener(new OnCancelListener() { + @Override + public void onCancel() { + blocked.interrupt(); + } + }); + signal.throwIfCanceled(); + } + + try { + Thread.sleep(timeoutMillis); + } catch (InterruptedException e) { + } + + if (signal != null) { + signal.throwIfCanceled(); } } @@ -360,6 +399,7 @@ public class TestDocumentsProvider extends DocumentsProvider { if (MY_DOC_ID.equals(docId)) { row.add(Document.COLUMN_MIME_TYPE, Document.MIME_TYPE_DIR); + row.add(Document.COLUMN_FLAGS, Document.FLAG_DIR_SUPPORTS_CREATE); } else if (MY_DOC_NULL.equals(docId)) { // No MIME type } else { diff --git a/packages/Keyguard/AndroidManifest.xml b/packages/Keyguard/AndroidManifest.xml index 9e296e2..66d1e75 100644 --- a/packages/Keyguard/AndroidManifest.xml +++ b/packages/Keyguard/AndroidManifest.xml @@ -38,6 +38,7 @@ <uses-permission android:name="android.permission.BIND_DEVICE_ADMIN" /> <uses-permission android:name="android.permission.CHANGE_COMPONENT_ENABLED_STATE" /> <uses-permission android:name="android.permission.MEDIA_CONTENT_CONTROL" /> + <uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" /> <application android:label="@string/app_name" android:process="com.android.systemui" diff --git a/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_forgotpassword_normal.png b/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_forgotpassword_normal.png Binary files differdeleted file mode 100644 index 6402d3d..0000000 --- a/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_forgotpassword_normal.png +++ /dev/null diff --git a/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_forgotpassword_pressed.png b/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_forgotpassword_pressed.png Binary files differdeleted file mode 100644 index 83be046..0000000 --- a/packages/Keyguard/res/drawable-hdpi/ic_lockscreen_forgotpassword_pressed.png +++ /dev/null diff --git a/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_forgotpassword_normal.png b/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_forgotpassword_normal.png Binary files differdeleted file mode 100644 index a7e063a..0000000 --- a/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_forgotpassword_normal.png +++ /dev/null diff --git a/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_forgotpassword_pressed.png b/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_forgotpassword_pressed.png Binary files differdeleted file mode 100644 index 53af5a5..0000000 --- a/packages/Keyguard/res/drawable-mdpi/ic_lockscreen_forgotpassword_pressed.png +++ /dev/null diff --git a/packages/Keyguard/res/drawable-xhdpi/ic_lockscreen_forgotpassword_normal.png b/packages/Keyguard/res/drawable-xhdpi/ic_lockscreen_forgotpassword_normal.png Binary files differdeleted file mode 100644 index e4172ce..0000000 --- a/packages/Keyguard/res/drawable-xhdpi/ic_lockscreen_forgotpassword_normal.png +++ /dev/null diff --git a/packages/Keyguard/res/drawable-xhdpi/ic_lockscreen_forgotpassword_pressed.png b/packages/Keyguard/res/drawable-xhdpi/ic_lockscreen_forgotpassword_pressed.png Binary files differdeleted file mode 100644 index e2c7621..0000000 --- a/packages/Keyguard/res/drawable-xhdpi/ic_lockscreen_forgotpassword_pressed.png +++ /dev/null diff --git a/packages/Keyguard/res/layout-land/keyguard_host_view.xml b/packages/Keyguard/res/layout-land/keyguard_host_view.xml index eeb9ee7..9f1c1f0 100644 --- a/packages/Keyguard/res/layout-land/keyguard_host_view.xml +++ b/packages/Keyguard/res/layout-land/keyguard_host_view.xml @@ -60,6 +60,8 @@ android:id="@+id/keyguard_security_container" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" androidprv:layout_childType="challenge" androidprv:layout_centerWithinArea="0.55"> <com.android.keyguard.KeyguardSecurityViewFlipper diff --git a/packages/Keyguard/res/layout-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-port/keyguard_host_view.xml index 8498dcf..136b296 100644 --- a/packages/Keyguard/res/layout-port/keyguard_host_view.xml +++ b/packages/Keyguard/res/layout-port/keyguard_host_view.xml @@ -31,7 +31,8 @@ <com.android.keyguard.SlidingChallengeLayout android:id="@+id/sliding_layout" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:clipChildren="false"> <FrameLayout android:layout_width="match_parent" @@ -64,6 +65,8 @@ android:id="@+id/keyguard_security_container" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" androidprv:layout_maxHeight="@dimen/keyguard_security_height" androidprv:layout_childType="challenge" android:padding="0dp" diff --git a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml index 77bc9b5..85f6b6d 100644 --- a/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml +++ b/packages/Keyguard/res/layout-sw600dp-port/keyguard_host_view.xml @@ -61,6 +61,8 @@ android:id="@+id/keyguard_security_container" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:clipChildren="false" + android:clipToPadding="false" androidprv:layout_centerWithinArea="0.5" androidprv:layout_childType="challenge" android:layout_gravity="center_horizontal|bottom"> diff --git a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml index 313fe9f..b4847f0 100644 --- a/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml +++ b/packages/Keyguard/res/layout/keyguard_emergency_carrier_area.xml @@ -32,17 +32,15 @@ android:id="@+id/carrier_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:singleLine="true" android:ellipsize="marquee" android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="@dimen/kg_status_line_font_size" - android:textColor="?android:attr/textColorSecondary" - android:textAllCaps="@bool/kg_use_all_caps" /> + android:textColor="?android:attr/textColorSecondary" /> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="-10dip" + android:layout_marginTop="@dimen/eca_overlap" style="?android:attr/buttonBarStyle" android:orientation="horizontal" android:gravity="center" @@ -66,12 +64,10 @@ android:layout_width="0dip" android:layout_height="wrap_content" android:layout_weight="1" - android:drawableLeft="@drawable/lockscreen_forgot_password_button" style="?android:attr/buttonBarButtonStyle" android:textSize="@dimen/kg_status_line_font_size" android:textColor="?android:attr/textColorSecondary" android:textAppearance="?android:attr/textAppearanceMedium" - android:drawablePadding="8dip" android:visibility="gone" android:textAllCaps="@bool/kg_use_all_caps" /> </LinearLayout> diff --git a/packages/Keyguard/res/layout/keyguard_presentation.xml b/packages/Keyguard/res/layout/keyguard_presentation.xml new file mode 100644 index 0000000..7df0b70 --- /dev/null +++ b/packages/Keyguard/res/layout/keyguard_presentation.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +** +** Copyright 2013, 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. +*/ +--> + +<!-- This is a view that shows general status information in Keyguard. --> +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/res/com.android.keyguard" + android:id="@+id/presentation" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.android.keyguard.KeyguardStatusView + android:id="@+id/clock" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/keyguard_accessibility_status"> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:orientation="vertical" + android:focusable="true"> + <TextClock + android:id="@+id/clock_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center_horizontal|top" + android:textColor="@color/clock_white" + android:singleLine="true" + style="@style/widget_big_thin" + android:format12Hour="@string/keyguard_widget_12_hours_format" + android:format24Hour="@string/keyguard_widget_24_hours_format" + android:baselineAligned="true" + android:layout_marginBottom="@dimen/bottom_text_spacing_digital" /> + + <include layout="@layout/keyguard_status_area" /> + <ImageView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="10dip" + android:layout_gravity="center_horizontal" + android:src="@drawable/kg_security_lock_normal" /> + </LinearLayout> + </com.android.keyguard.KeyguardStatusView> + +</FrameLayout> diff --git a/packages/Keyguard/res/layout/keyguard_status_view.xml b/packages/Keyguard/res/layout/keyguard_status_view.xml index 5857fc2..a4d298a 100644 --- a/packages/Keyguard/res/layout/keyguard_status_view.xml +++ b/packages/Keyguard/res/layout/keyguard_status_view.xml @@ -26,7 +26,7 @@ android:layout_height="match_parent" androidprv:layout_maxWidth="@dimen/keyguard_security_width" androidprv:layout_maxHeight="@dimen/keyguard_security_height" - android:gravity="center_horizontal"> + android:gravity="center"> <com.android.keyguard.KeyguardStatusView android:id="@+id/keyguard_status_view_face_palm" diff --git a/packages/Keyguard/res/values-sw540dp-port/dimens.xml b/packages/Keyguard/res/values-sw540dp-port/dimens.xml new file mode 100644 index 0000000..de3106f --- /dev/null +++ b/packages/Keyguard/res/values-sw540dp-port/dimens.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* //device/apps/common/assets/res/any/dimens.xml +** +** Copyright 2013, 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. +*/ +--> +<resources> + <!-- Height of the sliding KeyguardSecurityContainer + (includes 2x keyguard_security_view_margin) --> + <dimen name="keyguard_security_height">500dp</dimen> +</resources> diff --git a/packages/Keyguard/res/values-sw600dp/dimens.xml b/packages/Keyguard/res/values-sw600dp/dimens.xml index f8a1362..ea5ef27 100644 --- a/packages/Keyguard/res/values-sw600dp/dimens.xml +++ b/packages/Keyguard/res/values-sw600dp/dimens.xml @@ -66,4 +66,7 @@ <dimen name="widget_label_font_size">16dp</dimen> <dimen name="widget_big_font_size">141dp</dimen> + <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. + Should be 0 on devices with plenty of room (e.g. tablets) --> + <dimen name="eca_overlap">0dip</dimen> </resources> diff --git a/packages/Keyguard/res/values/dimens.xml b/packages/Keyguard/res/values/dimens.xml index a1ad120..71e9924 100644 --- a/packages/Keyguard/res/values/dimens.xml +++ b/packages/Keyguard/res/values/dimens.xml @@ -150,6 +150,10 @@ security mode. --> <dimen name="kg_small_widget_height">160dp</dimen> + <!-- EmergencyCarrierArea overlap - amount to overlap the emergency button and carrier text. + Should be 0 on devices with plenty of room (e.g. tablets) --> + <dimen name="eca_overlap">-10dip</dimen> + <!-- Default clock parameters --> <dimen name="bottom_text_spacing_digital">-8dp</dimen> <dimen name="label_font_size">14dp</dimen> diff --git a/packages/Keyguard/res/values/strings.xml b/packages/Keyguard/res/values/strings.xml index abc4483..4738049 100644 --- a/packages/Keyguard/res/values/strings.xml +++ b/packages/Keyguard/res/values/strings.xml @@ -24,12 +24,12 @@ <!-- Instructions telling the user to enter their SIM PUK to unlock the keyguard. Displayed in one line in a large font. --> - <string name="keyguard_password_enter_puk_code">Type PUK and new PIN code</string> + <string name="keyguard_password_enter_puk_code">Type SIM PUK and new PIN code</string> <!-- Prompt to enter SIM PUK in Edit Text Box in unlock screen --> - <string name="keyguard_password_enter_puk_prompt">PUK code</string> + <string name="keyguard_password_enter_puk_prompt">SIM PUK code</string> <!-- Prompt to enter New SIM PIN in Edit Text Box in unlock screen --> - <string name="keyguard_password_enter_pin_prompt">New PIN code</string> + <string name="keyguard_password_enter_pin_prompt">New SIM PIN code</string> <!-- Displayed as hint in passwordEntry EditText on PasswordUnlockScreen [CHAR LIMIT=30]--> <string name="keyguard_password_entry_touch_hint"><font size="17">Touch to type password</font></string> @@ -249,8 +249,6 @@ <string name="kg_enter_confirm_pin_hint">Confirm desired PIN code</string> <!-- Message shown in dialog while the device is unlocking the SIM card --> <string name="kg_sim_unlock_progress_dialog_message">Unlocking SIM card\u2026</string> - <!-- Message shown when the user enters the wrong PIN code --> - <string name="kg_password_wrong_pin_code">Incorrect PIN code.</string> <!-- Message shown when the user enters an invalid SIM pin password in PUK screen --> <string name="kg_invalid_sim_pin_hint">Type a PIN that is 4 to 8 numbers.</string> <!-- Message shown when the user enters an invalid PUK code in the PUK screen --> @@ -333,6 +331,34 @@ <!-- The delete-widget drop target button text --> <string name="kg_reordering_delete_drop_target_text">Remove</string> + <!-- Instructions telling the user that they entered the wrong SIM PIN for the last time. + Displayed in a dialog box. --> + <string name="kg_password_wrong_pin_code_pukked">Incorrect SIM PIN code you must now contact your carrier to unlock your device.</string> + <!-- Instructions telling the user that they entered the wrong SIM PIN while trying + to unlock the keyguard. Displayed in a dialog box. --> + <plurals name="kg_password_wrong_pin_code"> + <item quantity="one">Incorrect SIM PIN code, you have <xliff:g id="number">%d</xliff:g> remaining attempt before you must contact your carrier to unlock your device.</item> + <item quantity="other">Incorrect SIM PIN code, you have <xliff:g id="number">%d</xliff:g> remaining attempts.</item> + </plurals> + + <!-- Instructions telling the user that they have exhausted SIM PUK retries and the SIM is now unusable. + Displayed in a dialog box. --> + <string name="kg_password_wrong_puk_code_dead">SIM is unusable. Contact your carrier.</string> + <!-- Instructions telling the user that they entered the wrong puk while trying + to unlock the keyguard. Displayed in a dialog box. --> + <plurals name="kg_password_wrong_puk_code"> + <item quantity="one">Incorrect SIM PUK code, you have <xliff:g id="number">%d</xliff:g> remaining attempt before SIM becomes permanently unusable.</item> + <item quantity="other">Incorrect SIM PUK code, you have <xliff:g id="number">%d</xliff:g> remaining attempts before SIM becomes permanently unusable.</item> + </plurals> + <!-- Instructions telling the user that the operation to unlock the keyguard + with SIM PIN failed. Displayed in one line in a large font. --> + <string name="kg_password_pin_failed">SIM PIN operation failed!</string> + <!-- Instructions telling the user that the operation to unlock the keyguard + with PUK failed. Displayed in one line in a large font. --> + <string name="kg_password_puk_failed">SIM PUK operation failed!</string> + <!-- Notification telling the user that the PIN1 they entered is valid --> + <string name="kg_pin_accepted">Code Accepted!</string> + <!-- Transport control strings --> <!-- Shown on transport control of lockscreen. Pressing button goes to previous track. --> <string name="keyguard_transport_prev_description">Previous track button</string> diff --git a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java index 7d1f24f..74e6f33 100644 --- a/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java +++ b/packages/Keyguard/src/com/android/keyguard/CameraWidgetFrame.java @@ -19,7 +19,6 @@ package com.android.keyguard; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Color; -import android.graphics.Paint; import android.graphics.Point; import android.graphics.Rect; import android.os.Handler; @@ -41,7 +40,7 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private static final String TAG = CameraWidgetFrame.class.getSimpleName(); private static final boolean DEBUG = KeyguardHostView.DEBUG; private static final int WIDGET_ANIMATION_DURATION = 250; // ms - private static final int WIDGET_WAIT_DURATION = 650; // ms + private static final int WIDGET_WAIT_DURATION = 400; // ms private static final int RECOVERY_DELAY = 1000; // ms interface Callbacks { @@ -68,6 +67,7 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private FixedSizeFrameLayout mPreview; private View mFullscreenPreview; private View mFakeNavBar; + private boolean mUseFastTransition; private final Runnable mTransitionToCameraRunnable = new Runnable() { @Override @@ -243,11 +243,12 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0; final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0; - mPreview.setPivotX(0); + final boolean isRtl = mPreview.getLayoutDirection() == LAYOUT_DIRECTION_RTL; + mPreview.setPivotX(isRtl ? mPreview.width : 0); mPreview.setPivotY(0); mPreview.setScaleX(pvScale); mPreview.setScaleY(pvScale); - mPreview.setTranslationX(pvTransX); + mPreview.setTranslationX((isRtl ? -1 : 1) * pvTransX); mPreview.setTranslationY(pvTransY); mRenderedSize.set(width, height); @@ -417,7 +418,8 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli private void rescheduleTransitionToCamera() { if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis()); mHandler.removeCallbacks(mTransitionToCameraRunnable); - mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION); + final long duration = mUseFastTransition ? 0 : WIDGET_WAIT_DURATION; + mHandler.postDelayed(mTransitionToCameraRunnable, duration); } private void cancelTransitionToCamera() { @@ -512,4 +514,8 @@ public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnCli if (DEBUG) Log.d(TAG, "setInsets: " + insets); mInsets.set(insets); } + + public void setUseFastTransition(boolean useFastTransition) { + mUseFastTransition = useFastTransition; + } } diff --git a/packages/Keyguard/src/com/android/keyguard/CarrierText.java b/packages/Keyguard/src/com/android/keyguard/CarrierText.java index c33f174..88558cd 100644 --- a/packages/Keyguard/src/com/android/keyguard/CarrierText.java +++ b/packages/Keyguard/src/com/android/keyguard/CarrierText.java @@ -17,14 +17,18 @@ package com.android.keyguard; import android.content.Context; +import android.text.method.SingleLineTransformationMethod; import android.text.TextUtils; import android.util.AttributeSet; +import android.view.View; import android.widget.TextView; import com.android.internal.telephony.IccCardConstants; import com.android.internal.telephony.IccCardConstants.State; import com.android.internal.widget.LockPatternUtils; +import java.util.Locale; + public class CarrierText extends TextView { private static CharSequence mSeparator; @@ -77,6 +81,8 @@ public class CarrierText extends TextView { public CarrierText(Context context, AttributeSet attrs) { super(context, attrs); mLockPatternUtils = new LockPatternUtils(mContext); + boolean useAllCaps = mContext.getResources().getBoolean(R.bool.kg_use_all_caps); + setTransformationMethod(new CarrierTextTransformationMethod(mContext, useAllCaps)); } protected void updateCarrierText(State simState, CharSequence plmn, CharSequence spn) { @@ -258,4 +264,25 @@ public class CarrierText extends TextView { return mContext.getText(carrierHelpTextId); } + + private class CarrierTextTransformationMethod extends SingleLineTransformationMethod { + private final Locale mLocale; + private final boolean mAllCaps; + + public CarrierTextTransformationMethod(Context context, boolean allCaps) { + mLocale = context.getResources().getConfiguration().locale; + mAllCaps = allCaps; + } + + @Override + public CharSequence getTransformation(CharSequence source, View view) { + source = super.getTransformation(source, view); + + if (mAllCaps && source != null) { + source = source.toString().toUpperCase(mLocale); + } + + return source; + } + } } diff --git a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java index 677f1f1..2ee21ac 100644 --- a/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java +++ b/packages/Keyguard/src/com/android/keyguard/ChallengeLayout.java @@ -39,7 +39,7 @@ public interface ChallengeLayout { * * @param b true to show, false to hide */ - void showChallenge(boolean b); + void showChallenge(boolean show); /** * Show the bouncer challenge. This may block access to other child views. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java index 9a1aa5b..0a915ea 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardActivityLauncher.java @@ -99,6 +99,11 @@ public abstract class KeyguardActivityLauncher { public void launchCamera(Handler worker, Runnable onSecureCameraStarted) { LockPatternUtils lockPatternUtils = getLockPatternUtils(); + + // Workaround to avoid camera release/acquisition race when resuming face unlock + // after showing lockscreen camera (bug 11063890). + KeyguardUpdateMonitor.getInstance(getContext()).setAlternateUnlockEnabled(false); + if (lockPatternUtils.isSecure()) { // Launch the secure version of the camera if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java new file mode 100644 index 0000000..6bcbd6c --- /dev/null +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardDisplayManager.java @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2013 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.keyguard; + +import android.app.Presentation; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnDismissListener; +import android.graphics.Point; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; +import android.os.Bundle; +import android.util.Slog; +import android.view.Display; +import android.view.View; +import android.view.WindowManager; + +public class KeyguardDisplayManager { + protected static final String TAG = "KeyguardDisplayManager"; + private static boolean DEBUG = KeyguardViewMediator.DEBUG; + Presentation mPresentation; + private MediaRouter mMediaRouter; + private Context mContext; + private boolean mShowing; + + KeyguardDisplayManager(Context context) { + mContext = context; + mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE); + } + + void show() { + if (!mShowing) { + if (DEBUG) Slog.v(TAG, "show"); + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + mMediaRouterCallback, MediaRouter.CALLBACK_FLAG_PASSIVE_DISCOVERY); + updateDisplays(true); + } + mShowing = true; + } + + void hide() { + if (mShowing) { + if (DEBUG) Slog.v(TAG, "hide"); + mMediaRouter.removeCallback(mMediaRouterCallback); + updateDisplays(false); + } + mShowing = false; + } + + private final MediaRouter.SimpleCallback mMediaRouterCallback = + new MediaRouter.SimpleCallback() { + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRouteSelected: type=" + type + ", info=" + info); + updateDisplays(mShowing); + } + + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRouteUnselected: type=" + type + ", info=" + info); + updateDisplays(mShowing); + } + + @Override + public void onRoutePresentationDisplayChanged(MediaRouter router, RouteInfo info) { + if (DEBUG) Slog.d(TAG, "onRoutePresentationDisplayChanged: info=" + info); + updateDisplays(mShowing); + } + }; + + private OnDismissListener mOnDismissListener = new OnDismissListener() { + + @Override + public void onDismiss(DialogInterface dialog) { + mPresentation = null; + } + }; + + protected void updateDisplays(boolean showing) { + if (showing) { + MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean useDisplay = route != null + && route.getPlaybackType() == MediaRouter.RouteInfo.PLAYBACK_TYPE_REMOTE; + Display presentationDisplay = useDisplay ? route.getPresentationDisplay() : null; + + if (mPresentation != null && mPresentation.getDisplay() != presentationDisplay) { + if (DEBUG) Slog.v(TAG, "Display gone: " + mPresentation.getDisplay()); + mPresentation.dismiss(); + mPresentation = null; + } + + if (mPresentation == null && presentationDisplay != null) { + if (DEBUG) Slog.i(TAG, "Keyguard enabled on display: " + presentationDisplay); + mPresentation = new KeyguardPresentation(mContext, presentationDisplay); + mPresentation.setOnDismissListener(mOnDismissListener); + try { + mPresentation.show(); + } catch (WindowManager.InvalidDisplayException ex) { + Slog.w(TAG, "Invalid display:", ex); + mPresentation = null; + } + } + } else { + if (mPresentation != null) { + mPresentation.dismiss(); + mPresentation = null; + } + } + } + + private final static class KeyguardPresentation extends Presentation { + private static final int VIDEO_SAFE_REGION = 80; // Percentage of display width & height + private static final int MOVE_CLOCK_TIMEOUT = 10000; // 10s + private View mClock; + private int mUsableWidth; + private int mUsableHeight; + private int mMarginTop; + private int mMarginLeft; + Runnable mMoveTextRunnable = new Runnable() { + @Override + public void run() { + int x = mMarginLeft + (int) (Math.random() * (mUsableWidth - mClock.getWidth())); + int y = mMarginTop + (int) (Math.random() * (mUsableHeight - mClock.getHeight())); + mClock.setTranslationX(x); + mClock.setTranslationY(y); + mClock.postDelayed(mMoveTextRunnable, MOVE_CLOCK_TIMEOUT); + } + }; + + public KeyguardPresentation(Context context, Display display) { + super(context, display); + getWindow().setType(WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } + + public void onDetachedFromWindow() { + mClock.removeCallbacks(mMoveTextRunnable); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Point p = new Point(); + getDisplay().getSize(p); + mUsableWidth = VIDEO_SAFE_REGION * p.x/100; + mUsableHeight = VIDEO_SAFE_REGION * p.y/100; + mMarginLeft = (100 - VIDEO_SAFE_REGION) * p.x / 200; + mMarginTop = (100 - VIDEO_SAFE_REGION) * p.y / 200; + + setContentView(R.layout.keyguard_presentation); + mClock = findViewById(R.id.clock); + + // Avoid screen burn in + mClock.post(mMoveTextRunnable); + } + } +} diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java index e94cf18..1bae9b8 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardHostView.java @@ -16,6 +16,10 @@ package com.android.keyguard; +import com.android.internal.widget.LockPatternUtils; +import com.android.keyguard.KeyguardSecurityModel.SecurityMode; +import com.android.keyguard.KeyguardUpdateMonitor.DisplayClientState; + import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; @@ -51,9 +55,6 @@ import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.widget.RemoteViews.OnClickHandler; -import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.KeyguardSecurityModel.SecurityMode; -import com.android.keyguard.KeyguardUpdateMonitor.DisplayClientState; import java.io.File; import java.lang.ref.WeakReference; @@ -217,7 +218,7 @@ public class KeyguardHostView extends KeyguardViewBase { mTransportState = (dcs.clearing ? TRANSPORT_GONE : (isMusicPlaying(dcs.playbackState) ? TRANSPORT_VISIBLE : TRANSPORT_INVISIBLE)); - if (DEBUG) Log.v(TAG, "Initial transport state: " + if (DEBUGXPORT) Log.v(TAG, "Initial transport state: " + mTransportState + ", pbstate=" + dcs.playbackState); } @@ -279,7 +280,7 @@ public class KeyguardHostView extends KeyguardViewBase { if (newState != mTransportState) { mTransportState = newState; if (DEBUGXPORT) Log.v(TAG, "update widget: transport state changed"); - KeyguardHostView.this.postShowAppropriateWidgetPage(); + KeyguardHostView.this.post(mSwitchPageRunnable); } } @Override @@ -291,7 +292,7 @@ public class KeyguardHostView extends KeyguardViewBase { if (newState != mTransportState) { mTransportState = newState; if (DEBUGXPORT) Log.v(TAG, "update widget: play state changed"); - KeyguardHostView.this.postShowAppropriateWidgetPage(); + KeyguardHostView.this.post(mSwitchPageRunnable); } } } @@ -495,7 +496,6 @@ public class KeyguardHostView extends KeyguardViewBase { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - removeCallbacks(mSwitchPageRunnable); mAppWidgetHost.stopListening(); KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitorCallbacks); } @@ -1369,7 +1369,7 @@ public class KeyguardHostView extends KeyguardViewBase { } } - Runnable mSwitchPageRunnable = new Runnable() { + private final Runnable mSwitchPageRunnable = new Runnable() { @Override public void run() { showAppropriateWidgetPage(); @@ -1438,7 +1438,7 @@ public class KeyguardHostView extends KeyguardViewBase { mAppWidgetToShow = ss.appWidgetToShow; setInsets(ss.insets); if (DEBUG) Log.d(TAG, "onRestoreInstanceState, transport=" + mTransportState); - postShowAppropriateWidgetPage(); + mSwitchPageRunnable.run(); } @Override @@ -1471,20 +1471,22 @@ public class KeyguardHostView extends KeyguardViewBase { } } - void showAppropriateWidgetPage() { - int state = mTransportState; - ensureTransportPresentOrRemoved(state); - if (mAppWidgetContainer.isLayoutRequested()) { - postShowAppropriateWidgetPage(); - return; + private void showAppropriateWidgetPage() { + final int state = mTransportState; + final boolean transportAdded = ensureTransportPresentOrRemoved(state); + final int pageToShow = getAppropriateWidgetPage(state); + if (!transportAdded) { + mAppWidgetContainer.setCurrentPage(pageToShow); + } else if (state == TRANSPORT_VISIBLE) { + // If the transport was just added, we need to wait for layout to happen before + // we can set the current page. + post(new Runnable() { + @Override + public void run() { + mAppWidgetContainer.setCurrentPage(pageToShow); + } + }); } - int pageToShow = getAppropriateWidgetPage(state); - mAppWidgetContainer.setCurrentPage(pageToShow); - } - - void postShowAppropriateWidgetPage() { - removeCallbacks(mSwitchPageRunnable); - post(mSwitchPageRunnable); } /** @@ -1508,12 +1510,11 @@ public class KeyguardHostView extends KeyguardViewBase { * * @param state */ - private void ensureTransportPresentOrRemoved(int state) { + private boolean ensureTransportPresentOrRemoved(int state) { final boolean showing = getWidgetPosition(R.id.keyguard_transport_control) != -1; final boolean visible = state == TRANSPORT_VISIBLE; final boolean shouldBeVisible = state == TRANSPORT_INVISIBLE && isMusicPlaying(state); if (!showing && (visible || shouldBeVisible)) { - if (DEBUGXPORT) Log.v(TAG, "add transport"); // insert to left of camera if it exists, otherwise after right-most widget int lastWidget = mAppWidgetContainer.getChildCount() - 1; int position = 0; // handle no widget case @@ -1521,13 +1522,16 @@ public class KeyguardHostView extends KeyguardViewBase { position = mAppWidgetContainer.isCameraPage(lastWidget) ? lastWidget : lastWidget + 1; } + if (DEBUGXPORT) Log.v(TAG, "add transport at " + position); mAppWidgetContainer.addWidget(getOrCreateTransportControl(), position); + return true; } else if (showing && state == TRANSPORT_GONE) { if (DEBUGXPORT) Log.v(TAG, "remove transport"); mAppWidgetContainer.removeWidget(getOrCreateTransportControl()); mTransportControl = null; KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(null); } + return false; } private CameraWidgetFrame findCameraPage() { diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java index 69075ec..751572c 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardMessageArea.java @@ -177,6 +177,7 @@ class KeyguardMessageArea extends TextView { public KeyguardMessageArea(Context context, AttributeSet attrs) { super(context, attrs); + setLayerType(LAYER_TYPE_HARDWARE, null); // work around nested unclipped SaveLayer bug mLockPatternUtils = new LockPatternUtils(context); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java index d7c5fe2..36b2446 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardService.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardService.java @@ -141,6 +141,10 @@ public class KeyguardService extends Service { checkPermission(); mKeyguardViewMediator.launchCamera(); } + public void onBootCompleted() { + checkPermission(); + mKeyguardViewMediator.onBootCompleted(); + } }; } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java index 5059407..9accbb4 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPinView.java @@ -17,11 +17,16 @@ package com.android.keyguard; import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.PhoneConstants; import android.content.Context; +import android.content.DialogInterface; import android.app.Activity; +import android.app.AlertDialog; +import android.app.AlertDialog.Builder; import android.app.Dialog; import android.app.ProgressDialog; +import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; import android.text.Editable; @@ -30,6 +35,7 @@ import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.util.AttributeSet; import android.view.View; +import android.util.Log; import android.view.WindowManager; import android.widget.TextView.OnEditorActionListener; @@ -38,9 +44,14 @@ import android.widget.TextView.OnEditorActionListener; */ public class KeyguardSimPinView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { + private static final String LOG_TAG = "KeyguardSimPinView"; + private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + public static final String TAG = "KeyguardSimPinView"; private ProgressDialog mSimUnlockProgressDialog = null; - private volatile boolean mSimCheckInProgress; + private CheckSimPin mCheckSimPinThread; + + private AlertDialog mRemainingAttemptsDialog; public KeyguardSimPinView(Context context) { this(context, null); @@ -55,6 +66,23 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mPasswordEntry.setEnabled(true); } + private String getPinPasswordErrorMessage(int attemptsRemaining) { + String displayMessage; + + if (attemptsRemaining == 0) { + displayMessage = getContext().getString(R.string.kg_password_wrong_pin_code_pukked); + } else if (attemptsRemaining > 0) { + displayMessage = getContext().getResources() + .getQuantityString(R.plurals.kg_password_wrong_pin_code, attemptsRemaining, + attemptsRemaining); + } else { + displayMessage = getContext().getString(R.string.kg_password_pin_failed); + } + if (DEBUG) Log.d(LOG_TAG, "getPinPasswordErrorMessage:" + + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); + return displayMessage; + } + @Override protected boolean shouldLockout(long deadline) { // SIM PIN doesn't have a timed lockout @@ -109,6 +137,8 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView | InputType.TYPE_NUMBER_VARIATION_PASSWORD); mPasswordEntry.requestFocus(); + + mSecurityMessageDisplay.setTimeout(0); // don't show ownerinfo/charging status by default } @Override @@ -135,22 +165,25 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mPin = pin; } - abstract void onSimCheckResponse(boolean success); + abstract void onSimCheckResponse(final int result, final int attemptsRemaining); @Override public void run() { try { - final boolean result = ITelephony.Stub.asInterface(ServiceManager - .checkService("phone")).supplyPin(mPin); + Log.v(TAG, "call supplyPinReportResult()"); + final int[] result = ITelephony.Stub.asInterface(ServiceManager + .checkService("phone")).supplyPinReportResult(mPin); + Log.v(TAG, "supplyPinReportResult returned: " + result[0] + " " + result[1]); post(new Runnable() { public void run() { - onSimCheckResponse(result); + onSimCheckResponse(result[0], result[1]); } }); } catch (RemoteException e) { + Log.e(TAG, "RemoteException for supplyPinReportResult:", e); post(new Runnable() { public void run() { - onSimCheckResponse(false); + onSimCheckResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); } }); } @@ -164,14 +197,28 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView mContext.getString(R.string.kg_sim_unlock_progress_dialog_message)); mSimUnlockProgressDialog.setIndeterminate(true); mSimUnlockProgressDialog.setCancelable(false); - if (!(mContext instanceof Activity)) { - mSimUnlockProgressDialog.getWindow().setType( - WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); - } + mSimUnlockProgressDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); } return mSimUnlockProgressDialog; } + private Dialog getSimRemainingAttemptsDialog(int remaining) { + String msg = getPinPasswordErrorMessage(remaining); + if (mRemainingAttemptsDialog == null) { + Builder builder = new AlertDialog.Builder(mContext); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + @Override protected void verifyPasswordAndUnlock() { String entry = mPasswordEntry.getText().toString(); @@ -186,31 +233,45 @@ public class KeyguardSimPinView extends KeyguardAbsKeyInputView getSimUnlockProgressDialog().show(); - if (!mSimCheckInProgress) { - mSimCheckInProgress = true; // there should be only one - new CheckSimPin(mPasswordEntry.getText().toString()) { - void onSimCheckResponse(final boolean success) { + if (mCheckSimPinThread == null) { + mCheckSimPinThread = new CheckSimPin(mPasswordEntry.getText().toString()) { + void onSimCheckResponse(final int result, final int attemptsRemaining) { post(new Runnable() { public void run() { if (mSimUnlockProgressDialog != null) { mSimUnlockProgressDialog.hide(); } - if (success) { - // before closing the keyguard, report back that the sim is unlocked - // so it knows right away. + if (result == PhoneConstants.PIN_RESULT_SUCCESS) { KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(); mCallback.dismiss(true); } else { - mSecurityMessageDisplay.setMessage - (R.string.kg_password_wrong_pin_code, true); + if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { + if (attemptsRemaining <= 2) { + // this is getting critical - show dialog + getSimRemainingAttemptsDialog(attemptsRemaining).show(); + } else { + // show message + mSecurityMessageDisplay.setMessage( + getPinPasswordErrorMessage(attemptsRemaining), true); + } + } else { + // "PIN operation failed!" - no idea what this was and no way to + // find out. :/ + mSecurityMessageDisplay.setMessage(getContext().getString( + R.string.kg_password_pin_failed), true); + } + if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " CheckSimPin.onSimCheckResponse: " + result + + " attemptsRemaining=" + attemptsRemaining); mPasswordEntry.setText(""); } mCallback.userActivity(0); - mSimCheckInProgress = false; + mCheckSimPinThread = null; } }); } - }.start(); + }; + mCheckSimPinThread.start(); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java index 2ae4cc7..6e9e83e 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardSimPukView.java @@ -16,10 +16,10 @@ package com.android.keyguard; -import com.android.internal.telephony.ITelephony; - import android.content.Context; +import android.animation.AnimatorSet.Builder; import android.app.Activity; +import android.app.AlertDialog; import android.app.Dialog; import android.app.ProgressDialog; import android.os.RemoteException; @@ -29,21 +29,30 @@ import android.text.InputType; import android.text.TextWatcher; import android.text.method.DigitsKeyListener; import android.util.AttributeSet; +import android.util.Log; import android.view.View; import android.view.WindowManager; import android.widget.TextView.OnEditorActionListener; +import com.android.internal.telephony.ITelephony; +import com.android.internal.telephony.PhoneConstants; + + /** * Displays a PIN pad for entering a PUK (Pin Unlock Kode) provided by a carrier. */ public class KeyguardSimPukView extends KeyguardAbsKeyInputView implements KeyguardSecurityView, OnEditorActionListener, TextWatcher { + private static final String LOG_TAG = "KeyguardSimPukView"; + private static final boolean DEBUG = KeyguardViewMediator.DEBUG; + public static final String TAG = "KeyguardSimPukView"; private ProgressDialog mSimUnlockProgressDialog = null; - private volatile boolean mCheckInProgress; + private CheckSimPuk mCheckSimPukThread; private String mPukText; private String mPinText; private StateMachine mStateMachine = new StateMachine(); + private AlertDialog mRemainingAttemptsDialog; private class StateMachine { final int ENTER_PUK = 0; @@ -93,6 +102,23 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView } } + private String getPukPasswordErrorMessage(int attemptsRemaining) { + String displayMessage; + + if (attemptsRemaining == 0) { + displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead); + } else if (attemptsRemaining > 0) { + displayMessage = getContext().getResources() + .getQuantityString(R.plurals.kg_password_wrong_puk_code, attemptsRemaining, + attemptsRemaining); + } else { + displayMessage = getContext().getString(R.string.kg_password_puk_failed); + } + if (DEBUG) Log.d(LOG_TAG, "getPukPasswordErrorMessage:" + + " attemptsRemaining=" + attemptsRemaining + " displayMessage=" + displayMessage); + return displayMessage; + } + public KeyguardSimPukView(Context context) { this(context, null); } @@ -190,23 +216,25 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView mPin = pin; } - abstract void onSimLockChangedResponse(boolean success); + abstract void onSimLockChangedResponse(final int result, final int attemptsRemaining); @Override public void run() { try { - final boolean result = ITelephony.Stub.asInterface(ServiceManager - .checkService("phone")).supplyPuk(mPuk, mPin); - + Log.v(TAG, "call supplyPukReportResult()"); + final int[] result = ITelephony.Stub.asInterface(ServiceManager + .checkService("phone")).supplyPukReportResult(mPuk, mPin); + Log.v(TAG, "supplyPukReportResult returned: " + result[0] + " " + result[1]); post(new Runnable() { public void run() { - onSimLockChangedResponse(result); + onSimLockChangedResponse(result[0], result[1]); } }); } catch (RemoteException e) { + Log.e(TAG, "RemoteException for supplyPukReportResult:", e); post(new Runnable() { public void run() { - onSimLockChangedResponse(false); + onSimLockChangedResponse(PhoneConstants.PIN_GENERAL_FAILURE, -1); } }); } @@ -228,6 +256,22 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView return mSimUnlockProgressDialog; } + private Dialog getPukRemainingAttemptsDialog(int remaining) { + String msg = getPukPasswordErrorMessage(remaining); + if (mRemainingAttemptsDialog == null) { + AlertDialog.Builder builder = new AlertDialog.Builder(mContext); + builder.setMessage(msg); + builder.setCancelable(false); + builder.setNeutralButton(R.string.ok, null); + mRemainingAttemptsDialog = builder.create(); + mRemainingAttemptsDialog.getWindow().setType( + WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG); + } else { + mRemainingAttemptsDialog.setMessage(msg); + } + return mRemainingAttemptsDialog; + } + private boolean checkPuk() { // make sure the puk is at least 8 digits long. if (mPasswordEntry.getText().length() >= 8) { @@ -254,26 +298,42 @@ public class KeyguardSimPukView extends KeyguardAbsKeyInputView private void updateSim() { getSimUnlockProgressDialog().show(); - if (!mCheckInProgress) { - mCheckInProgress = true; - new CheckSimPuk(mPukText, mPinText) { - void onSimLockChangedResponse(final boolean success) { + if (mCheckSimPukThread == null) { + mCheckSimPukThread = new CheckSimPuk(mPukText, mPinText) { + void onSimLockChangedResponse(final int result, final int attemptsRemaining) { post(new Runnable() { public void run() { if (mSimUnlockProgressDialog != null) { mSimUnlockProgressDialog.hide(); } - if (success) { + if (result == PhoneConstants.PIN_RESULT_SUCCESS) { + KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(); mCallback.dismiss(true); } else { + if (result == PhoneConstants.PIN_PASSWORD_INCORRECT) { + if (attemptsRemaining <= 2) { + // this is getting critical - show dialog + getPukRemainingAttemptsDialog(attemptsRemaining).show(); + } else { + // show message + mSecurityMessageDisplay.setMessage( + getPukPasswordErrorMessage(attemptsRemaining), true); + } + } else { + mSecurityMessageDisplay.setMessage(getContext().getString( + R.string.kg_password_puk_failed), true); + } + if (DEBUG) Log.d(LOG_TAG, "verifyPasswordAndUnlock " + + " UpdateSim.onSimCheckResponse: " + + " attemptsRemaining=" + attemptsRemaining); mStateMachine.reset(); - mSecurityMessageDisplay.setMessage(R.string.kg_invalid_puk, true); } - mCheckInProgress = false; + mCheckSimPukThread = null; } }); } - }.start(); + }; + mCheckSimPukThread.start(); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java index d933275..0bfee38 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardStatusView.java @@ -98,26 +98,13 @@ public class KeyguardStatusView extends GridLayout { } protected void refresh() { - Resources res = mContext.getResources(); - Locale locale = Locale.getDefault(); - final String dateFormat = DateFormat.getBestDateTimePattern(locale, - res.getString(R.string.abbrev_wday_month_day_no_year)); - - mDateView.setFormat24Hour(dateFormat); - mDateView.setFormat12Hour(dateFormat); - - // 12-hour clock. - // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton - // format. The following code removes the AM/PM indicator if we didn't want it. - final String clock12skel = res.getString(R.string.clock_12hr_format); - String clock12hr = DateFormat.getBestDateTimePattern(locale, clock12skel); - clock12hr = clock12skel.contains("a") ? clock12hr : clock12hr.replaceAll("a", "").trim(); - mClockView.setFormat12Hour(clock12hr); - - // 24-hour clock - final String clock24skel = res.getString(R.string.clock_24hr_format); - final String clock24hr = DateFormat.getBestDateTimePattern(locale, clock24skel); - mClockView.setFormat24Hour(clock24hr); + Patterns.update(mContext); + + mDateView.setFormat24Hour(Patterns.dateView); + mDateView.setFormat12Hour(Patterns.dateView); + + mClockView.setFormat12Hour(Patterns.clockView12); + mClockView.setFormat24Hour(Patterns.clockView24); refreshAlarmStatus(); } @@ -149,4 +136,35 @@ public class KeyguardStatusView extends GridLayout { return LockPatternUtils.ID_DEFAULT_STATUS_WIDGET; } + // DateFormat.getBestDateTimePattern is extremely expensive, and refresh is called often. + // This is an optimization to ensure we only recompute the patterns when the inputs change. + private static final class Patterns { + static String dateView; + static String clockView12; + static String clockView24; + static String cacheKey; + + static void update(Context context) { + final Locale locale = Locale.getDefault(); + final Resources res = context.getResources(); + final String dateViewSkel = res.getString(R.string.abbrev_wday_month_day_no_year); + final String clockView12Skel = res.getString(R.string.clock_12hr_format); + final String clockView24Skel = res.getString(R.string.clock_24hr_format); + final String key = locale.toString() + dateViewSkel + clockView12Skel + clockView24Skel; + if (key.equals(cacheKey)) return; + + dateView = DateFormat.getBestDateTimePattern(locale, dateViewSkel); + + clockView12 = DateFormat.getBestDateTimePattern(locale, clockView12Skel); + // CLDR insists on adding an AM/PM indicator even though it wasn't in the skeleton + // format. The following code removes the AM/PM indicator if we didn't want it. + if (!clockView12Skel.contains("a")) { + clockView12 = clockView12.replaceAll("a", "").trim(); + } + + clockView24 = DateFormat.getBestDateTimePattern(locale, clockView24Skel); + + cacheKey = key; + } + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java index a48f23e..349078f 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardTransportControlView.java @@ -60,12 +60,12 @@ import java.util.TimeZone; */ public class KeyguardTransportControlView extends FrameLayout { - private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s private static final int RESET_TO_METADATA_DELAY = 5000; protected static final boolean DEBUG = false; protected static final String TAG = "TransportControlView"; private static final boolean ANIMATE_TRANSITIONS = true; + protected static final long QUIESCENT_PLAYBACK_FACTOR = 1000; private ViewGroup mMetadataContainer; private ViewGroup mInfoContainer; @@ -75,7 +75,7 @@ public class KeyguardTransportControlView extends FrameLayout { private View mTransientSeek; private SeekBar mTransientSeekBar; private TextView mTransientSeekTimeElapsed; - private TextView mTransientSeekTimeRemaining; + private TextView mTransientSeekTimeTotal; private ImageView mBtnPrev; private ImageView mBtnPlay; @@ -89,9 +89,10 @@ public class KeyguardTransportControlView extends FrameLayout { private ImageView mBadge; private boolean mSeekEnabled; - private boolean mUserSeeking; private java.text.DateFormat mFormat; + private Date mTempDate = new Date(); + /** * The metadata which should be populated into the view once we've been attached */ @@ -108,18 +109,25 @@ public class KeyguardTransportControlView extends FrameLayout { @Override public void onClientPlaybackStateUpdate(int state) { - setSeekBarsEnabled(false); updatePlayPauseState(state); } @Override public void onClientPlaybackStateUpdate(int state, long stateChangeTimeMs, long currentPosMs, float speed) { - setSeekBarsEnabled(mMetadata != null && mMetadata.duration > 0); updatePlayPauseState(state); if (DEBUG) Log.d(TAG, "onClientPlaybackStateUpdate(state=" + state + ", stateChangeTimeMs=" + stateChangeTimeMs + ", currentPosMs=" + currentPosMs + ", speed=" + speed + ")"); + + removeCallbacks(mUpdateSeekBars); + // Since the music client may be responding to historical events that cause the + // playback state to change dramatically, wait until things become quiescent before + // resuming automatic scrub position update. + if (mTransientSeek.getVisibility() == View.VISIBLE + && playbackPositionShouldMove(mCurrentPlayState)) { + postDelayed(mUpdateSeekBars, QUIESCENT_PLAYBACK_FACTOR); + } } @Override @@ -133,14 +141,21 @@ public class KeyguardTransportControlView extends FrameLayout { } }; - private final Runnable mUpdateSeekBars = new Runnable() { + private class UpdateSeekBarRunnable implements Runnable { public void run() { - if (updateSeekBars()) { + boolean seekAble = updateOnce(); + if (seekAble) { + removeCallbacks(this); postDelayed(this, 1000); } } + public boolean updateOnce() { + return updateSeekBars(); + } }; + private final UpdateSeekBarRunnable mUpdateSeekBars = new UpdateSeekBarRunnable(); + private final Runnable mResetToMetadata = new Runnable() { public void run() { resetToMetadata(); @@ -159,6 +174,7 @@ public class KeyguardTransportControlView extends FrameLayout { } if (keyCode != -1) { sendMediaButtonClick(keyCode); + delayResetToMetadata(); // if the scrub bar is showing, keep showing it. } } }; @@ -173,25 +189,67 @@ public class KeyguardTransportControlView extends FrameLayout { } }; + // This class is here to throttle scrub position updates to the music client + class FutureSeekRunnable implements Runnable { + private int mProgress; + private boolean mPending; + + public void run() { + scrubTo(mProgress); + mPending = false; + } + + void setProgress(int progress) { + mProgress = progress; + if (!mPending) { + mPending = true; + postDelayed(this, 30); + } + } + }; + + // This is here because RemoteControlClient's method isn't visible :/ + private final static boolean playbackPositionShouldMove(int playstate) { + switch(playstate) { + case RemoteControlClient.PLAYSTATE_STOPPED: + case RemoteControlClient.PLAYSTATE_PAUSED: + case RemoteControlClient.PLAYSTATE_BUFFERING: + case RemoteControlClient.PLAYSTATE_ERROR: + case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: + case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: + return false; + case RemoteControlClient.PLAYSTATE_PLAYING: + case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: + case RemoteControlClient.PLAYSTATE_REWINDING: + default: + return true; + } + } + + private final FutureSeekRunnable mFutureSeekRunnable = new FutureSeekRunnable(); + private final SeekBar.OnSeekBarChangeListener mOnSeekBarChangeListener = new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { if (fromUser) { - scrubTo(progress); + mFutureSeekRunnable.setProgress(progress); delayResetToMetadata(); + mTempDate.setTime(progress); + mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); + } else { + updateSeekDisplay(); } - updateSeekDisplay(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { - mUserSeeking = true; + delayResetToMetadata(); + removeCallbacks(mUpdateSeekBars); // don't update during user interaction } @Override public void onStopTrackingTouch(SeekBar seekBar) { - mUserSeeking = false; } }; @@ -204,10 +262,10 @@ public class KeyguardTransportControlView extends FrameLayout { = new KeyguardUpdateMonitorCallback() { public void onScreenTurnedOff(int why) { setEnableMarquee(false); - }; + } public void onScreenTurnedOn() { setEnableMarquee(true); - }; + } }; public KeyguardTransportControlView(Context context, AttributeSet attrs) { @@ -243,18 +301,11 @@ public class KeyguardTransportControlView extends FrameLayout { if (enabled == mSeekEnabled) return; mSeekEnabled = enabled; - if (mTransientSeek.getVisibility() == VISIBLE) { + if (mTransientSeek.getVisibility() == VISIBLE && !enabled) { mTransientSeek.setVisibility(INVISIBLE); mMetadataContainer.setVisibility(VISIBLE); - mUserSeeking = false; cancelResetToMetadata(); } - if (enabled) { - mUpdateSeekBars.run(); - postDelayed(mUpdateSeekBars, 1000); - } else { - removeCallbacks(mUpdateSeekBars); - } } public void setTransportControlCallback(KeyguardHostView.TransportControlCallback @@ -280,7 +331,7 @@ public class KeyguardTransportControlView extends FrameLayout { mTransientSeekBar = (SeekBar) findViewById(R.id.transient_seek_bar); mTransientSeekBar.setOnSeekBarChangeListener(mOnSeekBarChangeListener); mTransientSeekTimeElapsed = (TextView) findViewById(R.id.transient_seek_time_elapsed); - mTransientSeekTimeRemaining = (TextView) findViewById(R.id.transient_seek_time_remaining); + mTransientSeekTimeTotal = (TextView) findViewById(R.id.transient_seek_time_remaining); mBtnPrev = (ImageView) findViewById(R.id.btn_prev); mBtnPlay = (ImageView) findViewById(R.id.btn_play); mBtnNext = (ImageView) findViewById(R.id.btn_next); @@ -291,6 +342,8 @@ public class KeyguardTransportControlView extends FrameLayout { } final boolean screenOn = KeyguardUpdateMonitor.getInstance(mContext).isScreenOn(); setEnableMarquee(screenOn); + // Allow long-press anywhere else in this view to show the seek bar + setOnLongClickListener(mTransportShowSeekBarListener); } @Override @@ -302,6 +355,7 @@ public class KeyguardTransportControlView extends FrameLayout { mPopulateMetadataWhenAttached = null; } if (DEBUG) Log.v(TAG, "Registering TCV " + this); + mMetadata.clear(); mAudioManager.registerRemoteController(mRemoteController); KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitor); } @@ -321,10 +375,37 @@ public class KeyguardTransportControlView extends FrameLayout { if (DEBUG) Log.v(TAG, "Unregistering TCV " + this); mAudioManager.unregisterRemoteController(mRemoteController); KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mUpdateMonitor); - mUserSeeking = false; + mMetadata.clear(); removeCallbacks(mUpdateSeekBars); } + @Override + protected Parcelable onSaveInstanceState() { + SavedState ss = new SavedState(super.onSaveInstanceState()); + ss.artist = mMetadata.artist; + ss.trackTitle = mMetadata.trackTitle; + ss.albumTitle = mMetadata.albumTitle; + ss.duration = mMetadata.duration; + ss.bitmap = mMetadata.bitmap; + return ss; + } + + @Override + protected void onRestoreInstanceState(Parcelable state) { + if (!(state instanceof SavedState)) { + super.onRestoreInstanceState(state); + return; + } + SavedState ss = (SavedState) state; + super.onRestoreInstanceState(ss.getSuperState()); + mMetadata.artist = ss.artist; + mMetadata.trackTitle = ss.trackTitle; + mMetadata.albumTitle = ss.albumTitle; + mMetadata.duration = ss.duration; + mMetadata.bitmap = ss.bitmap; + populateMetadata(); + } + void setBadgeIcon(Drawable bmp) { mBadge.setImageDrawable(bmp); @@ -395,10 +476,10 @@ public class KeyguardTransportControlView extends FrameLayout { Log.e(TAG, "Couldn't get remote control client package icon", e); } setBadgeIcon(badgeIcon); - if (!TextUtils.isEmpty(mMetadata.trackTitle)) { - mTrackTitle.setText(mMetadata.trackTitle); - } - StringBuilder sb = new StringBuilder(); + mTrackTitle.setText(!TextUtils.isEmpty(mMetadata.trackTitle) + ? mMetadata.trackTitle : null); + + final StringBuilder sb = new StringBuilder(); if (!TextUtils.isEmpty(mMetadata.artist)) { if (sb.length() != 0) { sb.append(" - "); @@ -411,7 +492,10 @@ public class KeyguardTransportControlView extends FrameLayout { } sb.append(mMetadata.albumTitle); } - mTrackArtistAlbum.setText(sb.toString()); + + final String trackArtistAlbum = sb.toString(); + mTrackArtistAlbum.setText(!TextUtils.isEmpty(trackArtistAlbum) ? + trackArtistAlbum : null); if (mMetadata.duration >= 0) { setSeekBarsEnabled(true); @@ -434,8 +518,7 @@ public class KeyguardTransportControlView extends FrameLayout { setSeekBarsEnabled(false); } - KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground( - mMetadata.bitmap); + KeyguardUpdateMonitor.getInstance(getContext()).dispatchSetBackground(mMetadata.bitmap); final int flags = mTransportControlFlags; setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS); setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT); @@ -450,15 +533,13 @@ public class KeyguardTransportControlView extends FrameLayout { void updateSeekDisplay() { if (mMetadata != null && mRemoteController != null && mFormat != null) { - final long timeElapsed = mRemoteController.getEstimatedMediaPosition(); - final long duration = mMetadata.duration; - final long remaining = duration - timeElapsed; + mTempDate.setTime(mRemoteController.getEstimatedMediaPosition()); + mTransientSeekTimeElapsed.setText(mFormat.format(mTempDate)); + mTempDate.setTime(mMetadata.duration); + mTransientSeekTimeTotal.setText(mFormat.format(mTempDate)); - mTransientSeekTimeElapsed.setText(mFormat.format(new Date(timeElapsed))); - mTransientSeekTimeRemaining.setText(mFormat.format(new Date(remaining))); - - if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + timeElapsed + - " duration=" + duration + " remaining=" + remaining); + if (DEBUG) Log.d(TAG, "updateSeekDisplay timeElapsed=" + mTempDate + + " duration=" + mMetadata.duration); } } @@ -470,10 +551,16 @@ public class KeyguardTransportControlView extends FrameLayout { mTransientSeek.setVisibility(INVISIBLE); mMetadataContainer.setVisibility(VISIBLE); cancelResetToMetadata(); + removeCallbacks(mUpdateSeekBars); // don't update if scrubber isn't visible } else { mTransientSeek.setVisibility(VISIBLE); mMetadataContainer.setVisibility(INVISIBLE); delayResetToMetadata(); + if (playbackPositionShouldMove(mCurrentPlayState)) { + mUpdateSeekBars.run(); + } else { + mUpdateSeekBars.updateOnce(); + } } mTransportControlCallback.userActivity(); return true; @@ -535,9 +622,6 @@ public class KeyguardTransportControlView extends FrameLayout { case RemoteControlClient.PLAYSTATE_PLAYING: imageResId = R.drawable.ic_media_pause; imageDescId = R.string.keyguard_transport_pause_description; - if (mSeekEnabled) { - postDelayed(mUpdateSeekBars, 1000); - } break; case RemoteControlClient.PLAYSTATE_BUFFERING: @@ -552,10 +636,9 @@ public class KeyguardTransportControlView extends FrameLayout { break; } - if (state != RemoteControlClient.PLAYSTATE_PLAYING) { - removeCallbacks(mUpdateSeekBars); - updateSeekBars(); - } + boolean clientSupportsSeek = mMetadata != null && mMetadata.duration > 0; + setSeekBarsEnabled(clientSupportsSeek); + mBtnPlay.setImageResource(imageResId); mBtnPlay.setContentDescription(getResources().getString(imageDescId)); mCurrentPlayState = state; @@ -563,10 +646,9 @@ public class KeyguardTransportControlView extends FrameLayout { boolean updateSeekBars() { final int position = (int) mRemoteController.getEstimatedMediaPosition(); + if (DEBUG) Log.v(TAG, "Estimated time:" + position); if (position >= 0) { - if (!mUserSeeking) { - mTransientSeekBar.setProgress(position); - } + mTransientSeekBar.setProgress(position); return true; } Log.w(TAG, "Updating seek bars; received invalid estimated media position (" + @@ -577,6 +659,11 @@ public class KeyguardTransportControlView extends FrameLayout { static class SavedState extends BaseSavedState { boolean clientPresent; + String artist; + String trackTitle; + String albumTitle; + long duration; + Bitmap bitmap; SavedState(Parcelable superState) { super(superState); @@ -584,13 +671,23 @@ public class KeyguardTransportControlView extends FrameLayout { private SavedState(Parcel in) { super(in); - this.clientPresent = in.readInt() != 0; + clientPresent = in.readInt() != 0; + artist = in.readString(); + trackTitle = in.readString(); + albumTitle = in.readString(); + duration = in.readLong(); + bitmap = Bitmap.CREATOR.createFromParcel(in); } @Override public void writeToParcel(Parcel out, int flags) { super.writeToParcel(out, flags); - out.writeInt(this.clientPresent ? 1 : 0); + out.writeInt(clientPresent ? 1 : 0); + out.writeString(artist); + out.writeString(trackTitle); + out.writeString(albumTitle); + out.writeLong(duration); + bitmap.writeToParcel(out, flags); } public static final Parcelable.Creator<SavedState> CREATOR @@ -617,34 +714,4 @@ public class KeyguardTransportControlView extends FrameLayout { public boolean providesClock() { return false; } - - private boolean wasPlayingRecently(int state, long stateChangeTimeMs) { - switch (state) { - case RemoteControlClient.PLAYSTATE_PLAYING: - case RemoteControlClient.PLAYSTATE_FAST_FORWARDING: - case RemoteControlClient.PLAYSTATE_REWINDING: - case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS: - case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS: - case RemoteControlClient.PLAYSTATE_BUFFERING: - // actively playing or about to play - return true; - case RemoteControlClient.PLAYSTATE_NONE: - return false; - case RemoteControlClient.PLAYSTATE_STOPPED: - case RemoteControlClient.PLAYSTATE_PAUSED: - case RemoteControlClient.PLAYSTATE_ERROR: - // we have stopped playing, check how long ago - if (DEBUG) { - if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) { - Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently"); - } else { - Log.v(TAG, "wasPlayingRecently: time > TIMEOUT"); - } - } - return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS); - default: - Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()"); - return false; - } - } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java index 45cd3d4..a849316 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -635,15 +635,14 @@ public class KeyguardUpdateMonitor { * PhoneWindowManager in this case. */ protected void dispatchBootCompleted() { - if (!mBootCompleted) { - mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED); - } + mHandler.sendEmptyMessage(MSG_BOOT_COMPLETED); } /** * Handle {@link #MSG_BOOT_COMPLETED} */ protected void handleBootCompleted() { + if (mBootCompleted) return; mBootCompleted = true; mAudioManager = new AudioManager(mContext); mAudioManager.registerRemoteControlDisplay(mRemoteControlDisplay); @@ -816,7 +815,7 @@ public class KeyguardUpdateMonitor { for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onKeyguardVisibilityChanged(isShowing); + cb.onKeyguardVisibilityChangedRaw(isShowing); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 76f9637..c08880d 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -19,6 +19,7 @@ import android.app.PendingIntent; import android.app.admin.DevicePolicyManager; import android.graphics.Bitmap; import android.media.AudioManager; +import android.os.SystemClock; import android.view.WindowManagerPolicy; import com.android.internal.telephony.IccCardConstants; @@ -27,6 +28,11 @@ import com.android.internal.telephony.IccCardConstants; * Callback for general information relevant to lock screen. */ class KeyguardUpdateMonitorCallback { + + private static final long VISIBILITY_CHANGED_COLLAPSE_MS = 1000; + private long mVisibilityChangedCalled; + private boolean mShowing; + /** * Called when the battery status changes, e.g. when plugged in or unplugged, charge * level, etc. changes. @@ -70,6 +76,15 @@ class KeyguardUpdateMonitorCallback { */ void onKeyguardVisibilityChanged(boolean showing) { } + void onKeyguardVisibilityChangedRaw(boolean showing) { + final long now = SystemClock.elapsedRealtime(); + if (showing == mShowing + && (now - mVisibilityChangedCalled) < VISIBILITY_CHANGED_COLLAPSE_MS) return; + onKeyguardVisibilityChanged(showing); + mVisibilityChangedCalled = now; + mShowing = showing; + } + /** * Called when visibility of lockscreen clock changes, such as when * obscured by a widget. diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java index fd7cae6..6aa0a4b 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewManager.java @@ -61,9 +61,11 @@ import android.widget.FrameLayout; public class KeyguardViewManager { private final static boolean DEBUG = KeyguardViewMediator.DEBUG; private static String TAG = "KeyguardViewManager"; - public static boolean USE_UPPER_CASE = true; public final static String IS_SWITCHING_USER = "is_switching_user"; + // Delay dismissing keyguard to allow animations to complete. + private static final int HIDE_KEYGUARD_DELAY = 500; + // Timeout used for keypresses static final int DIGIT_PRESS_WAKE_MILLIS = 5000; @@ -509,9 +511,10 @@ public class KeyguardViewManager { mKeyguardHost.setCustomBackground(null); updateShowWallpaper(true); mKeyguardHost.removeView(lastView); + mViewMediatorCallback.keyguardGone(); } } - }, 500); + }, HIDE_KEYGUARD_DELAY); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java index a37a3a4..4086f84 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewMediator.java @@ -200,7 +200,7 @@ public class KeyguardViewMediator { // cached value of whether we are showing (need to know this to quickly // answer whether the input should be restricted) - private boolean mShowing = false; + private boolean mShowing; // true if the keyguard is hidden by another window private boolean mHidden = false; @@ -253,6 +253,11 @@ public class KeyguardViewMediator { private final float mLockSoundVolume; /** + * For managing external displays + */ + private KeyguardDisplayManager mKeyguardDisplayManager; + + /** * Cache of avatar drawables, for use by KeyguardMultiUserAvatar. */ private static MultiUserAvatarCache sMultiUserAvatarCache = new MultiUserAvatarCache(); @@ -304,6 +309,11 @@ public class KeyguardViewMediator { * Report that the keyguard is dismissable, pending the next keyguardDone call. */ void keyguardDonePending(); + + /** + * Report when keyguard is actually gone + */ + void keyguardGone(); } KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() { @@ -457,6 +467,11 @@ public class KeyguardViewMediator { public void keyguardDonePending() { mKeyguardDonePending = true; } + + @Override + public void keyguardGone() { + mKeyguardDisplayManager.hide(); + } }; private void userActivity() { @@ -483,6 +498,8 @@ public class KeyguardViewMediator { mContext.registerReceiver(mBroadcastReceiver, new IntentFilter(DELAYED_KEYGUARD_ACTION)); + mKeyguardDisplayManager = new KeyguardDisplayManager(context); + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); mUpdateMonitor = KeyguardUpdateMonitor.getInstance(context); @@ -491,6 +508,10 @@ public class KeyguardViewMediator { ? lockPatternUtils : new LockPatternUtils(mContext); mLockPatternUtils.setCurrentUser(UserHandle.USER_OWNER); + // Assume keyguard is showing (unless it's disabled) until we know for sure... + mShowing = (mUpdateMonitor.isDeviceProvisioned() || mLockPatternUtils.isSecure()) + && !mLockPatternUtils.isLockScreenDisabled(); + WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); mKeyguardViewManager = new KeyguardViewManager(context, wm, mViewMediatorCallback, @@ -530,9 +551,6 @@ public class KeyguardViewMediator { mSystemReady = true; mUpdateMonitor.registerCallback(mUpdateCallback); - // Send boot completed message if it hasn't already been sent. - mUpdateMonitor.dispatchBootCompleted(); - // Suppress biometric unlock right after boot until things have settled if it is the // selected security method, otherwise unsuppress it. It must be unsuppressed if it is // not the selected security method for the following reason: if the user starts @@ -1221,6 +1239,7 @@ public class KeyguardViewMediator { mShowKeyguardWakeLock.release(); } + mKeyguardDisplayManager.show(); } /** @@ -1366,4 +1385,8 @@ public class KeyguardViewMediator { Message msg = mHandler.obtainMessage(LAUNCH_CAMERA); mHandler.sendMessage(msg); } + + public void onBootCompleted() { + mUpdateMonitor.dispatchBootCompleted(); + } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java index 8e39628..169899f 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardViewStateManager.java @@ -15,6 +15,9 @@ */ package com.android.keyguard; +import android.animation.Animator; +import android.animation.Animator.AnimatorListener; +import android.animation.AnimatorListenerAdapter; import android.os.Handler; import android.os.Looper; import android.util.Log; @@ -46,6 +49,20 @@ public class KeyguardViewStateManager implements int mChallengeTop = 0; + private final AnimatorListener mPauseListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + mKeyguardSecurityContainer.onPause(); + } + }; + + private final AnimatorListener mResumeListener = new AnimatorListenerAdapter() { + public void onAnimationEnd(Animator animation) { + if (((View)mKeyguardSecurityContainer).isShown()) { + mKeyguardSecurityContainer.onResume(0); + } + } + }; + public KeyguardViewStateManager(KeyguardHostView hostView) { mKeyguardHostView = hostView; } @@ -102,20 +119,20 @@ public class KeyguardViewStateManager implements } public void fadeOutSecurity(int duration) { - ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration).start(); + ((View) mKeyguardSecurityContainer).animate().alpha(0f).setDuration(duration) + .setListener(mPauseListener); } public void fadeInSecurity(int duration) { - ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration).start(); + ((View) mKeyguardSecurityContainer).animate().alpha(1f).setDuration(duration) + .setListener(mResumeListener); } public void onPageBeginMoving() { if (mChallengeLayout.isChallengeOverlapping() && mChallengeLayout instanceof SlidingChallengeLayout) { SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; - if (!mKeyguardWidgetPager.isWarping()) { - scl.fadeOutChallenge(); - } + scl.fadeOutChallenge(); mPageIndexOnPageBeginMoving = mKeyguardWidgetPager.getCurrentPage(); } // We use mAppWidgetToShow to show a particular widget after you add it-- @@ -137,11 +154,12 @@ public class KeyguardViewStateManager implements public void onPageSwitching(View newPage, int newPageIndex) { if (mKeyguardWidgetPager != null && mChallengeLayout instanceof SlidingChallengeLayout) { boolean isCameraPage = newPage instanceof CameraWidgetFrame; - SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; - scl.setChallengeInteractive(!isCameraPage); if (isCameraPage) { - scl.fadeOutChallenge(); + CameraWidgetFrame camera = (CameraWidgetFrame) newPage; + camera.setUseFastTransition(mKeyguardWidgetPager.isWarping()); } + SlidingChallengeLayout scl = (SlidingChallengeLayout) mChallengeLayout; + scl.setChallengeInteractive(!isCameraPage); final int currentFlags = mKeyguardWidgetPager.getSystemUiVisibility(); final int newFlags = isCameraPage ? (currentFlags | View.STATUS_BAR_DISABLE_SEARCH) : (currentFlags & ~View.STATUS_BAR_DISABLE_SEARCH); @@ -178,7 +196,7 @@ public class KeyguardViewStateManager implements boolean challengeOverlapping = mChallengeLayout.isChallengeOverlapping(); if (challengeOverlapping && !newCurPage.isSmall() && mPageListeningToSlider != newPageIndex) { - newCurPage.shrinkWidget(); + newCurPage.shrinkWidget(true); } } diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java index ab8a759..8ee9b61 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetFrame.java @@ -375,10 +375,6 @@ public class KeyguardWidgetFrame extends FrameLayout { return mSmallFrameHeight; } - public void shrinkWidget() { - shrinkWidget(true); - } - public void setWidgetLockedSmall(boolean locked) { if (locked) { setWidgetHeight(mSmallWidgetHeight); diff --git a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java index e07e0d0..99f7757 100644 --- a/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java +++ b/packages/Keyguard/src/com/android/keyguard/KeyguardWidgetPager.java @@ -40,6 +40,7 @@ import android.view.accessibility.AccessibilityManager; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.TextClock; + import com.android.internal.widget.LockPatternUtils; import java.util.ArrayList; @@ -193,7 +194,9 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit @Override public void onPageEndWarp() { - hideOutlinesAndSidePages(); + // if we're moving to the warp page, then immediately hide the other widgets. + int duration = getPageWarpIndex() == getNextPage() ? 0 : -1; + animateOutlinesAndSidePages(false, duration); mViewStateManager.onPageEndWarp(); } @@ -668,7 +671,7 @@ public class KeyguardWidgetPager extends PagedView implements PagedView.PageSwit // On the very first measure pass, if the challenge is showing, we need to make sure // that the widget on the current page is small. if (challengeShowing && i == mCurrentPage && !mHasMeasure) { - frame.shrinkWidget(); + frame.shrinkWidget(true); } } } diff --git a/packages/Keyguard/src/com/android/keyguard/PagedView.java b/packages/Keyguard/src/com/android/keyguard/PagedView.java index 9d237dc..53c17a5 100644 --- a/packages/Keyguard/src/com/android/keyguard/PagedView.java +++ b/packages/Keyguard/src/com/android/keyguard/PagedView.java @@ -82,13 +82,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private static final float RETURN_TO_ORIGINAL_PAGE_THRESHOLD = 0.33f; // The page is moved more than halfway, automatically move to the next page on touch up. - private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.4f; + private static final float SIGNIFICANT_MOVE_THRESHOLD = 0.5f; // The following constants need to be scaled based on density. The scaled versions will be // assigned to the corresponding member variables below. - private static final int FLING_THRESHOLD_VELOCITY = 500; + private static final int FLING_THRESHOLD_VELOCITY = 1500; private static final int MIN_SNAP_VELOCITY = 1500; - private static final int MIN_FLING_VELOCITY = 250; + private static final int MIN_FLING_VELOCITY = 500; // We are disabling touch interaction of the widget region for factory ROM. private static final boolean DISABLE_TOUCH_INTERACTION = false; @@ -267,6 +267,8 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private boolean mIsCameraEvent; private float mWarpPeekAmount; + private boolean mOnPageEndWarpCalled; + private boolean mOnPageBeginWarpCalled; public interface PageSwitchListener { void onPageSwitching(View newPage, int newPageIndex); @@ -491,7 +493,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (!mIsPageMoving) { mIsPageMoving = true; if (isWarping()) { - onPageBeginWarp(); + dispatchOnPageBeginWarp(); if (mPageSwapIndex != -1) { swapPages(mPageSwapIndex, mPageWarpIndex); } @@ -500,6 +502,22 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } } + private void dispatchOnPageBeginWarp() { + if (!mOnPageBeginWarpCalled) { + onPageBeginWarp(); + mOnPageBeginWarpCalled = true; + } + mOnPageEndWarpCalled = false; + } + + private void dispatchOnPageEndWarp() { + if (!mOnPageEndWarpCalled) { + onPageEndWarp(); + mOnPageEndWarpCalled = true; + } + mOnPageBeginWarpCalled = false; + } + protected void pageEndMoving() { if (DEBUG_WARP) Log.v(TAG, "pageEndMoving(" + mIsPageMoving + ")"); if (mIsPageMoving) { @@ -508,7 +526,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc if (mPageSwapIndex != -1) { swapPages(mPageSwapIndex, mPageWarpIndex); } - onPageEndWarp(); + dispatchOnPageEndWarp(); resetPageWarp(); } onPageEndMoving(); @@ -1919,11 +1937,13 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc } if (isWarping()) { - onPageEndWarp(); + dispatchOnPageEndWarp(); + notifyPageSwitching(whichPage); resetPageWarp(); + } else { + notifyPageSwitching(whichPage); } - notifyPageSwitching(whichPage); View focusedChild = getFocusedChild(); if (focusedChild != null && whichPage != mCurrentPage && focusedChild == getPageAt(mCurrentPage)) { @@ -2260,11 +2280,11 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc mTempVisiblePagesRange[0] = 0; mTempVisiblePagesRange[1] = getPageCount() - 1; boundByReorderablePages(true, mTempVisiblePagesRange); - mReorderingStarted = true; // Check if we are within the reordering range if (mTempVisiblePagesRange[0] <= dragViewIndex && dragViewIndex <= mTempVisiblePagesRange[1]) { + mReorderingStarted = true; if (zoomOut()) { // Find the drag view under the pointer mDragView = getChildAt(dragViewIndex); @@ -2702,12 +2722,12 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc @Override public void onAnimationEnd(Animator animation) { mWarpAnimation = null; - mWarpPageExposed = true; + mWarpPageExposed = false; } }; private void cancelWarpAnimation(String msg, boolean abortAnimation) { - if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ")"); + if (DEBUG_WARP) Log.v(TAG, "cancelWarpAnimation(" + msg + ",abort=" + abortAnimation + ")"); if (abortAnimation) { // We're done with the animation and moving to a new page. Let the scroller // take over the animation. @@ -2727,9 +2747,9 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private void animateWarpPageOnScreen(String reason) { if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOnScreen(" + reason + ")"); - if (isWarping()) { + if (isWarping() && !mWarpPageExposed) { mWarpPageExposed = true; - onPageBeginWarp(); + dispatchOnPageBeginWarp(); KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex); if (DEBUG_WARP) Log.v(TAG, "moving page on screen: Tx=" + v.getTranslationX()); DecelerateInterpolator interp = new DecelerateInterpolator(1.5f); @@ -2744,7 +2764,7 @@ public abstract class PagedView extends ViewGroup implements ViewGroup.OnHierarc private void animateWarpPageOffScreen(String reason, boolean animate) { if (DEBUG_WARP) Log.v(TAG, "animateWarpPageOffScreen(" + reason + " anim:" + animate + ")"); if (isWarping()) { - onPageEndWarp(); + dispatchOnPageEndWarp(); KeyguardWidgetFrame v = (KeyguardWidgetFrame) getPageAt(mPageWarpIndex); if (DEBUG_WARP) Log.v(TAG, "moving page off screen: Tx=" + v.getTranslationX()); AccelerateInterpolator interp = new AccelerateInterpolator(1.5f); diff --git a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java index 7a9a1c8..3d515ce 100644 --- a/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java +++ b/packages/Keyguard/src/com/android/keyguard/SlidingChallengeLayout.java @@ -1003,6 +1003,16 @@ public class SlidingChallengeLayout extends ViewGroup implements ChallengeLayout } } + @Override + protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) { + // Focus security fileds before widgets. + if (mChallengeView != null && + mChallengeView.requestFocus(direction, previouslyFocusedRect)) { + return true; + } + return super.onRequestFocusInDescendants(direction, previouslyFocusedRect); + } + public void computeScroll() { super.computeScroll(); diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml index d503216..3303ef1 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_container.xml @@ -16,7 +16,7 @@ <com.android.printspooler.PrintDialogFrame xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" - android:layout_height="fill_parent"> + android:layout_height="wrap_content"> <FrameLayout android:id="@+id/content_container" android:layout_width="fill_parent" diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml index 02740a3..e50a7af 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_editing.xml @@ -18,8 +18,7 @@ android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:scrollbars="vertical" - android:background="@color/editable_background"> + android:scrollbars="vertical"> <LinearLayout android:layout_width="fill_parent" @@ -42,6 +41,7 @@ <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" + android:layout_marginBottom="24dip" android:orientation="horizontal" android:baselineAligned="false"> @@ -203,27 +203,79 @@ </LinearLayout> + <!-- Advanced settings button --> + + <LinearLayout + android:id="@+id/advanced_settings_container" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:visibility="gone"> + + <ImageView + android:layout_width="fill_parent" + android:layout_height="1dip" + android:layout_marginStart="24dip" + android:layout_marginEnd="24dip" + android:layout_gravity="fill_horizontal" + android:background="@color/separator" + android:contentDescription="@null"> + </ImageView> + + <Button + android:id="@+id/advanced_settings_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_marginStart="24dip" + android:layout_marginEnd="24dip" + android:layout_gravity="fill_horizontal" + android:text="@string/advanced_settings_button" + android:gravity="start|center_vertical" + android:textSize="16sp" + android:textColor="@color/item_text_color"> + </Button> + + <ImageView + android:layout_width="fill_parent" + android:layout_height="1dip" + android:layout_gravity="fill_horizontal" + android:layout_marginStart="24dip" + android:layout_marginEnd="24dip" + android:background="@color/separator" + android:contentDescription="@null"> + </ImageView> + + </LinearLayout> + <!-- Print button --> - <ImageView + <FrameLayout android:layout_width="fill_parent" - android:layout_height="1dip" + android:layout_height="wrap_content" android:layout_marginTop="24dip" - android:layout_gravity="fill_horizontal" - android:background="@color/separator" - android:contentDescription="@null"> - </ImageView> + android:background="@color/action_button_background"> + + <ImageView + android:layout_width="fill_parent" + android:layout_height="1dip" + android:layout_gravity="fill_horizontal" + android:background="@color/separator" + android:contentDescription="@null"> + </ImageView> + + <Button + android:id="@+id/print_button" + style="?android:attr/buttonBarButtonStyle" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_gravity="fill_horizontal" + android:text="@string/print_button" + android:textSize="16sp" + android:textColor="@color/item_text_color"> + </Button> - <Button - android:id="@+id/print_button" - style="?android:attr/buttonBarButtonStyle" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_horizontal" - android:text="@string/print_button" - android:textSize="16sp" - android:textColor="@color/item_text_color"> - </Button> + </FrameLayout> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml index 222b5b6..d9f0a9a 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_error.xml @@ -23,7 +23,6 @@ <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" - android:background="@color/editable_background" android:orientation="vertical"> <TextView @@ -36,30 +35,36 @@ android:layout_marginBottom="32dip" android:layout_gravity="center" style="?android:attr/buttonBarButtonStyle" - android:singleLine="true" android:ellipsize="end" android:text="@string/print_error_default_message" android:textColor="@color/important_text" android:textSize="16sp"> </TextView> + </LinearLayout> + + <FrameLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="@color/action_button_background"> + <View android:layout_width="fill_parent" android:layout_height="1dip" android:background="@color/separator"> </View> - </LinearLayout> + <Button + android:id="@+id/ok_button" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_gravity="fill_horizontal" + style="?android:attr/buttonBarButtonStyle" + android:text="@android:string/ok" + android:textSize="16sp" + android:textColor="@color/important_text"> + </Button> - <Button - android:id="@+id/ok_button" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_horizontal" - style="?android:attr/buttonBarButtonStyle" - android:text="@android:string/ok" - android:textSize="16sp" - android:textColor="@color/important_text"> - </Button> + </FrameLayout> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml index 8bdb6c9..10602ee 100644 --- a/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml +++ b/packages/PrintSpooler/res/layout/print_job_config_activity_content_generating.xml @@ -23,7 +23,6 @@ <LinearLayout android:layout_width="fill_parent" android:layout_height="fill_parent" - android:background="@color/editable_background" android:orientation="vertical"> <TextView @@ -51,23 +50,30 @@ style="?android:attr/progressBarStyleLarge"> </ProgressBar> + </LinearLayout> + + <FrameLayout + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:background="@color/action_button_background"> + <View android:layout_width="fill_parent" android:layout_height="1dip" android:background="@color/separator"> </View> - </LinearLayout> + <Button + android:id="@+id/cancel_button" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_gravity="fill_horizontal" + style="?android:attr/buttonBarButtonStyle" + android:text="@string/cancel" + android:textSize="16sp" + android:textColor="@color/important_text"> + </Button> - <Button - android:id="@+id/cancel_button" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_gravity="fill_horizontal" - style="?android:attr/buttonBarButtonStyle" - android:text="@string/cancel" - android:textSize="16sp" - android:textColor="@color/important_text"> - </Button> + </FrameLayout> </LinearLayout> diff --git a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml index 2749aa6..1a61b99 100644 --- a/packages/PrintSpooler/res/layout/printer_dropdown_item.xml +++ b/packages/PrintSpooler/res/layout/printer_dropdown_item.xml @@ -15,13 +15,13 @@ --> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:paddingStart="16dip" - android:paddingEnd="16dip" - android:minHeight="?android:attr/listPreferredItemHeightSmall" - android:orientation="horizontal" - android:gravity="start|center_vertical"> + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingStart="16dip" + android:paddingEnd="16dip" + android:minHeight="?android:attr/listPreferredItemHeightSmall" + android:orientation="horizontal" + android:gravity="start|center_vertical"> <ImageView android:id="@+id/icon" @@ -31,7 +31,7 @@ android:layout_marginEnd="8dip" android:duplicateParentState="true" android:contentDescription="@null" - android:visibility="gone"> + android:visibility="invisible"> </ImageView> <LinearLayout diff --git a/packages/PrintSpooler/res/layout/printer_list_item.xml b/packages/PrintSpooler/res/layout/printer_list_item.xml new file mode 100644 index 0000000..47eb0b5 --- /dev/null +++ b/packages/PrintSpooler/res/layout/printer_list_item.xml @@ -0,0 +1,71 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" + android:minHeight="?android:attr/listPreferredItemHeight" + android:orientation="horizontal" + android:gravity="start|center_vertical"> + + <ImageView + android:id="@+id/icon" + android:layout_width="32dip" + android:layout_height="32dip" + android:layout_gravity="center_vertical" + android:layout_marginEnd="8dip" + android:duplicateParentState="true" + android:contentDescription="@null" + android:visibility="gone"> + </ImageView> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="vertical" + android:duplicateParentState="true"> + + <TextView + android:id="@+id/title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:singleLine="true" + android:ellipsize="end" + android:textIsSelectable="false" + android:gravity="top|start" + android:textColor="@color/item_text_color" + android:duplicateParentState="true"> + </TextView> + + <TextView + android:id="@+id/subtitle" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall" + android:singleLine="true" + android:ellipsize="end" + android:textIsSelectable="false" + android:visibility="gone" + android:textColor="@color/print_option_title" + android:duplicateParentState="true"> + </TextView> + + </LinearLayout> + +</LinearLayout> diff --git a/packages/PrintSpooler/res/layout/select_printer_fragment.xml b/packages/PrintSpooler/res/layout/select_printer_fragment.xml new file mode 100644 index 0000000..bbd012e --- /dev/null +++ b/packages/PrintSpooler/res/layout/select_printer_fragment.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2013 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. +--> + +<ListView xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/list" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:paddingStart="@dimen/printer_list_view_padding_start" + android:paddingEnd="@dimen/printer_list_view_padding_end" + android:scrollbarStyle="outsideOverlay" + android:cacheColorHint="@android:color/transparent" + android:scrollbarAlwaysDrawVerticalTrack="true" > +</ListView> diff --git a/packages/PrintSpooler/res/values-ja/arrays.xml b/packages/PrintSpooler/res/values-ja/arrays.xml index 3364979..3187cbe 100644 --- a/packages/PrintSpooler/res/values-ja/arrays.xml +++ b/packages/PrintSpooler/res/values-ja/arrays.xml @@ -20,13 +20,13 @@ <item>JIS_B9</item> <item>JIS_B8</item> <item>JIS_B7</item> - <item>JIS_b6</item> - <item>JIS_b5</item> - <item>JIS_b4</item> - <item>JIS_b3</item> - <item>JIS_b2</item> - <item>JIS_b1</item> - <item>JIS_b0</item> + <item>JIS_B6</item> + <item>JIS_B5</item> + <item>JIS_B4</item> + <item>JIS_B3</item> + <item>JIS_B2</item> + <item>JIS_B1</item> + <item>JIS_B0</item> <item>JIS_EXEC</item> <item>JPN_CHOU4</item> <item>JPN_CHOU3</item> diff --git a/packages/Keyguard/res/drawable/lockscreen_forgot_password_button.xml b/packages/PrintSpooler/res/values-land/constants.xml index 6c081bf..d68b77e 100644 --- a/packages/Keyguard/res/drawable/lockscreen_forgot_password_button.xml +++ b/packages/PrintSpooler/res/values-land/constants.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<!-- Copyright (C) 2008 The Android Open Source Project +<!-- Copyright (C) 2013 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. @@ -14,8 +14,9 @@ limitations under the License. --> -<selector xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:state_enabled="true" android:drawable="@drawable/ic_lockscreen_forgotpassword_normal" /> - <item android:state_pressed="true" android:drawable="@drawable/ic_lockscreen_forgotpassword_pressed" /> - <item android:drawable="@drawable/ic_lockscreen_forgotpassword_normal" /> -</selector> +<resources> + + <dimen name="printer_list_view_padding_start">48dip</dimen> + <dimen name="printer_list_view_padding_end">48dip</dimen> + +</resources> diff --git a/packages/PrintSpooler/res/values/colors.xml b/packages/PrintSpooler/res/values/colors.xml index 9972c96..4fc25b3 100644 --- a/packages/PrintSpooler/res/values/colors.xml +++ b/packages/PrintSpooler/res/values/colors.xml @@ -16,10 +16,10 @@ <resources> - <color name="container_background">#FFFFFF</color> + <color name="container_background">#F2F2F2</color> <color name="important_text">#333333</color> <color name="print_option_title">#888888</color> <color name="separator">#CCCCCC</color> - <color name="editable_background">#F2F2F2</color> + <color name="action_button_background">#FFFFFF</color> -</resources>
\ No newline at end of file +</resources> diff --git a/packages/PrintSpooler/res/values/constants.xml b/packages/PrintSpooler/res/values/constants.xml index e5a9d5d..e9c925c 100644 --- a/packages/PrintSpooler/res/values/constants.xml +++ b/packages/PrintSpooler/res/values/constants.xml @@ -26,4 +26,7 @@ <dimen name="print_dialog_frame_max_width_dip">400dip</dimen> -</resources>
\ No newline at end of file + <dimen name="printer_list_view_padding_start">16dip</dimen> + <dimen name="printer_list_view_padding_end">16dip</dimen> + +</resources> diff --git a/packages/PrintSpooler/res/values/strings.xml b/packages/PrintSpooler/res/values/strings.xml index d74b414..d2613d0 100644 --- a/packages/PrintSpooler/res/values/strings.xml +++ b/packages/PrintSpooler/res/values/strings.xml @@ -19,6 +19,9 @@ <!-- Title of the PrintSpooler application. [CHAR LIMIT=50] --> <string name="app_label">Print Spooler</string> + <!-- Label of the print dialog's button for advanced printer settings. [CHAR LIMIT=25] --> + <string name="advanced_settings_button">Printer settings</string> + <!-- Label of the print dialog's print button. [CHAR LIMIT=16] --> <string name="print_button">Print</string> @@ -90,6 +93,12 @@ <!-- Title of the action bar button to got to add a printer. [CHAR LIMIT=25] --> <string name="print_add_printer">Add printer</string> + <!-- Title of the menu item to select a printer. [CHAR LIMIT=25] --> + <string name="print_select_printer">Select printer</string> + + <!-- Title of the menu item to forget a printer. [CHAR LIMIT=25] --> + <string name="print_forget_printer">Forget printer</string> + <!-- Utterance to announce a change in the number of matches during a search. This is spoken to a blind user. [CHAR LIMIT=none] --> <plurals name="print_search_result_count_utterance"> <item quantity="one"><xliff:g id="count" example="1">%1$s</xliff:g> printer found</item> diff --git a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java index 0601467..9831839 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java +++ b/packages/PrintSpooler/src/com/android/printspooler/FusedPrintersProvider.java @@ -79,6 +79,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private PrinterId mTrackedPrinter; + private boolean mPrintersUpdatedBefore; + public FusedPrintersProvider(Context context) { super(context); mPersistenceManager = new PersistenceManager(context); @@ -88,13 +90,14 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mPersistenceManager.addPrinterAndWritePrinterHistory(printer); } - private void computeAndDeliverResult(Map<PrinterId, PrinterInfo> discoveredPrinters) { + private void computeAndDeliverResult(ArrayMap<PrinterId, PrinterInfo> discoveredPrinters, + ArrayMap<PrinterId, PrinterInfo> favoritePrinters) { List<PrinterInfo> printers = new ArrayList<PrinterInfo>(); // Add the updated favorite printers. - final int favoritePrinterCount = mFavoritePrinters.size(); + final int favoritePrinterCount = favoritePrinters.size(); for (int i = 0; i < favoritePrinterCount; i++) { - PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + PrinterInfo favoritePrinter = favoritePrinters.valueAt(i); PrinterInfo updatedPrinter = discoveredPrinters.remove( favoritePrinter.getId()); if (updatedPrinter != null) { @@ -123,8 +126,11 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mPrinters.addAll(printers); if (isStarted()) { - // Deliver the printers. + // If stated deliver the new printers. deliverResult(printers); + } else { + // Otherwise, take a note for the change. + onContentChanged(); } } @@ -165,6 +171,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { .getSystemService(Context.PRINT_SERVICE); mDiscoverySession = printManager.createPrinterDiscoverySession(); mPersistenceManager.readPrinterHistory(); + } else if (mPersistenceManager.isHistoryChanged()) { + mPersistenceManager.readPrinterHistory(); } if (mPersistenceManager.isReadHistoryCompleted() && !mDiscoverySession.isPrinterDiscoveryStarted()) { @@ -176,7 +184,7 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { + mDiscoverySession.getPrinters().size() + " " + FusedPrintersProvider.this.hashCode()); } - updatePrinters(mDiscoverySession.getPrinters()); + updatePrinters(mDiscoverySession.getPrinters(), mFavoritePrinters); } }); final int favoriteCount = mFavoritePrinters.size(); @@ -187,15 +195,19 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mDiscoverySession.startPrinterDisovery(printerIds); List<PrinterInfo> printers = mDiscoverySession.getPrinters(); if (!printers.isEmpty()) { - updatePrinters(printers); + updatePrinters(printers, mFavoritePrinters); } } } - private void updatePrinters(List<PrinterInfo> printers) { - if (mPrinters.equals(printers)) { + private void updatePrinters(List<PrinterInfo> printers, List<PrinterInfo> favoritePrinters) { + if (mPrintersUpdatedBefore && mPrinters.equals(printers) + && mFavoritePrinters.equals(favoritePrinters)) { return; } + + mPrintersUpdatedBefore = true; + ArrayMap<PrinterId, PrinterInfo> printersMap = new ArrayMap<PrinterId, PrinterInfo>(); final int printerCount = printers.size(); @@ -203,7 +215,16 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { PrinterInfo printer = printers.get(i); printersMap.put(printer.getId(), printer); } - computeAndDeliverResult(printersMap); + + ArrayMap<PrinterId, PrinterInfo> favoritePrintersMap = + new ArrayMap<PrinterId, PrinterInfo>(); + final int favoritePrinterCount = favoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = favoritePrinters.get(i); + favoritePrintersMap.put(favoritePrinter.getId(), favoritePrinter); + } + + computeAndDeliverResult(printersMap, favoritePrintersMap); } @Override @@ -264,6 +285,42 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { } } + public boolean isFavoritePrinter(PrinterId printerId) { + final int printerCount = mFavoritePrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterInfo favoritePritner = mFavoritePrinters.get(i); + if (favoritePritner.getId().equals(printerId)) { + return true; + } + } + return false; + } + + public void forgetFavoritePrinter(PrinterId printerId) { + List<PrinterInfo> newFavoritePrinters = null; + + // Remove the printer from the favorites. + final int favoritePrinterCount = mFavoritePrinters.size(); + for (int i = 0; i < favoritePrinterCount; i++) { + PrinterInfo favoritePrinter = mFavoritePrinters.get(i); + if (favoritePrinter.getId().equals(printerId)) { + newFavoritePrinters = new ArrayList<PrinterInfo>(); + newFavoritePrinters.addAll(mPrinters); + newFavoritePrinters.remove(i); + break; + } + } + + // If we removed a favorite printer, we have work to do. + if (newFavoritePrinters != null) { + // Remove the printer from history and persist the latter. + mPersistenceManager.removeHistoricalPrinterAndWritePrinterHistory(printerId); + + // Recompute and deliver the printers. + updatePrinters(mDiscoverySession.getPrinters(), newFavoritePrinters); + } + } + private final class PersistenceManager { private static final String PERSIST_FILE_NAME = "printer_history.xml"; @@ -281,13 +338,15 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { private final AtomicFile mStatePersistFile; - private List<PrinterInfo> mHistoricalPrinters; + private List<PrinterInfo> mHistoricalPrinters = new ArrayList<PrinterInfo>(); private boolean mReadHistoryCompleted; private boolean mReadHistoryInProgress; private ReadTask mReadTask; + private volatile long mLastReadHistoryTimestamp; + private PersistenceManager(Context context) { mStatePersistFile = new AtomicFile(new File(context.getFilesDir(), PERSIST_FILE_NAME)); @@ -327,6 +386,27 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { new ArrayList<PrinterInfo>(mHistoricalPrinters)); } + @SuppressWarnings("unchecked") + public void removeHistoricalPrinterAndWritePrinterHistory(PrinterId printerId) { + boolean writeHistory = false; + final int printerCount = mHistoricalPrinters.size(); + for (int i = printerCount - 1; i >= 0; i--) { + PrinterInfo historicalPrinter = mHistoricalPrinters.get(i); + if (historicalPrinter.getId().equals(printerId)) { + mHistoricalPrinters.remove(i); + writeHistory = true; + } + } + if (writeHistory) { + new WriteTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, + new ArrayList<PrinterInfo>(mHistoricalPrinters)); + } + } + + public boolean isHistoryChanged() { + return mLastReadHistoryTimestamp != mStatePersistFile.getBaseFile().lastModified(); + } + private List<PrinterInfo> computeFavoritePrinters(List<PrinterInfo> printers) { Map<PrinterId, PrinterRecord> recordMap = new ArrayMap<PrinterId, PrinterRecord>(); @@ -423,11 +503,10 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { mReadHistoryInProgress = false; mReadHistoryCompleted = true; - // Deliver the favorites. - Map<PrinterId, PrinterInfo> discoveredPrinters = Collections.emptyMap(); - computeAndDeliverResult(discoveredPrinters); + // Deliver the printers. + updatePrinters(mDiscoverySession.getPrinters(), mHistoricalPrinters); - // Start loading the available printers. + // Loading the available printers if needed. loadInternal(); // We are done. @@ -450,6 +529,8 @@ public class FusedPrintersProvider extends Loader<List<PrinterInfo>> { XmlPullParser parser = Xml.newPullParser(); parser.setInput(in, null); parseState(parser, printers); + // Take a note which version of the history was read. + mLastReadHistoryTimestamp = mStatePersistFile.getBaseFile().lastModified(); return printers; } catch (IllegalStateException ise) { Slog.w(LOG_TAG, "Failed parsing ", ise); diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java index 2997707..88403a3 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintJobConfigActivity.java @@ -19,6 +19,7 @@ package com.android.printspooler; import android.app.Activity; import android.app.Dialog; import android.app.LoaderManager; +import android.content.ActivityNotFoundException; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -26,6 +27,8 @@ import android.content.Loader; import android.content.ServiceConnection; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.database.DataSetObserver; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -40,6 +43,7 @@ import android.os.Message; import android.os.RemoteException; import android.print.ILayoutResultCallback; import android.print.IPrintDocumentAdapter; +import android.print.IPrintDocumentAdapterObserver; import android.print.IWriteResultCallback; import android.print.PageRange; import android.print.PrintAttributes; @@ -54,6 +58,8 @@ import android.print.PrintManager; import android.print.PrinterCapabilitiesInfo; import android.print.PrinterId; import android.print.PrinterInfo; +import android.printservice.PrintService; +import android.printservice.PrintServiceInfo; import android.provider.DocumentsContract; import android.text.Editable; import android.text.TextUtils; @@ -129,6 +135,7 @@ public class PrintJobConfigActivity extends Activity { private static final int ACTIVITY_REQUEST_CREATE_FILE = 1; private static final int ACTIVITY_REQUEST_SELECT_PRINTER = 2; + private static final int ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS = 3; private static final int CONTROLLER_STATE_FINISHED = 1; private static final int CONTROLLER_STATE_FAILED = 2; @@ -201,6 +208,14 @@ public class PrintJobConfigActivity extends Activity { throw new IllegalArgumentException("PrintDocumentAdapter cannot be null"); } + try { + IPrintDocumentAdapter.Stub.asInterface(mIPrintDocumentAdapter) + .setObserver(new PrintDocumentAdapterObserver(this)); + } catch (RemoteException re) { + finish(); + return; + } + PrintAttributes attributes = printJob.getAttributes(); if (attributes != null) { mCurrPrintAttributes.copyFrom(attributes); @@ -245,31 +260,32 @@ public class PrintJobConfigActivity extends Activity { } @Override - protected void onDestroy() { - // We can safely do the work in here since at this point - // the system is bound to our (spooler) process which - // guarantees that this process will not be killed. - if (mController.hasStarted()) { - mController.finish(); - } - if (mEditor.isPrintConfirmed() && mController.isFinished()) { - mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_QUEUED, null); - } else { - mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, - PrintJobInfo.STATE_CANCELED, null); - } - mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); - if (mGeneratingPrintJobDialog != null) { - mGeneratingPrintJobDialog.dismiss(); - mGeneratingPrintJobDialog = null; - } - mSpoolerProvider.destroy(); - super.onDestroy(); + public void onPause() { + if (isFinishing()) { + if (mController != null && mController.hasStarted()) { + mController.finish(); + } + if (mEditor != null && mEditor.isPrintConfirmed() + && mController != null && mController.isFinished()) { + mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_QUEUED, null); + } else { + mSpoolerProvider.getSpooler().setPrintJobState(mPrintJobId, + PrintJobInfo.STATE_CANCELED, null); + } + if (mGeneratingPrintJobDialog != null) { + mGeneratingPrintJobDialog.dismiss(); + mGeneratingPrintJobDialog = null; + } + mIPrintDocumentAdapter.unlinkToDeath(mDeathRecipient, 0); + mSpoolerProvider.destroy(); + } + super.onPause(); } public boolean onTouchEvent(MotionEvent event) { - if (!mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) { + if (mController != null && mEditor != null && + !mEditor.isPrintConfirmed() && mEditor.shouldCloseOnTouch(event)) { if (!mController.isWorking()) { PrintJobConfigActivity.this.finish(); } @@ -287,17 +303,19 @@ public class PrintJobConfigActivity extends Activity { } public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - if (mEditor.isShwoingGeneratingPrintJobUi()) { - return true; - } - if (event.isTracking() && !event.isCanceled()) { - if (!mController.isWorking()) { - PrintJobConfigActivity.this.finish(); + if (mController != null && mEditor != null) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (mEditor.isShwoingGeneratingPrintJobUi()) { + return true; + } + if (event.isTracking() && !event.isCanceled()) { + if (!mController.isWorking()) { + PrintJobConfigActivity.this.finish(); + } } + mEditor.cancel(); + return true; } - mEditor.cancel(); - return true; } return super.onKeyUp(keyCode, event); } @@ -339,6 +357,9 @@ public class PrintJobConfigActivity extends Activity { } public void cancel() { + if (isWorking()) { + mRemotePrintAdapter.cancel(); + } mControllerState = CONTROLLER_STATE_CANCELLED; } @@ -594,21 +615,17 @@ public class PrintJobConfigActivity extends Activity { } else { // We did not get the pages we requested, then the application // misbehaves, so we fail quickly. - // TODO: We need some UI for announcing an error. mControllerState = CONTROLLER_STATE_FAILED; Log.e(LOG_TAG, "Received invalid pages from the app"); - mEditor.cancel(); - PrintJobConfigActivity.this.finish(); + mEditor.showUi(Editor.UI_ERROR, null); } } private void requestCreatePdfFileOrFinish() { if (mEditor.isPrintingToPdf()) { - PrintJobInfo printJob = mSpoolerProvider.getSpooler() - .getPrintJobInfo(mPrintJobId, PrintManager.APP_ID_ANY); Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); intent.setType("application/pdf"); - intent.putExtra(Intent.EXTRA_TITLE, printJob.getLabel()); + intent.putExtra(Intent.EXTRA_TITLE, mDocument.info.getName()); intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, mCallingPackageName); startActivityForResult(intent, ACTIVITY_REQUEST_CREATE_FILE); } else { @@ -778,6 +795,19 @@ public class PrintJobConfigActivity extends Activity { } mEditor.ensureCurrentPrinterSelected(); } break; + + case ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS: { + if (resultCode == RESULT_OK) { + PrintJobInfo printJobInfo = (PrintJobInfo) data.getParcelableExtra( + PrintService.EXTRA_PRINT_JOB_INFO); + if (printJobInfo != null) { + mEditor.updateFromAdvancedOptions(printJobInfo); + break; + } + } + mEditor.cancel(); + PrintJobConfigActivity.this.finish(); + } break; } } @@ -856,6 +886,10 @@ public class PrintJobConfigActivity extends Activity { private View mContentContainer; + private View mAdvancedPrintOptionsContainer; + + private Button mAdvancedOptionsButton; + private Button mPrintButton; private PrinterId mNextPrinterId; @@ -903,6 +937,7 @@ public class PrintJobConfigActivity extends Activity { mPrintJobId, mCurrentPrinter); if (mCurrentPrinter.getStatus() == PrinterInfo.STATUS_UNAVAILABLE) { + mCapabilitiesTimeout.post(); updateUi(); return; } @@ -919,6 +954,10 @@ public class PrintJobConfigActivity extends Activity { refreshCurrentPrinter(); } } else if (spinner == mMediaSizeSpinner) { + if (mIgnoreNextMediaSizeChange) { + mIgnoreNextMediaSizeChange = false; + return; + } if (mOldMediaSizeSelectionIndex == mMediaSizeSpinner.getSelectedItemPosition()) { mOldMediaSizeSelectionIndex = AdapterView.INVALID_POSITION; @@ -934,6 +973,10 @@ public class PrintJobConfigActivity extends Activity { mController.update(); } } else if (spinner == mColorModeSpinner) { + if (mIgnoreNextColorChange) { + mIgnoreNextColorChange = false; + return; + } if (mOldColorModeSelectionIndex == mColorModeSpinner.getSelectedItemPosition()) { mOldColorModeSelectionIndex = AdapterView.INVALID_POSITION; @@ -1180,6 +1223,16 @@ public class PrintJobConfigActivity extends Activity { // greater than the to page. When computing the requested pages // we just swap them if necessary. + // Keep the print job up to date with the selected pages if we + // know how many pages are there in the document. + PageRange[] requestedPages = getRequestedPages(); + if (requestedPages != null && requestedPages.length > 0 + && requestedPages[requestedPages.length - 1].getEnd() + < mDocument.info.getPageCount()) { + mSpoolerProvider.getSpooler().setPrintJobPagesNoPersistence( + mPrintJobId, requestedPages); + } + mPageRangeEditText.setError(null); mPrintButton.setEnabled(true); updateUi(); @@ -1202,6 +1255,8 @@ public class PrintJobConfigActivity extends Activity { private boolean mIgnoreNextRangeOptionChange; private boolean mIgnoreNextCopiesChange; private boolean mIgnoreNextRangeChange; + private boolean mIgnoreNextMediaSizeChange; + private boolean mIgnoreNextColorChange; private int mCurrentUi = UI_NONE; @@ -1411,6 +1466,88 @@ public class PrintJobConfigActivity extends Activity { } } + public void updateFromAdvancedOptions(PrintJobInfo printJobInfo) { + boolean updateContent = false; + + // Copies. + mCopiesEditText.setText(String.valueOf(printJobInfo.getCopies())); + + // Media size and orientation + PrintAttributes attributes = printJobInfo.getAttributes(); + if (!mCurrPrintAttributes.getMediaSize().equals(attributes.getMediaSize())) { + final int mediaSizeCount = mMediaSizeSpinnerAdapter.getCount(); + for (int i = 0; i < mediaSizeCount; i++) { + MediaSize mediaSize = mMediaSizeSpinnerAdapter.getItem(i).value; + if (mediaSize.asPortrait().equals(attributes.getMediaSize().asPortrait())) { + updateContent = true; + mCurrPrintAttributes.setMediaSize(attributes.getMediaSize()); + mMediaSizeSpinner.setSelection(i); + mIgnoreNextMediaSizeChange = true; + if (attributes.getMediaSize().isPortrait()) { + mOrientationSpinner.setSelection(0); + mIgnoreNextOrientationChange = true; + } else { + mOrientationSpinner.setSelection(1); + mIgnoreNextOrientationChange = true; + } + break; + } + } + } + + // Color mode. + final int colorMode = attributes.getColorMode(); + if (mCurrPrintAttributes.getColorMode() != colorMode) { + if (colorMode == PrintAttributes.COLOR_MODE_MONOCHROME) { + updateContent = true; + mColorModeSpinner.setSelection(0); + mIgnoreNextColorChange = true; + mCurrPrintAttributes.setColorMode(attributes.getColorMode()); + } else if (colorMode == PrintAttributes.COLOR_MODE_COLOR) { + updateContent = true; + mColorModeSpinner.setSelection(1); + mIgnoreNextColorChange = true; + mCurrPrintAttributes.setColorMode(attributes.getColorMode()); + } + } + + // Range. + PageRange[] pageRanges = printJobInfo.getPages(); + if (pageRanges != null && pageRanges.length > 0) { + pageRanges = PageRangeUtils.normalize(pageRanges); + final int pageRangeCount = pageRanges.length; + if (pageRangeCount == 1 && pageRanges[0] == PageRange.ALL_PAGES) { + mRangeOptionsSpinner.setSelection(0); + } else { + final int pageCount = mDocument.info.getPageCount(); + if (pageRanges[0].getStart() >= 0 + && pageRanges[pageRanges.length - 1].getEnd() < pageCount) { + mRangeOptionsSpinner.setSelection(1); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < pageRangeCount; i++) { + if (builder.length() > 0) { + builder.append(','); + } + PageRange pageRange = pageRanges[i]; + builder.append(pageRange.getStart()); + builder.append('-'); + builder.append(pageRange.getEnd()); + } + mPageRangeEditText.setText(builder.toString()); + } + } + } + + // Update the advanced options. + mSpoolerProvider.getSpooler().setPrintJobAdvancedOptionsNoPersistence( + mPrintJobId, printJobInfo.getAdvancedOptions()); + + // Update the content if needed. + if (updateContent) { + mController.update(); + } + } + public void ensurePrinterSelected(PrinterId printerId) { // If the printer is not present maybe the loader is not // updated yet. In this case make a note and as soon as @@ -1596,6 +1733,44 @@ public class PrintJobConfigActivity extends Activity { } } + private void registerAdvancedPrintOptionsButtonClickListener() { + Button advancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button); + advancedOptionsButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + ComponentName serviceName = mCurrentPrinter.getId().getServiceName(); + String activityName = getAdvancedOptionsActivityName(serviceName); + if (TextUtils.isEmpty(activityName)) { + return; + } + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.setComponent(new ComponentName(serviceName.getPackageName(), + activityName)); + + List<ResolveInfo> resolvedActivities = getPackageManager() + .queryIntentActivities(intent, 0); + if (resolvedActivities.isEmpty()) { + return; + } + // The activity is a component name, therefore it is one or none. + if (resolvedActivities.get(0).activityInfo.exported) { + PrintJobInfo printJobInfo = mSpoolerProvider.getSpooler().getPrintJobInfo( + mPrintJobId, PrintManager.APP_ID_ANY); + intent.putExtra(PrintService.EXTRA_PRINT_JOB_INFO, printJobInfo); + // TODO: Make this an API for the next release. + intent.putExtra("android.intent.extra.print.EXTRA_PRINTER_INFO", + mCurrentPrinter); + try { + startActivityForResult(intent, + ACTIVITY_POPULATE_ADVANCED_PRINT_OPTIONS); + } catch (ActivityNotFoundException anfe) { + Log.e(LOG_TAG, "Error starting activity for intent: " + intent, anfe); + } + } + } + }); + } + private void registerPrintButtonClickListener() { Button printButton = (Button) findViewById(R.id.print_button); printButton.setOnClickListener(new OnClickListener() { @@ -1643,6 +1818,9 @@ public class PrintJobConfigActivity extends Activity { mEditor.initialize(); mEditor.bindUi(); mEditor.reselectCurrentPrinter(); + if (!mController.hasPerformedLayout()) { + mController.update(); + } } }); } @@ -1844,6 +2022,11 @@ public class PrintJobConfigActivity extends Activity { mPageRangeEditText.setOnFocusChangeListener(mFocusListener); mPageRangeEditText.addTextChangedListener(mRangeTextWatcher); + // Advanced options button. + mAdvancedPrintOptionsContainer = findViewById(R.id.advanced_settings_container); + mAdvancedOptionsButton = (Button) findViewById(R.id.advanced_settings_button); + registerAdvancedPrintOptionsButtonClickListener(); + // Print button mPrintButton = (Button) findViewById(R.id.print_button); registerPrintButtonClickListener(); @@ -1862,6 +2045,7 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinner.setEnabled(false); mPageRangeEditText.setEnabled(false); mPrintButton.setEnabled(false); + mAdvancedOptionsButton.setEnabled(false); return false; } @@ -1887,6 +2071,7 @@ public class PrintJobConfigActivity extends Activity { mRangeOptionsSpinner.setEnabled(false); mPageRangeEditText.setEnabled(false); mPrintButton.setEnabled(false); + mAdvancedOptionsButton.setEnabled(false); return false; } else { boolean someAttributeSelectionChanged = false; @@ -2064,7 +2249,17 @@ public class PrintJobConfigActivity extends Activity { mPageRangeTitle.setVisibility(View.INVISIBLE); } - // Print/Print preview + // Advanced print options + ComponentName serviceName = mCurrentPrinter.getId().getServiceName(); + if (!TextUtils.isEmpty(getAdvancedOptionsActivityName(serviceName))) { + mAdvancedPrintOptionsContainer.setVisibility(View.VISIBLE); + mAdvancedOptionsButton.setEnabled(true); + } else { + mAdvancedPrintOptionsContainer.setVisibility(View.GONE); + mAdvancedOptionsButton.setEnabled(false); + } + + // Print if (mDestinationSpinner.getSelectedItemId() != DEST_ADAPTER_ITEM_ID_SAVE_AS_PDF) { String newText = getString(R.string.print_button); @@ -2104,6 +2299,21 @@ public class PrintJobConfigActivity extends Activity { } } + private String getAdvancedOptionsActivityName(ComponentName serviceName) { + PrintManager printManager = (PrintManager) getSystemService(Context.PRINT_SERVICE); + List<PrintServiceInfo> printServices = printManager.getEnabledPrintServices(); + final int printServiceCount = printServices.size(); + for (int i = 0; i < printServiceCount; i ++) { + PrintServiceInfo printServiceInfo = printServices.get(i); + ServiceInfo serviceInfo = printServiceInfo.getResolveInfo().serviceInfo; + if (serviceInfo.name.equals(serviceName.getClassName()) + && serviceInfo.packageName.equals(serviceName.getPackageName())) { + return printServiceInfo.getAdvancedOptionsActivityName(); + } + } + return null; + } + private void setMediaSizeSpinnerSelectionNoCallback(int position) { if (mMediaSizeSpinner.getSelectedItemPosition() != position) { mOldMediaSizeSelectionIndex = position; @@ -2303,8 +2513,6 @@ public class PrintJobConfigActivity extends Activity { R.layout.printer_dropdown_item, parent, false); } - convertView.getLayoutParams().width = mDestinationSpinner.getWidth(); - CharSequence title = null; CharSequence subtitle = null; Drawable icon = null; @@ -2353,7 +2561,7 @@ public class PrintJobConfigActivity extends Activity { iconView.setImageDrawable(icon); iconView.setVisibility(View.VISIBLE); } else { - iconView.setVisibility(View.GONE); + iconView.setVisibility(View.INVISIBLE); } return convertView; @@ -2703,4 +2911,32 @@ public class PrintJobConfigActivity extends Activity { /* do noting - we are in the same process */ } } + + private static final class PrintDocumentAdapterObserver + extends IPrintDocumentAdapterObserver.Stub { + private final WeakReference<PrintJobConfigActivity> mWeakActvity; + + public PrintDocumentAdapterObserver(PrintJobConfigActivity activity) { + mWeakActvity = new WeakReference<PrintJobConfigActivity>(activity); + } + + @Override + public void onDestroy() { + final PrintJobConfigActivity activity = mWeakActvity.get(); + if (activity != null) { + activity.mController.mHandler.post(new Runnable() { + @Override + public void run() { + if (activity.mController != null) { + activity.mController.cancel(); + } + if (activity.mEditor != null) { + activity.mEditor.cancel(); + } + activity.finish(); + } + }); + } + } + } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java index 636e245..615d667 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java +++ b/packages/PrintSpooler/src/com/android/printspooler/PrintSpoolerService.java @@ -20,6 +20,7 @@ import android.app.Service; import android.content.ComponentName; import android.content.Intent; import android.os.AsyncTask; +import android.os.Bundle; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; @@ -440,6 +441,7 @@ public final class PrintSpoolerService extends Service { private void removeObsoletePrintJobs() { synchronized (mLock) { + boolean persistState = false; final int printJobCount = mPrintJobs.size(); for (int i = printJobCount - 1; i >= 0; i--) { PrintJobInfo printJob = mPrintJobs.get(i); @@ -449,9 +451,12 @@ public final class PrintSpoolerService extends Service { Slog.i(LOG_TAG, "[REMOVE] " + printJob.getId().flattenToString()); } removePrintJobFileLocked(printJob.getId()); + persistState = true; } } - mPersistanceManager.writeStateLocked(); + if (persistState) { + mPersistanceManager.writeStateLocked(); + } } } @@ -543,7 +548,7 @@ public final class PrintSpoolerService extends Service { final int printJobCount = mPrintJobs.size(); for (int i = 0; i < printJobCount; i++) { PrintJobInfo printJob = mPrintJobs.get(i); - if (isActiveState(printJob.getState()) + if (isActiveState(printJob.getState()) && printJob.getPrinterId() != null && printJob.getPrinterId().getServiceName().equals(service)) { return true; } @@ -623,6 +628,16 @@ public final class PrintSpoolerService extends Service { } } + public void setPrintJobAdvancedOptionsNoPersistence(PrintJobId printJobId, + Bundle advancedOptions) { + synchronized (mLock) { + PrintJobInfo printJob = getPrintJobInfo(printJobId, PrintManager.APP_ID_ANY); + if (printJob != null) { + printJob.setAdvancedOptions(advancedOptions); + } + } + } + public void setPrintJobPrintDocumentInfoNoPersistence(PrintJobId printJobId, PrintDocumentInfo info) { synchronized (mLock) { @@ -704,6 +719,14 @@ public final class PrintSpoolerService extends Service { private static final String ATTR_STATE_REASON = "stateReason"; private static final String ATTR_CANCELLING = "cancelling"; + private static final String TAG_ADVANCED_OPTIONS = "advancedOptions"; + private static final String TAG_ADVANCED_OPTION = "advancedOption"; + private static final String ATTR_KEY = "key"; + private static final String ATTR_TYPE = "type"; + private static final String ATTR_VALUE = "value"; + private static final String TYPE_STRING = "string"; + private static final String TYPE_INT = "int"; + private static final String TAG_MEDIA_SIZE = "mediaSize"; private static final String TAG_RESOLUTION = "resolution"; private static final String TAG_MARGINS = "margins"; @@ -780,6 +803,10 @@ public final class PrintSpoolerService extends Service { for (int j = 0; j < printJobCount; j++) { PrintJobInfo printJob = printJobs.get(j); + if (!shouldPersistPrintJob(printJob)) { + continue; + } + serializer.startTag(null, TAG_JOB); serializer.attribute(null, ATTR_ID, printJob.getId().flattenToString()); @@ -899,6 +926,30 @@ public final class PrintSpoolerService extends Service { serializer.endTag(null, TAG_DOCUMENT_INFO); } + Bundle advancedOptions = printJob.getAdvancedOptions(); + if (advancedOptions != null) { + serializer.startTag(null, TAG_ADVANCED_OPTIONS); + for (String key : advancedOptions.keySet()) { + Object value = advancedOptions.get(key); + if (value instanceof String) { + String stringValue = (String) value; + serializer.startTag(null, TAG_ADVANCED_OPTION); + serializer.attribute(null, ATTR_KEY, key); + serializer.attribute(null, ATTR_TYPE, TYPE_STRING); + serializer.attribute(null, ATTR_VALUE, stringValue); + serializer.endTag(null, TAG_ADVANCED_OPTION); + } else if (value instanceof Integer) { + String intValue = Integer.toString((Integer) value); + serializer.startTag(null, TAG_ADVANCED_OPTION); + serializer.attribute(null, ATTR_KEY, key); + serializer.attribute(null, ATTR_TYPE, TYPE_INT); + serializer.attribute(null, ATTR_VALUE, intValue); + serializer.endTag(null, TAG_ADVANCED_OPTION); + } + } + serializer.endTag(null, TAG_ADVANCED_OPTIONS); + } + serializer.endTag(null, TAG_JOB); if (DEBUG_PERSISTENCE) { @@ -1027,6 +1078,7 @@ public final class PrintSpoolerService extends Service { skipEmptyTextTags(parser); expect(parser, XmlPullParser.END_TAG, TAG_PAGE_RANGE); parser.next(); + skipEmptyTextTags(parser); } if (pageRanges != null) { PageRange[] pageRangesArray = new PageRange[pageRanges.size()]; @@ -1057,8 +1109,8 @@ public final class PrintSpoolerService extends Service { final int labelResId = (labelResIdString != null) ? Integer.parseInt(labelResIdString) : 0; label = parser.getAttributeValue(null, ATTR_LABEL); - MediaSize mediaSize = new MediaSize(id, label, packageName, labelResId, - widthMils, heightMils); + MediaSize mediaSize = new MediaSize(id, label, packageName, + widthMils, heightMils, labelResId); builder.setMediaSize(mediaSize); parser.next(); skipEmptyTextTags(parser); @@ -1127,6 +1179,32 @@ public final class PrintSpoolerService extends Service { parser.next(); } + skipEmptyTextTags(parser); + if (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTIONS)) { + parser.next(); + skipEmptyTextTags(parser); + Bundle advancedOptions = new Bundle(); + while (accept(parser, XmlPullParser.START_TAG, TAG_ADVANCED_OPTION)) { + String key = parser.getAttributeValue(null, ATTR_KEY); + String value = parser.getAttributeValue(null, ATTR_VALUE); + String type = parser.getAttributeValue(null, ATTR_TYPE); + if (TYPE_STRING.equals(type)) { + advancedOptions.putString(key, value); + } else if (TYPE_INT.equals(type)) { + advancedOptions.putInt(key, Integer.valueOf(value)); + } + parser.next(); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTION); + parser.next(); + skipEmptyTextTags(parser); + } + printJob.setAdvancedOptions(advancedOptions); + skipEmptyTextTags(parser); + expect(parser, XmlPullParser.END_TAG, TAG_ADVANCED_OPTIONS); + parser.next(); + } + mPrintJobs.add(printJob); if (DEBUG_PERSISTENCE) { diff --git a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java index fd14af9..d9ccb5d 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java +++ b/packages/PrintSpooler/src/com/android/printspooler/RemotePrintDocumentAdapter.java @@ -137,4 +137,15 @@ final class RemotePrintDocumentAdapter { Log.e(LOG_TAG, "Error calling finish()", re); } } + + public void cancel() { + if (DEBUG) { + Log.i(LOG_TAG, "cancel()"); + } + try { + mRemoteInterface.cancel(); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error calling cancel()", re); + } + } } diff --git a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java index be94ba4..fe5920c 100644 --- a/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java +++ b/packages/PrintSpooler/src/com/android/printspooler/SelectPrinterFragment.java @@ -22,7 +22,6 @@ import android.app.Dialog; import android.app.DialogFragment; import android.app.Fragment; import android.app.FragmentTransaction; -import android.app.ListFragment; import android.app.LoaderManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -47,12 +46,17 @@ import android.printservice.PrintServiceInfo; import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.accessibility.AccessibilityManager; +import android.widget.AdapterView; +import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.Filter; @@ -68,7 +72,7 @@ import java.util.List; /** * This is a fragment for selecting a printer. */ -public final class SelectPrinterFragment extends ListFragment { +public final class SelectPrinterFragment extends Fragment { private static final String LOG_TAG = "SelectPrinterFragment"; @@ -80,9 +84,13 @@ public final class SelectPrinterFragment extends ListFragment { private static final String FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS = "FRAGMRNT_ARGUMENT_PRINT_SERVICE_INFOS"; + private static final String EXTRA_PRINTER_ID = "EXTRA_PRINTER_ID"; + private final ArrayList<PrintServiceInfo> mAddPrinterServices = new ArrayList<PrintServiceInfo>(); + private ListView mListView; + private AnnounceFilterResult mAnnounceFilterResult; public static interface OnPrinterSelectedListener { @@ -97,8 +105,12 @@ public final class SelectPrinterFragment extends ListFragment { } @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View content = inflater.inflate(R.layout.select_printer_fragment, container, false); + + // Hook up the list view. + mListView = (ListView) content.findViewById(android.R.id.list); final DestinationAdapter adapter = new DestinationAdapter(); adapter.registerDataSetObserver(new DataSetObserver() { @Override @@ -115,7 +127,28 @@ public final class SelectPrinterFragment extends ListFragment { } } }); - setListAdapter(adapter); + mListView.setAdapter(adapter); + + mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + if (!((DestinationAdapter) mListView.getAdapter()).isActionable(position)) { + return; + } + PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); + Activity activity = getActivity(); + if (activity instanceof OnPrinterSelectedListener) { + ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId()); + } else { + throw new IllegalStateException("the host activity must implement" + + " OnPrinterSelectedListener"); + } + } + }); + + registerForContextMenu(mListView); + + return content; } @Override @@ -133,7 +166,7 @@ public final class SelectPrinterFragment extends ListFragment { @Override public boolean onQueryTextChange(String searchString) { - ((DestinationAdapter) getListAdapter()).getFilter().filter(searchString); + ((DestinationAdapter) mListView.getAdapter()).getFilter().filter(searchString); return true; } }); @@ -162,6 +195,62 @@ public final class SelectPrinterFragment extends ListFragment { } @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo menuInfo) { + if (view == mListView) { + final int position = ((AdapterContextMenuInfo) menuInfo).position; + PrinterInfo printer = (PrinterInfo) mListView.getAdapter().getItem(position); + + menu.setHeaderTitle(printer.getName()); + + // Add the select menu item if applicable. + if (printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE) { + MenuItem selectItem = menu.add(Menu.NONE, R.string.print_select_printer, + Menu.NONE, R.string.print_select_printer); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); + selectItem.setIntent(intent); + } + + // Add the forget menu item if applicable. + FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>) + getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + if (provider.isFavoritePrinter(printer.getId())) { + MenuItem forgetItem = menu.add(Menu.NONE, R.string.print_forget_printer, + Menu.NONE, R.string.print_forget_printer); + Intent intent = new Intent(); + intent.putExtra(EXTRA_PRINTER_ID, printer.getId()); + forgetItem.setIntent(intent); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.string.print_select_printer: { + PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra( + EXTRA_PRINTER_ID); + Activity activity = getActivity(); + if (activity instanceof OnPrinterSelectedListener) { + ((OnPrinterSelectedListener) activity).onPrinterSelected(printerId); + } else { + throw new IllegalStateException("the host activity must implement" + + " OnPrinterSelectedListener"); + } + } return true; + + case R.string.print_forget_printer: { + PrinterId printerId = (PrinterId) item.getIntent().getParcelableExtra( + EXTRA_PRINTER_ID); + FusedPrintersProvider provider = (FusedPrintersProvider) (Loader<?>) + getLoaderManager().getLoader(LOADER_ID_PRINTERS_LOADER); + provider.forgetFavoritePrinter(printerId); + } return true; + } + return false; + } + + @Override public void onResume() { updateAddPrintersAdapter(); getActivity().invalidateOptionsMenu(); @@ -177,18 +266,6 @@ public final class SelectPrinterFragment extends ListFragment { } @Override - public void onListItemClick(ListView list, View view, int position, long id) { - PrinterInfo printer = (PrinterInfo) list.getAdapter().getItem(position); - Activity activity = getActivity(); - if (activity instanceof OnPrinterSelectedListener) { - ((OnPrinterSelectedListener) activity).onPrinterSelected(printer.getId()); - } else { - throw new IllegalStateException("the host activity must implement" - + " OnPrinterSelectedListener"); - } - } - - @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_add_printer) { showAddPrinterSelectionDialog(); @@ -260,9 +337,9 @@ public final class SelectPrinterFragment extends ListFragment { } public void updateEmptyView(DestinationAdapter adapter) { - if (getListView().getEmptyView() == null) { + if (mListView.getEmptyView() == null) { View emptyView = getActivity().findViewById(R.id.empty_print_state); - getListView().setEmptyView(emptyView); + mListView.setEmptyView(emptyView); } TextView titleView = (TextView) getActivity().findViewById(R.id.title); View progressBar = getActivity().findViewById(R.id.progress_bar); @@ -450,10 +527,10 @@ public final class SelectPrinterFragment extends ListFragment { public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = getActivity().getLayoutInflater().inflate( - R.layout.printer_dropdown_item, parent, false); + R.layout.printer_list_item, parent, false); } - convertView.setEnabled(isEnabled(position)); + convertView.setEnabled(isActionable(position)); CharSequence title = null; CharSequence subtitle = null; @@ -495,8 +572,7 @@ public final class SelectPrinterFragment extends ListFragment { return convertView; } - @Override - public boolean isEnabled(int position) { + public boolean isActionable(int position) { PrinterInfo printer = (PrinterInfo) getItem(position); return printer.getStatus() != PrinterInfo.STATUS_UNAVAILABLE; } @@ -539,16 +615,16 @@ public final class SelectPrinterFragment extends ListFragment { public void post() { remove(); - getListView().postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); + mListView.postDelayed(this, SEARCH_RESULT_ANNOUNCEMENT_DELAY); } public void remove() { - getListView().removeCallbacks(this); + mListView.removeCallbacks(this); } @Override public void run() { - final int count = getListView().getAdapter().getCount(); + final int count = mListView.getAdapter().getCount(); final String text; if (count <= 0) { text = getString(R.string.print_no_printers); @@ -556,7 +632,7 @@ public final class SelectPrinterFragment extends ListFragment { text = getActivity().getResources().getQuantityString( R.plurals.print_search_result_count_utterance, count, count); } - getListView().announceForAccessibility(text); + mListView.announceForAccessibility(text); } } } diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index ffb4c20..29e8d1d 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -5,7 +5,6 @@ > <!-- Standard permissions granted to the shell. --> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.SEND_SMS" /> <uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_CONTACTS" /> @@ -64,6 +63,7 @@ <uses-permission android:name="android.permission.SET_SCREEN_COMPATIBILITY" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" /> <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" /> <uses-permission android:name="android.permission.MANAGE_USERS" /> diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 09ac2da..8d6fe41 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -71,6 +71,9 @@ <!-- Keyguard --> <uses-permission android:name="android.permission.CONTROL_KEYGUARD" /> + <!-- Wifi Display --> + <uses-permission android:name="android.permission.CONFIGURE_WIFI_DISPLAY" /> + <application android:persistent="true" android:allowClearUserData="false" diff --git a/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..5bbfa4f --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png Binary files differindex 2d8d074..693abf5 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..1c3518a --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..9dbc65e --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..ddb002d --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..43b7ef2 --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..1d8b7ee --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png Binary files differindex 7220968..e3b3eeb 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_location.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_location.png Binary files differdeleted file mode 100644 index c561446..0000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_location.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 02d7fda..0000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 263f07c..0000000 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-hdpi/ic_qs_settings.png b/packages/SystemUI/res/drawable-hdpi/ic_qs_settings.png Binary files differindex a53108d..cfa539f 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_qs_settings.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_qs_settings.png diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png Binary files differindex 8f4cb64..c6f03c4 100644 --- a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-hdpi/search_light.png b/packages/SystemUI/res/drawable-hdpi/search_light.png Binary files differindex 116b1f0..3c0dc4e 100644 --- a/packages/SystemUI/res/drawable-hdpi/search_light.png +++ b/packages/SystemUI/res/drawable-hdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-hdpi/search_light_land.png b/packages/SystemUI/res/drawable-hdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..731f19b --- /dev/null +++ b/packages/SystemUI/res/drawable-hdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..1a58144 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-hdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..a12519e --- /dev/null +++ b/packages/SystemUI/res/drawable-land-mdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..ce41454 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-xhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..b0b4561 --- /dev/null +++ b/packages/SystemUI/res/drawable-land-xxhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..2856e09 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png Binary files differindex 399db00..15340d3 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..11b2134 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..a858573 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..04de5d7 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..caea37e --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..b66aa46 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png Binary files differindex 8c2dc68..cc81794 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_location.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_location.png Binary files differdeleted file mode 100644 index e285bba..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_location.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 09ae409..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 780cfc8..0000000 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-mdpi/ic_qs_settings.png b/packages/SystemUI/res/drawable-mdpi/ic_qs_settings.png Binary files differindex 0f7607b..e6237eb 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_qs_settings.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_qs_settings.png diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png Binary files differindex 2142147..1c2d7aa 100644 --- a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-mdpi/search_light.png b/packages/SystemUI/res/drawable-mdpi/search_light.png Binary files differindex 7a70984..8010ce7 100644 --- a/packages/SystemUI/res/drawable-mdpi/search_light.png +++ b/packages/SystemUI/res/drawable-mdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-mdpi/search_light_land.png b/packages/SystemUI/res/drawable-mdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..a4d82f0 --- /dev/null +++ b/packages/SystemUI/res/drawable-mdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..72269f2 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png Binary files differindex c0032e2..e3cc9b0 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..10ebcd5 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..fef43b8 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..05e3267 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..ef42b27 --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..fc1c95e --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png Binary files differindex bffbf55..65d15b5 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_location.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_location.png Binary files differdeleted file mode 100644 index a52dc8d..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_location.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index 48f90ac..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index 621c045..0000000 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_qs_settings.png b/packages/SystemUI/res/drawable-xhdpi/ic_qs_settings.png Binary files differindex 4ce9460..208089d 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_qs_settings.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_qs_settings.png diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png Binary files differindex b0ea8e0..fbd4d6b 100644 --- a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light.png b/packages/SystemUI/res/drawable-xhdpi/search_light.png Binary files differindex e2aed09..6d46fdd 100644 --- a/packages/SystemUI/res/drawable-xhdpi/search_light.png +++ b/packages/SystemUI/res/drawable-xhdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-xhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..b62c74e --- /dev/null +++ b/packages/SystemUI/res/drawable-xhdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png Binary files differnew file mode 100644 index 0000000..efc9b04 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/bg_protect.9.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png Binary files differindex a3cc08d..e15981a 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_notify_settings_normal.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png Binary files differnew file mode 100644 index 0000000..68b1b7c --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_available.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png Binary files differnew file mode 100644 index 0000000..8a8f890 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connected.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png Binary files differnew file mode 100644 index 0000000..12d4a01 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_0.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png Binary files differnew file mode 100644 index 0000000..3cb4421 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_1.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png Binary files differnew file mode 100644 index 0000000..4620b3a --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_cast_connecting_2.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png Binary files differindex ab841d2..1a5d26a 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_ime.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_location.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_location.png Binary files differdeleted file mode 100644 index 3175636..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_location.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png Binary files differdeleted file mode 100644 index b07be828..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png Binary files differdeleted file mode 100644 index f02d0ab..0000000 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_remote_display_connected.png +++ /dev/null diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_settings.png b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_settings.png Binary files differindex 74a78dc..452942e 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_qs_settings.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_qs_settings.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png Binary files differindex aac3428..86df881 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png +++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_camera.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light.png b/packages/SystemUI/res/drawable-xxhdpi/search_light.png Binary files differindex e5ef85d..7742207 100644 --- a/packages/SystemUI/res/drawable-xxhdpi/search_light.png +++ b/packages/SystemUI/res/drawable-xxhdpi/search_light.png diff --git a/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png Binary files differnew file mode 100644 index 0000000..f364577 --- /dev/null +++ b/packages/SystemUI/res/drawable-xxhdpi/search_light_land.png diff --git a/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml new file mode 100644 index 0000000..70db2a9 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_qs_cast_connecting.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* + * Copyright 2013, 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. + */ +--> +<animation-list + xmlns:android="http://schemas.android.com/apk/res/android" + android:oneshot="false"> + <item android:drawable="@drawable/ic_qs_cast_connecting_0" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_2" android:duration="500" /> + <item android:drawable="@drawable/ic_qs_cast_connecting_1" android:duration="500" /> +</animation-list> diff --git a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml index b2ba25a..0c0be29 100644 --- a/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout-land/status_bar_recent_panel.xml @@ -24,6 +24,7 @@ android:id="@+id/recents_root" android:layout_height="match_parent" android:layout_width="match_parent" + android:foreground="@drawable/bg_protect" systemui:recentItemLayout="@layout/status_bar_recent_item" > <FrameLayout diff --git a/packages/SystemUI/res/layout/navigation_bar.xml b/packages/SystemUI/res/layout/navigation_bar.xml index aa365ae..5488a87 100644 --- a/packages/SystemUI/res/layout/navigation_bar.xml +++ b/packages/SystemUI/res/layout/navigation_bar.xml @@ -311,7 +311,7 @@ android:layout_height="80dp" android:layout_width="match_parent" android:layout_gravity="center_vertical" - android:src="@drawable/search_light" + android:src="@drawable/search_light_land" android:scaleType="center" android:visibility="gone" android:contentDescription="@string/accessibility_search_light" diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml index d7312df..eb66908 100644 --- a/packages/SystemUI/res/layout/status_bar.xml +++ b/packages/SystemUI/res/layout/status_bar.xml @@ -95,12 +95,12 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" /> - <!-- battery must be padded below by 2px to match assets --> + <!-- battery must be padded below to match assets --> <com.android.systemui.BatteryMeterView android:id="@+id/battery" android:layout_height="16dp" android:layout_width="10.5dp" - android:layout_marginBottom="2px" + android:layout_marginBottom="0.33dp" android:layout_marginStart="4dip" /> </LinearLayout> diff --git a/packages/SystemUI/res/layout/status_bar_recent_panel.xml b/packages/SystemUI/res/layout/status_bar_recent_panel.xml index e41475b..2f3968d 100644 --- a/packages/SystemUI/res/layout/status_bar_recent_panel.xml +++ b/packages/SystemUI/res/layout/status_bar_recent_panel.xml @@ -24,6 +24,7 @@ android:id="@+id/recents_root" android:layout_height="match_parent" android:layout_width="match_parent" + android:foreground="@drawable/bg_protect" systemui:recentItemLayout="@layout/status_bar_recent_item" > <FrameLayout diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 58865ab..e36ca8e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -382,6 +382,8 @@ <string name="accessibility_quick_settings_airplane">Airplane Mode <xliff:g id="state" example="Off">%s</xliff:g>.</string> <!-- Content description of the bluetooth tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_bluetooth">Bluetooth <xliff:g id="state" example="Off">%s</xliff:g>.</string> + <!-- Content description of the location tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> + <string name="accessibility_quick_settings_location">Location <xliff:g id="state" example="Off">%s</xliff:g>.</string> <!-- Content description of the alarm tile in quick settings (not shown on the screen). [CHAR LIMIT=NONE] --> <string name="accessibility_quick_settings_alarm">Alarm set for <xliff:g id="time" example="Wed 3:30 PM">%s</xliff:g>.</string> @@ -488,10 +490,8 @@ <string name="quick_settings_wifi_no_network">No Network</string> <!-- QuickSettings: Wifi (Off) [CHAR LIMIT=NONE] --> <string name="quick_settings_wifi_off_label">Wi-Fi Off</string> - <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] --> - <string name="quick_settings_wifi_display_label">Wi-Fi Display</string> - <!-- QuickSettings: Wifi display [CHAR LIMIT=NONE] --> - <string name="quick_settings_wifi_display_no_connection_label">Wireless Display</string> + <!-- QuickSettings: Remote display [CHAR LIMIT=NONE] --> + <string name="quick_settings_remote_display_no_connection_label">Cast Screen</string> <!-- QuickSettings: Brightness dialog title [CHAR LIMIT=NONE] --> <string name="quick_settings_brightness_dialog_title">Brightness</string> <!-- QuickSettings: Brightness dialog auto brightness button [CHAR LIMIT=NONE] --> diff --git a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java index b6e03e1..13aafb2 100755 --- a/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/BatteryMeterView.java @@ -66,7 +66,7 @@ public class BatteryMeterView extends View implements DemoMode { private final RectF mFrame = new RectF(); private final RectF mButtonFrame = new RectF(); private final RectF mClipFrame = new RectF(); - private final Rect mBoltFrame = new Rect(); + private final RectF mBoltFrame = new RectF(); private class BatteryTracker extends BroadcastReceiver { public static final int UNKNOWN_LEVEL = -1; @@ -319,10 +319,10 @@ public class BatteryMeterView extends View implements DemoMode { if (tracker.plugged) { // draw the bolt - final int bl = (int)(mFrame.left + mFrame.width() / 4.5f); - final int bt = (int)(mFrame.top + mFrame.height() / 6f); - final int br = (int)(mFrame.right - mFrame.width() / 7f); - final int bb = (int)(mFrame.bottom - mFrame.height() / 10f); + final float bl = mFrame.left + mFrame.width() / 4.5f; + final float bt = mFrame.top + mFrame.height() / 6f; + final float br = mFrame.right - mFrame.width() / 7f; + final float bb = mFrame.bottom - mFrame.height() / 10f; if (mBoltFrame.left != bl || mBoltFrame.top != bt || mBoltFrame.right != br || mBoltFrame.bottom != bb) { mBoltFrame.set(bl, bt, br, bb); diff --git a/packages/SystemUI/src/com/android/systemui/DessertCase.java b/packages/SystemUI/src/com/android/systemui/DessertCase.java index dd4c018..d797e38 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCase.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCase.java @@ -36,7 +36,8 @@ public class DessertCase extends Activity { if (pm.getComponentEnabledSetting(cn) != PackageManager.COMPONENT_ENABLED_STATE_ENABLED) { Slog.v("DessertCase", "ACHIEVEMENT UNLOCKED"); pm.setComponentEnabledSetting(cn, - PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0); + PackageManager.COMPONENT_ENABLED_STATE_ENABLED, + PackageManager.DONT_KILL_APP); } mView = new DessertCaseView(this); diff --git a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java index 90de65e..4147155 100644 --- a/packages/SystemUI/src/com/android/systemui/DessertCaseView.java +++ b/packages/SystemUI/src/com/android/systemui/DessertCaseView.java @@ -17,6 +17,7 @@ package com.android.systemui; import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; @@ -166,15 +167,19 @@ public class DessertCaseView extends FrameLayout { if (mCellSize < 512) { // assuming 512x512 images opts.inSampleSize = 2; } + opts.inMutable = true; + Bitmap loaded = null; for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) { for (int resid : list) { - final BitmapDrawable d = new BitmapDrawable(res, - convertToAlphaMask(BitmapFactory.decodeResource(res, resid, opts))); + opts.inBitmap = loaded; + loaded = BitmapFactory.decodeResource(res, resid, opts); + final BitmapDrawable d = new BitmapDrawable(res, convertToAlphaMask(loaded)); d.setColorFilter(new ColorMatrixColorFilter(ALPHA_MASK)); d.setBounds(0, 0, mCellSize, mCellSize); mDrawables.append(resid, d); } } + loaded = null; if (DEBUG) setWillNotDraw(false); } @@ -304,8 +309,6 @@ public class DessertCaseView extends FrameLayout { v.getOverlay().add(d); } - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - lp.width = lp.height = mCellSize; addView(v, lp); place(v, pt, false); @@ -314,7 +317,7 @@ public class DessertCaseView extends FrameLayout { v.setScaleX(0.5f * s); v.setScaleY(0.5f * s); v.setAlpha(0f); - v.animate().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen); + v.animate().withLayer().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen); } } } @@ -323,6 +326,21 @@ public class DessertCaseView extends FrameLayout { place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate); } + // we don't have .withLayer() on general Animators + private final Animator.AnimatorListener makeHardwareLayerListener(final View v) { + return new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animator) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + v.buildLayer(); + } + @Override + public void onAnimationEnd(Animator animator) { + v.setLayerType(View.LAYER_TYPE_NONE, null); + } + }; + } + private final HashSet<View> tmpSet = new HashSet<View>(); public synchronized void place(View v, Point pt, boolean animate) { final int i = pt.x; @@ -370,7 +388,8 @@ public class DessertCaseView extends FrameLayout { if (squatter != v) { squatter.setTag(TAG_POS, null); if (animate) { - squatter.animate().scaleX(0.5f).scaleY(0.5f).alpha(0) + squatter.animate().withLayer() + .scaleX(0.5f).scaleY(0.5f).alpha(0) .setDuration(DURATION) .setInterpolator(new AccelerateInterpolator()) .setListener(new Animator.AnimatorListener() { @@ -397,6 +416,7 @@ public class DessertCaseView extends FrameLayout { if (animate) { v.bringToFront(); + AnimatorSet set1 = new AnimatorSet(); set1.playTogether( ObjectAnimator.ofFloat(v, View.SCALE_X, (float) scale), @@ -404,7 +424,6 @@ public class DessertCaseView extends FrameLayout { ); set1.setInterpolator(new AnticipateOvershootInterpolator()); set1.setDuration(DURATION); - set1.start(); AnimatorSet set2 = new AnimatorSet(); set2.playTogether( @@ -414,6 +433,10 @@ public class DessertCaseView extends FrameLayout { ); set2.setInterpolator(new DecelerateInterpolator()); set2.setDuration(DURATION); + + set1.addListener(makeHardwareLayerListener(v)); + + set1.start(); set2.start(); } else { v.setX(i * mCellSize + (scale-1) * mCellSize /2); @@ -473,7 +496,6 @@ public class DessertCaseView extends FrameLayout { } public static class RescalingContainer extends FrameLayout { - private static final int SYSTEM_UI_MODE_800 = 0x00000800; private DessertCaseView mView; private float mDarkness; @@ -486,7 +508,7 @@ public class DessertCaseView extends FrameLayout { | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | SYSTEM_UI_MODE_800 + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java index 9839fe9..7d3e870 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AnimatedImageView.java @@ -38,7 +38,7 @@ public class AnimatedImageView extends ImageView { } private void updateAnim() { - Drawable drawable = getDrawable(); + Drawable drawable = mAttached ? getDrawable() : null; if (mAttached && mAnim != null) { mAnim.stop(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java index 6a2bc5f..ed00398 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar; import android.app.ActivityManager; import android.app.ActivityManagerNative; -import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.app.TaskStackBuilder; @@ -70,6 +69,7 @@ import com.android.systemui.R; import com.android.systemui.RecentsComponent; import com.android.systemui.SearchPanelView; import com.android.systemui.SystemUI; +import com.android.systemui.statusbar.phone.KeyguardTouchDelegate; import com.android.systemui.statusbar.policy.NotificationRowLayout; import java.util.ArrayList; @@ -128,7 +128,6 @@ public abstract class BaseStatusBar extends SystemUI implements protected boolean mUseHeadsUp = false; protected IDreamManager mDreamManager; - KeyguardManager mKeyguardManager; PowerManager mPowerManager; protected int mRowHeight; @@ -221,7 +220,6 @@ public abstract class BaseStatusBar extends SystemUI implements mDreamManager = IDreamManager.Stub.asInterface( ServiceManager.checkService(DreamService.DREAM_SERVICE)); - mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); mProvisioningObserver.onChange(false); // set up @@ -749,9 +747,7 @@ public abstract class BaseStatusBar extends SystemUI implements Log.w(TAG, "Sending contentIntent failed: " + e); } - KeyguardManager kgm = - (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - if (kgm != null) kgm.exitKeyguardSecurely(null); + KeyguardTouchDelegate.getInstance(mContext).dismiss(); } try { @@ -1056,10 +1052,12 @@ public abstract class BaseStatusBar extends SystemUI implements boolean isAllowed = notification.extras.getInt(Notification.EXTRA_AS_HEADS_UP, Notification.HEADS_UP_ALLOWED) != Notification.HEADS_UP_NEVER; + final KeyguardTouchDelegate keyguard = KeyguardTouchDelegate.getInstance(mContext); boolean interrupt = (isFullscreen || (isHighPriority && isNoisy)) && isAllowed && mPowerManager.isScreenOn() - && !mKeyguardManager.isKeyguardLocked(); + && !keyguard.isShowingAndNotHidden() + && !keyguard.isInputRestricted(); try { interrupt = interrupt && !mDreamManager.isDreaming(); } catch (RemoteException e) { @@ -1087,8 +1085,7 @@ public abstract class BaseStatusBar extends SystemUI implements } public boolean inKeyguardRestrictedInputMode() { - KeyguardManager km = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); - return km.inKeyguardRestrictedInputMode(); + return KeyguardTouchDelegate.getInstance(mContext).isInputRestricted(); } public void setInteracting(int barWindow, boolean interacting) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index e8173b7..39333d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -55,8 +55,7 @@ public class CommandQueue extends IStatusBar.Stub { private static final int MSG_TOGGLE_RECENT_APPS = 13 << MSG_SHIFT; private static final int MSG_PRELOAD_RECENT_APPS = 14 << MSG_SHIFT; private static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 15 << MSG_SHIFT; - private static final int MSG_SET_NAVIGATION_ICON_HINTS = 16 << MSG_SHIFT; - private static final int MSG_SET_WINDOW_STATE = 17 << MSG_SHIFT; + private static final int MSG_SET_WINDOW_STATE = 16 << MSG_SHIFT; public static final int FLAG_EXCLUDE_NONE = 0; public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0; @@ -98,7 +97,6 @@ public class CommandQueue extends IStatusBar.Stub { public void showSearchPanel(); public void hideSearchPanel(); public void cancelPreloadRecentApps(); - public void setNavigationIconHints(int hints); public void setWindowState(int window, int state); } @@ -227,13 +225,6 @@ public class CommandQueue extends IStatusBar.Stub { } } - public void setNavigationIconHints(int hints) { - synchronized (mList) { - mHandler.removeMessages(MSG_SET_NAVIGATION_ICON_HINTS); - mHandler.obtainMessage(MSG_SET_NAVIGATION_ICON_HINTS, hints, 0, null).sendToTarget(); - } - } - public void setWindowState(int window, int state) { synchronized (mList) { // don't coalesce these @@ -318,9 +309,6 @@ public class CommandQueue extends IStatusBar.Stub { case MSG_CANCEL_PRELOAD_RECENT_APPS: mCallbacks.cancelPreloadRecentApps(); break; - case MSG_SET_NAVIGATION_ICON_HINTS: - mCallbacks.setNavigationIconHints(msg.arg1); - break; case MSG_SET_WINDOW_STATE: mCallbacks.setWindowState(msg.arg1, msg.arg2); break; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java index 1c8702a..cb17ac6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BarTransitions.java @@ -16,16 +16,20 @@ package com.android.systemui.statusbar.phone; -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; -import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.animation.TimeInterpolator; import android.app.ActivityManager; +import android.content.Context; import android.content.res.Resources; -import android.graphics.drawable.ColorDrawable; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.PixelFormat; +import android.graphics.Rect; import android.graphics.drawable.Drawable; -import android.graphics.drawable.TransitionDrawable; +import android.os.SystemClock; import android.util.Log; import android.view.View; +import android.view.animation.LinearInterpolator; import com.android.systemui.R; @@ -45,43 +49,16 @@ public class BarTransitions { private final String mTag; private final View mView; private final boolean mSupportsTransitions = ActivityManager.isHighEndGfx(); - - private final int mOpaque; - private final int mSemiTransparent; + private final BarBackgroundDrawable mBarBackground; private int mMode; - private ValueAnimator mColorDrawableAnimator; - private boolean mColorDrawableShowing; - - private final ColorDrawable mColorDrawable; - private final TransitionDrawable mTransitionDrawable; - private final AnimatorUpdateListener mAnimatorListener = new AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - mColorDrawable.setColor((Integer) animator.getAnimatedValue()); - } - }; public BarTransitions(View view, int gradientResourceId) { mTag = "BarTransitions." + view.getClass().getSimpleName(); mView = view; - final Resources res = mView.getContext().getResources(); - - if (DEBUG_COLORS) { - mOpaque = 0xff0000ff; - mSemiTransparent = 0x7f0000ff; - } else { - mOpaque = res.getColor(R.color.system_bar_background_opaque); - mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent); - } - - mColorDrawable = new ColorDrawable(mOpaque); - mTransitionDrawable = new TransitionDrawable( - new Drawable[] { res.getDrawable(gradientResourceId), mColorDrawable }); - mTransitionDrawable.setCrossFadeEnabled(true); - mTransitionDrawable.resetTransition(); + mBarBackground = new BarBackgroundDrawable(mView.getContext(), gradientResourceId); if (mSupportsTransitions) { - mView.setBackground(mTransitionDrawable); + mView.setBackground(mBarBackground); } } @@ -100,65 +77,144 @@ public class BarTransitions { } } - private Integer getBackgroundColor(int mode) { - if (mode == MODE_SEMI_TRANSPARENT) return mSemiTransparent; - if (mode == MODE_OPAQUE) return mOpaque; - if (mode == MODE_LIGHTS_OUT) return mOpaque; - return null; - } - protected void onTransition(int oldMode, int newMode, boolean animate) { applyModeBackground(oldMode, newMode, animate); } protected void applyModeBackground(int oldMode, int newMode, boolean animate) { - if (DEBUG) Log.d(mTag, String.format("applyModeBackground %s animate=%s", - modeToString(newMode), animate)); - cancelColorAnimation(); - Integer oldColor = getBackgroundColor(oldMode); - Integer newColor = getBackgroundColor(newMode); - if (newColor != null) { - if (animate && oldColor != null && !oldColor.equals(newColor)) { - startColorAnimation(oldColor, newColor); - } else if (!newColor.equals(mColorDrawable.getColor())) { - if (DEBUG) Log.d(mTag, String.format("setColor = %08x", newColor)); - mColorDrawable.setColor(newColor); + if (DEBUG) Log.d(mTag, String.format("applyModeBackground oldMode=%s newMode=%s animate=%s", + modeToString(oldMode), modeToString(newMode), animate)); + mBarBackground.applyModeBackground(oldMode, newMode, animate); + } + + public static String modeToString(int mode) { + if (mode == MODE_OPAQUE) return "MODE_OPAQUE"; + if (mode == MODE_SEMI_TRANSPARENT) return "MODE_SEMI_TRANSPARENT"; + if (mode == MODE_TRANSLUCENT) return "MODE_TRANSLUCENT"; + if (mode == MODE_LIGHTS_OUT) return "MODE_LIGHTS_OUT"; + throw new IllegalArgumentException("Unknown mode " + mode); + } + + public void finishAnimations() { + mBarBackground.finishAnimation(); + } + + public void setContentVisible(boolean visible) { + // for subclasses + } + + private static class BarBackgroundDrawable extends Drawable { + private final int mOpaque; + private final int mSemiTransparent; + private final Drawable mGradient; + private final TimeInterpolator mInterpolator; + + private int mMode = -1; + private boolean mAnimating; + private long mStartTime; + private long mEndTime; + + private int mGradientAlpha; + private int mColor; + + private int mGradientAlphaStart; + private int mColorStart; + + public BarBackgroundDrawable(Context context, int gradientResourceId) { + final Resources res = context.getResources(); + if (DEBUG_COLORS) { + mOpaque = 0xff0000ff; + mSemiTransparent = 0x7f0000ff; + } else { + mOpaque = res.getColor(R.color.system_bar_background_opaque); + mSemiTransparent = res.getColor(R.color.system_bar_background_semi_transparent); } + mGradient = res.getDrawable(gradientResourceId); + mInterpolator = new LinearInterpolator(); + } + + @Override + public void setAlpha(int alpha) { + // noop } - if (newColor == null && mColorDrawableShowing) { - if (DEBUG) Log.d(mTag, "Hide color layer"); + + @Override + public void setColorFilter(ColorFilter cf) { + // noop + } + + @Override + protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + mGradient.setBounds(bounds); + } + + public void applyModeBackground(int oldMode, int newMode, boolean animate) { + if (mMode == newMode) return; + mMode = newMode; + mAnimating = animate; if (animate) { - mTransitionDrawable.reverseTransition(BACKGROUND_DURATION); - } else { - mTransitionDrawable.resetTransition(); + long now = SystemClock.elapsedRealtime(); + mStartTime = now; + mEndTime = now + BACKGROUND_DURATION; + mGradientAlphaStart = mGradientAlpha; + mColorStart = mColor; } - mColorDrawableShowing = false; - } else if (newColor != null && !mColorDrawableShowing) { - if (DEBUG) Log.d(mTag, "Show color layer"); - mTransitionDrawable.startTransition(animate ? BACKGROUND_DURATION : 0); - mColorDrawableShowing = true; + invalidateSelf(); } - } - private void startColorAnimation(int from, int to) { - if (DEBUG) Log.d(mTag, String.format("startColorAnimation %08x -> %08x", from, to)); - mColorDrawableAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), from, to); - mColorDrawableAnimator.addUpdateListener(mAnimatorListener); - mColorDrawableAnimator.start(); - } + @Override + public int getOpacity() { + return PixelFormat.TRANSLUCENT; + } - private void cancelColorAnimation() { - if (mColorDrawableAnimator != null && mColorDrawableAnimator.isStarted()) { - mColorDrawableAnimator.cancel(); - mColorDrawableAnimator = null; + public void finishAnimation() { + if (mAnimating) { + mAnimating = false; + invalidateSelf(); + } } - } - public static String modeToString(int mode) { - if (mode == MODE_OPAQUE) return "MODE_OPAQUE"; - if (mode == MODE_SEMI_TRANSPARENT) return "MODE_SEMI_TRANSPARENT"; - if (mode == MODE_TRANSLUCENT) return "MODE_TRANSLUCENT"; - if (mode == MODE_LIGHTS_OUT) return "MODE_LIGHTS_OUT"; - throw new IllegalArgumentException("Unknown mode " + mode); + @Override + public void draw(Canvas canvas) { + int targetGradientAlpha = 0, targetColor = 0; + if (mMode == MODE_TRANSLUCENT) { + targetGradientAlpha = 0xff; + } else if (mMode == MODE_SEMI_TRANSPARENT) { + targetColor = mSemiTransparent; + } else { + targetColor = mOpaque; + } + if (!mAnimating) { + mColor = targetColor; + mGradientAlpha = targetGradientAlpha; + } else { + final long now = SystemClock.elapsedRealtime(); + if (now >= mEndTime) { + mAnimating = false; + mColor = targetColor; + mGradientAlpha = targetGradientAlpha; + } else { + final float t = (now - mStartTime) / (float)(mEndTime - mStartTime); + final float v = Math.max(0, Math.min(mInterpolator.getInterpolation(t), 1)); + mGradientAlpha = (int)(v * targetGradientAlpha + mGradientAlphaStart * (1 - v)); + mColor = Color.argb( + (int)(v * Color.alpha(targetColor) + Color.alpha(mColorStart) * (1 - v)), + (int)(v * Color.red(targetColor) + Color.red(mColorStart) * (1 - v)), + (int)(v * Color.green(targetColor) + Color.green(mColorStart) * (1 - v)), + (int)(v * Color.blue(targetColor) + Color.blue(mColorStart) * (1 - v))); + } + } + if (mGradientAlpha > 0) { + mGradient.setAlpha(mGradientAlpha); + mGradient.draw(canvas); + } + if (Color.alpha(mColor) > 0) { + canvas.drawColor(mColor); + } + if (mAnimating) { + invalidateSelf(); // keep going + } + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java index 5c55f0d..c1646ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardTouchDelegate.java @@ -77,10 +77,11 @@ public class KeyguardTouchDelegate { } public static KeyguardTouchDelegate getInstance(Context context) { - if (sInstance == null) { - sInstance = new KeyguardTouchDelegate(context); + KeyguardTouchDelegate instance = sInstance; + if (instance == null) { + instance = sInstance = new KeyguardTouchDelegate(context); } - return sInstance; + return instance; } public boolean isSecure() { @@ -165,7 +166,21 @@ public class KeyguardTouchDelegate { Slog.e(TAG, "RemoteException launching camera!", e); } } else { - Slog.w(TAG, "dispatch(event): NO SERVICE!"); + Slog.w(TAG, "launchCamera(): NO SERVICE!"); + } + } + + public void dismiss() { + final IKeyguardService service = mService; + if (service != null) { + try { + service.dismiss(); + } catch (RemoteException e) { + // What to do? + Slog.e(TAG, "RemoteException dismissing keyguard!", e); + } + } else { + Slog.w(TAG, "dismiss(): NO SERVICE!"); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java index 5d4b995..a74230b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarTransitions.java @@ -30,6 +30,9 @@ import com.android.systemui.statusbar.policy.KeyButtonView; public final class NavigationBarTransitions extends BarTransitions { + private static final float KEYGUARD_QUIESCENT_ALPHA = 0.5f; + private static final int CONTENT_FADE_DURATION = 200; + private final NavigationBarView mView; private final IStatusBarService mBarService; @@ -73,18 +76,57 @@ public final class NavigationBarTransitions extends BarTransitions { private void applyMode(int mode, boolean animate, boolean force) { // apply to key buttons - final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; - final float alpha = isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; - setKeyButtonViewQuiescentAlpha(mView.getBackButton(), alpha, animate); + final float alpha = alphaForMode(mode); setKeyButtonViewQuiescentAlpha(mView.getHomeButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getRecentsButton(), alpha, animate); setKeyButtonViewQuiescentAlpha(mView.getMenuButton(), alpha, animate); - setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), alpha, animate); + + setKeyButtonViewQuiescentAlpha(mView.getSearchLight(), KEYGUARD_QUIESCENT_ALPHA, animate); + setKeyButtonViewQuiescentAlpha(mView.getCameraButton(), KEYGUARD_QUIESCENT_ALPHA, animate); + + applyBackButtonQuiescentAlpha(mode, animate); // apply to lights out applyLightsOut(mode == MODE_LIGHTS_OUT, animate, force); } + private float alphaForMode(int mode) { + final boolean isOpaque = mode == MODE_OPAQUE || mode == MODE_LIGHTS_OUT; + return isOpaque ? KeyButtonView.DEFAULT_QUIESCENT_ALPHA : 1f; + } + + public void applyBackButtonQuiescentAlpha(int mode, boolean animate) { + float backAlpha = 0; + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getSearchLight()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getCameraButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getHomeButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getRecentsButton()); + backAlpha = maxVisibleQuiescentAlpha(backAlpha, mView.getMenuButton()); + if (backAlpha > 0) { + setKeyButtonViewQuiescentAlpha(mView.getBackButton(), backAlpha, animate); + } + } + + private static float maxVisibleQuiescentAlpha(float max, View v) { + if ((v instanceof KeyButtonView) && v.isShown()) { + return Math.max(max, ((KeyButtonView)v).getQuiescentAlpha()); + } + return max; + } + + @Override + public void setContentVisible(boolean visible) { + final float alpha = visible ? 1 : 0; + fadeContent(mView.getCameraButton(), alpha); + fadeContent(mView.getSearchLight(), alpha); + } + + private void fadeContent(View v, float alpha) { + if (v != null) { + v.animate().alpha(alpha).setDuration(CONTENT_FADE_DURATION); + } + } + private void setKeyButtonViewQuiescentAlpha(View button, float alpha, boolean animate) { if (button instanceof KeyButtonView) { ((KeyButtonView) button).setQuiescentAlpha(alpha, animate); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java index d1c4109..839016d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java @@ -17,6 +17,10 @@ package com.android.systemui.statusbar.phone; import android.animation.LayoutTransition; +import android.animation.LayoutTransition.TransitionListener; +import android.animation.ObjectAnimator; +import android.animation.TimeInterpolator; +import android.animation.ValueAnimator; import android.app.ActivityManagerNative; import android.app.StatusBarManager; import android.app.admin.DevicePolicyManager; @@ -48,12 +52,12 @@ import com.android.systemui.R; import com.android.systemui.statusbar.BaseStatusBar; import com.android.systemui.statusbar.DelegateViewHelper; import com.android.systemui.statusbar.policy.DeadZone; +import com.android.systemui.statusbar.policy.KeyButtonView; import java.io.FileDescriptor; import java.io.PrintWriter; public class NavigationBarView extends LinearLayout { - private static final int CAMERA_BUTTON_FADE_DURATION = 200; final static boolean DEBUG = false; final static String TAG = "PhoneStatusBar/NavigationBarView"; @@ -89,6 +93,54 @@ public class NavigationBarView extends LinearLayout { // used to disable the camera icon in navbar when disabled by DPM private boolean mCameraDisabledByDpm; + // performs manual animation in sync with layout transitions + private final NavTransitionListener mTransitionListener = new NavTransitionListener(); + + private class NavTransitionListener implements TransitionListener { + private boolean mBackTransitioning; + private boolean mHomeAppearing; + private long mStartDelay; + private long mDuration; + private TimeInterpolator mInterpolator; + + @Override + public void startTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (view.getId() == R.id.back) { + mBackTransitioning = true; + } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { + mHomeAppearing = true; + mStartDelay = transition.getStartDelay(transitionType); + mDuration = transition.getDuration(transitionType); + mInterpolator = transition.getInterpolator(transitionType); + } + } + + @Override + public void endTransition(LayoutTransition transition, ViewGroup container, + View view, int transitionType) { + if (view.getId() == R.id.back) { + mBackTransitioning = false; + } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { + mHomeAppearing = false; + } + } + + public void onBackAltCleared() { + // When dismissing ime during unlock, force the back button to run the same appearance + // animation as home (if we catch this condition early enough). + if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE + && mHomeAppearing && getHomeButton().getAlpha() == 0) { + getBackButton().setAlpha(0); + ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1); + a.setStartDelay(mStartDelay); + a.setDuration(mDuration); + a.setInterpolator(mInterpolator); + a.start(); + } + } + } + // simplified click handler to be used when device is in accessibility mode private final OnClickListener mAccessibilityClickListener = new OnClickListener() { @Override @@ -108,12 +160,12 @@ public class NavigationBarView extends LinearLayout { case MotionEvent.ACTION_DOWN: // disable search gesture while interacting with camera mDelegateHelper.setDisabled(true); - transitionCameraAndSearchButtonAlpha(0.0f); + mBarTransitions.setContentVisible(false); break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: mDelegateHelper.setDisabled(false); - transitionCameraAndSearchButtonAlpha(1.0f); + mBarTransitions.setContentVisible(true); break; } return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event); @@ -163,17 +215,6 @@ public class NavigationBarView extends LinearLayout { watchForDevicePolicyChanges(); } - protected void transitionCameraAndSearchButtonAlpha(float alpha) { - View cameraButtonView = getCameraButton(); - if (cameraButtonView != null) { - cameraButtonView.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION); - } - View searchLight = getSearchLight(); - if (searchLight != null) { - searchLight.animate().alpha(alpha).setDuration(CAMERA_BUTTON_FADE_DURATION); - } - } - private void watchForDevicePolicyChanges() { final IntentFilter filter = new IntentFilter(); filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); @@ -277,7 +318,10 @@ public class NavigationBarView extends LinearLayout { public void setNavigationIconHints(int hints, boolean force) { if (!force && hints == mNavigationIconHints) return; - + final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; + if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { + mTransitionListener.onBackAltCleared(); + } if (DEBUG) { android.widget.Toast.makeText(mContext, "Navigation icon hints = " + hints, @@ -286,15 +330,7 @@ public class NavigationBarView extends LinearLayout { mNavigationIconHints = hints; - getBackButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); - getHomeButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); - getRecentsButton().setAlpha( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); - - ((ImageView)getBackButton()).setImageDrawable( - (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) + ((ImageView)getBackButton()).setImageDrawable(backAlt ? (mVertical ? mBackAltLandIcon : mBackAltIcon) : (mVertical ? mBackLandIcon : mBackIcon)); @@ -322,13 +358,20 @@ public class NavigationBarView extends LinearLayout { setSlippery(disableHome && disableRecent && disableBack && disableSearch); } - if (!mScreenOn && mCurrentView != null) { - ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); - LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition(); + ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); + if (navButtons != null) { + LayoutTransition lt = navButtons.getLayoutTransition(); if (lt != null) { - lt.disableTransitionType( - LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING | - LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING); + if (!lt.getTransitionListeners().contains(mTransitionListener)) { + lt.addTransitionListener(mTransitionListener); + } + if (!mScreenOn && mCurrentView != null) { + lt.disableTransitionType( + LayoutTransition.CHANGE_APPEARING | + LayoutTransition.CHANGE_DISAPPEARING | + LayoutTransition.APPEARING | + LayoutTransition.DISAPPEARING); + } } } @@ -336,12 +379,17 @@ public class NavigationBarView extends LinearLayout { getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); - final boolean shouldShowSearch = disableHome && !disableSearch; - getSearchLight().setVisibility(shouldShowSearch ? View.VISIBLE : View.GONE); - final View cameraButton = getCameraButton(); - if (cameraButton != null) { - cameraButton.setVisibility( - shouldShowSearch && !mCameraDisabledByDpm ? View.VISIBLE : View.GONE); + final boolean showSearch = disableHome && !disableSearch; + final boolean showCamera = showSearch && !mCameraDisabledByDpm; + setVisibleOrGone(getSearchLight(), showSearch); + setVisibleOrGone(getCameraButton(), showCamera); + + mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); + } + + private void setVisibleOrGone(View view, boolean visible) { + if (view != null) { + view.setVisibility(visible ? VISIBLE : GONE); } } @@ -574,28 +622,31 @@ public class NavigationBarView extends LinearLayout { mVertical ? "true" : "false", mShowMenu ? "true" : "false")); - final View back = getBackButton(); - final View home = getHomeButton(); - final View recent = getRecentsButton(); - final View menu = getMenuButton(); - - pw.println(" back: " - + PhoneStatusBar.viewInfo(back) - + " " + visibilityToString(back.getVisibility()) - ); - pw.println(" home: " - + PhoneStatusBar.viewInfo(home) - + " " + visibilityToString(home.getVisibility()) - ); - pw.println(" rcnt: " - + PhoneStatusBar.viewInfo(recent) - + " " + visibilityToString(recent.getVisibility()) - ); - pw.println(" menu: " - + PhoneStatusBar.viewInfo(menu) - + " " + visibilityToString(menu.getVisibility()) - ); + dumpButton(pw, "back", getBackButton()); + dumpButton(pw, "home", getHomeButton()); + dumpButton(pw, "rcnt", getRecentsButton()); + dumpButton(pw, "menu", getMenuButton()); + dumpButton(pw, "srch", getSearchLight()); + dumpButton(pw, "cmra", getCameraButton()); + pw.println(" }"); } + private static void dumpButton(PrintWriter pw, String caption, View button) { + pw.print(" " + caption + ": "); + if (button == null) { + pw.print("null"); + } else { + pw.print(PhoneStatusBar.viewInfo(button) + + " " + visibilityToString(button.getVisibility()) + + " alpha=" + button.getAlpha() + ); + if (button instanceof KeyButtonView) { + pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); + pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); + } + } + pw.println(); + } + } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java index 925179d..bbac4ef 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java @@ -53,6 +53,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; +import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; @@ -632,6 +633,10 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } } + PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); + mBroadcastReceiver.onReceive(mContext, + new Intent(pm.isScreenOn() ? Intent.ACTION_SCREEN_ON : Intent.ACTION_SCREEN_OFF)); + // receive broadcasts IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -649,14 +654,14 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { @Override protected void onShowSearchPanel() { if (mNavigationBarView != null) { - mNavigationBarView.transitionCameraAndSearchButtonAlpha(0.0f); + mNavigationBarView.getBarTransitions().setContentVisible(false); } } @Override protected void onHideSearchPanel() { if (mNavigationBarView != null) { - mNavigationBarView.transitionCameraAndSearchButtonAlpha(1.0f); + mNavigationBarView.getBarTransitions().setContentVisible(true); } } @@ -802,7 +807,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } private void repositionNavigationBar() { - if (mNavigationBarView == null) return; + if (mNavigationBarView == null || !mNavigationBarView.isAttachedToWindow()) return; prepareNavigationBarView(); @@ -1807,8 +1812,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { return mGestureRec; } - @Override // CommandQueue - public void setNavigationIconHints(int hints) { + private void setNavigationIconHints(int hints) { if (hints == mNavigationIconHints) return; mNavigationIconHints = hints; @@ -1945,6 +1949,13 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { transitions.transitionTo(mode, anim); } + private void finishBarAnimations() { + mStatusBarView.getBarTransitions().finishAnimations(); + if (mNavigationBarView != null) { + mNavigationBarView.getBarTransitions().finishAnimations(); + } + } + private final Runnable mCheckBarModes = new Runnable() { @Override public void run() { @@ -2038,7 +2049,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { boolean altBack = (backDisposition == InputMethodService.BACK_DISPOSITION_WILL_DISMISS) || ((vis & InputMethodService.IME_VISIBLE) != 0); - mCommandQueue.setNavigationIconHints( + setNavigationIconHints( altBack ? (mNavigationIconHints | NAVIGATION_HINT_BACK_ALT) : (mNavigationIconHints & ~NAVIGATION_HINT_BACK_ALT)); if (mQS != null) mQS.setImeWindowStatus(vis > 0); @@ -2094,9 +2105,12 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { } public void tickerHalting() { - mStatusBarContents.setVisibility(View.VISIBLE); + if (mStatusBarContents.getVisibility() != View.VISIBLE) { + mStatusBarContents.setVisibility(View.VISIBLE); + mStatusBarContents + .startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); + } mTickerView.setVisibility(View.GONE); - mStatusBarContents.startAnimation(loadAnim(com.android.internal.R.anim.fade_in, null)); // we do not animate the ticker away at this point, just get rid of it (b/6992707) } } @@ -2449,6 +2463,7 @@ public class PhoneStatusBar extends BaseStatusBar implements DemoMode { makeExpandedInvisible(); notifyNavigationBarScreenOn(false); notifyHeadsUpScreenOn(false); + finishBarAnimations(); } else if (Intent.ACTION_SCREEN_ON.equals(action)) { mScreenOn = true; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java index d9ac7e4..d0e9a99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java @@ -170,6 +170,9 @@ public class PhoneStatusBarView extends PanelBar { mBar.makeExpandedInvisibleSoon(); mFadingPanel = null; mLastFullyOpenedPanel = null; + if (mScrimColor != 0 && ActivityManager.isHighEndGfx()) { + mBar.mStatusBarWindow.setBackgroundColor(0); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java index 5423bb6..e7b8fa1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java @@ -37,9 +37,8 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.LevelListDrawable; import android.hardware.display.DisplayManager; -import android.hardware.display.WifiDisplayStatus; +import android.media.MediaRouter; import android.net.wifi.WifiManager; import android.os.AsyncTask; import android.os.Handler; @@ -62,6 +61,7 @@ import android.view.WindowManagerGlobal; import android.widget.ImageView; import android.widget.TextView; +import com.android.internal.app.MediaRouteDialogPresenter; import com.android.systemui.R; import com.android.systemui.statusbar.phone.QuickSettingsModel.ActivityState; import com.android.systemui.statusbar.phone.QuickSettingsModel.BluetoothState; @@ -92,9 +92,7 @@ class QuickSettings { private QuickSettingsModel mModel; private ViewGroup mContainerView; - private DisplayManager mDisplayManager; private DevicePolicyManager mDevicePolicyManager; - private WifiDisplayStatus mWifiDisplayStatus; private PhoneStatusBar mStatusBarService; private BluetoothState mBluetoothState; private BluetoothAdapter mBluetoothAdapter; @@ -118,13 +116,11 @@ class QuickSettings { new ArrayList<QuickSettingsTileView>(); public QuickSettings(Context context, QuickSettingsContainerView container) { - mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE); mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE); mContext = context; mContainerView = container; mModel = new QuickSettingsModel(context); - mWifiDisplayStatus = new WifiDisplayStatus(); mBluetoothState = new QuickSettingsModel.BluetoothState(); mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); @@ -171,7 +167,6 @@ class QuickSettings { mLocationController = locationController; setupQuickSettings(); - updateWifiDisplayStatus(); updateResources(); applyLocationEnabledStatus(); @@ -314,11 +309,19 @@ class QuickSettings { collapsePanels(); final UserManager um = UserManager.get(mContext); if (um.getUsers(true).size() > 1) { - try { - WindowManagerGlobal.getWindowManagerService().lockNow(null); - } catch (RemoteException e) { - Log.e(TAG, "Couldn't show user switcher", e); - } + // Since keyguard and systemui were merged into the same process to save + // memory, they share the same Looper and graphics context. As a result, + // there's no way to allow concurrent animation while keyguard inflates. + // The workaround is to add a slight delay to allow the animation to finish. + mHandler.postDelayed(new Runnable() { + public void run() { + try { + WindowManagerGlobal.getWindowManagerService().lockNow(null); + } catch (RemoteException e) { + Log.e(TAG, "Couldn't show user switcher", e); + } + } + }, 400); // TODO: ideally this would be tied to the collapse of the panel } else { Intent intent = ContactsContract.QuickContact.composeQuickContactsIntent( mContext, v, ContactsContract.Profile.CONTENT_URI, @@ -630,8 +633,19 @@ class QuickSettings { return true; // Consume click }} ); } - mModel.addLocationTile(locationTile, - new QuickSettingsModel.BasicRefreshCallback(locationTile)); + mModel.addLocationTile(locationTile, new QuickSettingsModel.RefreshCallback() { + @Override + public void refreshView(QuickSettingsTileView unused, State state) { + locationTile.setImageResource(state.iconId); + String locationState = mContext.getString( + (state.enabled) ? R.string.accessibility_desc_on + : R.string.accessibility_desc_off); + locationTile.setContentDescription(mContext.getString( + R.string.accessibility_quick_settings_location, + locationState)); + locationTile.setText(state.label); + } + }); parent.addView(locationTile); } @@ -657,20 +671,33 @@ class QuickSettings { }); parent.addView(alarmTile); - // Wifi Display - QuickSettingsBasicTile wifiDisplayTile + // Remote Display + QuickSettingsBasicTile remoteDisplayTile = new QuickSettingsBasicTile(mContext); - wifiDisplayTile.setImageResource(R.drawable.ic_qs_remote_display); - wifiDisplayTile.setOnClickListener(new View.OnClickListener() { + remoteDisplayTile.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - startSettingsActivity(android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS); + collapsePanels(); + + final Dialog[] dialog = new Dialog[1]; + dialog[0] = MediaRouteDialogPresenter.createDialog(mContext, + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + new View.OnClickListener() { + @Override + public void onClick(View v) { + dialog[0].dismiss(); + startSettingsActivity( + android.provider.Settings.ACTION_WIFI_DISPLAY_SETTINGS); + } + }); + dialog[0].getWindow().setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY); + dialog[0].show(); } }); - mModel.addWifiDisplayTile(wifiDisplayTile, - new QuickSettingsModel.BasicRefreshCallback(wifiDisplayTile) + mModel.addRemoteDisplayTile(remoteDisplayTile, + new QuickSettingsModel.BasicRefreshCallback(remoteDisplayTile) .setShowWhenEnabled(true)); - parent.addView(wifiDisplayTile); + parent.addView(remoteDisplayTile); if (SHOW_IME_TILE || DEBUG_GONE_TILES) { // IME @@ -805,15 +832,6 @@ class QuickSettings { dialog.show(); } - private void updateWifiDisplayStatus() { - mWifiDisplayStatus = mDisplayManager.getWifiDisplayStatus(); - applyWifiDisplayStatus(); - } - - private void applyWifiDisplayStatus() { - mModel.onWifiDisplayStateChanged(mWifiDisplayStatus); - } - private void applyBluetoothStatus() { mModel.onBluetoothStateChange(mBluetoothState); } @@ -837,12 +855,7 @@ class QuickSettings { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (DisplayManager.ACTION_WIFI_DISPLAY_STATUS_CHANGED.equals(action)) { - WifiDisplayStatus status = (WifiDisplayStatus)intent.getParcelableExtra( - DisplayManager.EXTRA_WIFI_DISPLAY_STATUS); - mWifiDisplayStatus = status; - applyWifiDisplayStatus(); - } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { + if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) { int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR); mBluetoothState.enabled = (state == BluetoothAdapter.STATE_ON); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java index 2026102..e1a20ec 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java @@ -27,7 +27,8 @@ import android.content.pm.PackageManager; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.drawable.Drawable; -import android.hardware.display.WifiDisplayStatus; +import android.media.MediaRouter; +import android.media.MediaRouter.RouteInfo; import android.net.ConnectivityManager; import android.os.Handler; import android.os.UserHandle; @@ -57,7 +58,6 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, BrightnessStateChangeCallback, RotationLockControllerCallback, LocationSettingsChangeCallback { - // Sett InputMethoManagerService private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; @@ -199,6 +199,30 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, } } + /** Callback for changes to remote display routes. */ + private class RemoteDisplayRouteCallback extends MediaRouter.SimpleCallback { + @Override + public void onRouteAdded(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteChanged(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteRemoved(MediaRouter router, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteSelected(MediaRouter router, int type, RouteInfo route) { + updateRemoteDisplays(); + } + @Override + public void onRouteUnselected(MediaRouter router, int type, RouteInfo route) { + updateRemoteDisplays(); + } + } + private final Context mContext; private final Handler mHandler; private final CurrentUserTracker mUserTracker; @@ -206,6 +230,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private final BugreportObserver mBugreportObserver; private final BrightnessObserver mBrightnessObserver; + private final MediaRouter mMediaRouter; + private final RemoteDisplayRouteCallback mRemoteDisplayRouteCallback; + private final boolean mHasMobileData; private QuickSettingsTileView mUserTile; @@ -228,9 +255,9 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, private RefreshCallback mWifiCallback; private WifiState mWifiState = new WifiState(); - private QuickSettingsTileView mWifiDisplayTile; - private RefreshCallback mWifiDisplayCallback; - private State mWifiDisplayState = new State(); + private QuickSettingsTileView mRemoteDisplayTile; + private RefreshCallback mRemoteDisplayCallback; + private State mRemoteDisplayState = new State(); private QuickSettingsTileView mRSSITile; private RefreshCallback mRSSICallback; @@ -278,12 +305,14 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mContext = context; mHandler = new Handler(); mUserTracker = new CurrentUserTracker(mContext) { + @Override public void onUserSwitched(int newUserId) { mBrightnessObserver.startObserving(); - onRotationLockChanged(); + refreshRotationLockTile(); onBrightnessLevelChanged(); onNextAlarmChanged(); onBugreportChanged(); + rebindMediaRouterAsCurrentUser(); } }; @@ -294,6 +323,11 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBrightnessObserver = new BrightnessObserver(mHandler); mBrightnessObserver.startObserving(); + mMediaRouter = (MediaRouter)context.getSystemService(Context.MEDIA_ROUTER_SERVICE); + rebindMediaRouterAsCurrentUser(); + + mRemoteDisplayRouteCallback = new RemoteDisplayRouteCallback(); + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); mHasMobileData = cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE); @@ -621,24 +655,64 @@ class QuickSettingsModel implements BluetoothStateChangeCallback, mBugreportCallback.refreshView(mBugreportTile, mBugreportState); } - // Wifi Display - void addWifiDisplayTile(QuickSettingsTileView view, RefreshCallback cb) { - mWifiDisplayTile = view; - mWifiDisplayCallback = cb; + // Remote Display + void addRemoteDisplayTile(QuickSettingsTileView view, RefreshCallback cb) { + mRemoteDisplayTile = view; + mRemoteDisplayCallback = cb; + final int[] count = new int[1]; + mRemoteDisplayTile.setOnPrepareListener(new QuickSettingsTileView.OnPrepareListener() { + @Override + public void onPrepare() { + mMediaRouter.addCallback(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY, + mRemoteDisplayRouteCallback, + MediaRouter.CALLBACK_FLAG_REQUEST_DISCOVERY); + updateRemoteDisplays(); + } + @Override + public void onUnprepare() { + mMediaRouter.removeCallback(mRemoteDisplayRouteCallback); + } + }); + + updateRemoteDisplays(); } - public void onWifiDisplayStateChanged(WifiDisplayStatus status) { - mWifiDisplayState.enabled = - (status.getFeatureState() == WifiDisplayStatus.FEATURE_STATE_ON); - if (status.getActiveDisplay() != null) { - mWifiDisplayState.label = status.getActiveDisplay().getFriendlyDisplayName(); - mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display_connected; + + private void rebindMediaRouterAsCurrentUser() { + mMediaRouter.rebindAsUser(mUserTracker.getCurrentUserId()); + } + + private void updateRemoteDisplays() { + MediaRouter.RouteInfo connectedRoute = mMediaRouter.getSelectedRoute( + MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean enabled = connectedRoute != null + && connectedRoute.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY); + boolean connecting; + if (enabled) { + connecting = connectedRoute.isConnecting(); } else { - mWifiDisplayState.label = mContext.getString( - R.string.quick_settings_wifi_display_no_connection_label); - mWifiDisplayState.iconId = R.drawable.ic_qs_remote_display; + connectedRoute = null; + connecting = false; + final int count = mMediaRouter.getRouteCount(); + for (int i = 0; i < count; i++) { + MediaRouter.RouteInfo route = mMediaRouter.getRouteAt(i); + if (route.matchesTypes(MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY)) { + enabled = true; + break; + } + } } - mWifiDisplayCallback.refreshView(mWifiDisplayTile, mWifiDisplayState); + mRemoteDisplayState.enabled = enabled; + if (connectedRoute != null) { + mRemoteDisplayState.label = connectedRoute.getName().toString(); + mRemoteDisplayState.iconId = connecting ? + R.drawable.ic_qs_cast_connecting : R.drawable.ic_qs_cast_connected; + } else { + mRemoteDisplayState.label = mContext.getString( + R.string.quick_settings_remote_display_no_connection_label); + mRemoteDisplayState.iconId = R.drawable.ic_qs_cast_available; + } + mRemoteDisplayCallback.refreshView(mRemoteDisplayTile, mRemoteDisplayState); } // IME diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java index 3d520f7..ad18294 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsTileView.java @@ -21,6 +21,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.View; +import android.view.ViewParent; import android.widget.FrameLayout; /** @@ -31,14 +32,14 @@ class QuickSettingsTileView extends FrameLayout { private int mContentLayoutId; private int mColSpan; - private int mRowSpan; + private boolean mPrepared; + private OnPrepareListener mOnPrepareListener; public QuickSettingsTileView(Context context, AttributeSet attrs) { super(context, attrs); mContentLayoutId = -1; mColSpan = 1; - mRowSpan = 1; } void setColumnSpan(int span) { @@ -77,4 +78,72 @@ class QuickSettingsTileView extends FrameLayout { } super.setVisibility(vis); } + + public void setOnPrepareListener(OnPrepareListener listener) { + if (mOnPrepareListener != listener) { + mOnPrepareListener = listener; + mPrepared = false; + post(new Runnable() { + @Override + public void run() { + updatePreparedState(); + } + }); + } + } + + @Override + protected void onVisibilityChanged(View changedView, int visibility) { + super.onVisibilityChanged(changedView, visibility); + updatePreparedState(); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + updatePreparedState(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + updatePreparedState(); + } + + private void updatePreparedState() { + if (mOnPrepareListener != null) { + if (isParentVisible()) { + if (!mPrepared) { + mPrepared = true; + mOnPrepareListener.onPrepare(); + } + } else if (mPrepared) { + mPrepared = false; + mOnPrepareListener.onUnprepare(); + } + } + } + + private boolean isParentVisible() { + if (!isAttachedToWindow()) { + return false; + } + for (ViewParent current = getParent(); current instanceof View; + current = current.getParent()) { + View view = (View)current; + if (view.getVisibility() != VISIBLE) { + return false; + } + } + return true; + } + + /** + * Called when the view's parent becomes visible or invisible to provide + * an opportunity for the client to provide new content. + */ + public interface OnPrepareListener { + void onPrepare(); + void onUnprepare(); + } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java index e77b420..4901823 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowView.java @@ -113,7 +113,7 @@ public class StatusBarWindowView extends FrameLayout handled = super.onTouchEvent(ev); } final int action = ev.getAction(); - if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (!handled && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) { mService.setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false); } return handled; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java index dca5e41..6eb88be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeadZone.java @@ -22,7 +22,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.os.SystemClock; import android.util.AttributeSet; -import android.util.Log; +import android.util.Slog; import android.view.MotionEvent; import android.view.View; @@ -75,7 +75,7 @@ public class DeadZone extends View { mVertical = (index == VERTICAL); if (DEBUG) - Log.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + Slog.v(TAG, this + " size=[" + mSizeMin + "-" + mSizeMax + "] hold=" + mHold + (mVertical ? " vertical" : " horizontal")); setFlashOnTouchCapture(context.getResources().getBoolean(R.bool.config_dead_zone_flash)); @@ -106,7 +106,7 @@ public class DeadZone extends View { @Override public boolean onTouchEvent(MotionEvent event) { if (DEBUG) { - Log.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); + Slog.v(TAG, this + " onTouch: " + MotionEvent.actionToString(event.getAction())); } final int action = event.getAction(); @@ -114,12 +114,12 @@ public class DeadZone extends View { poke(event); } else if (action == MotionEvent.ACTION_DOWN) { if (DEBUG) { - Log.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); + Slog.v(TAG, this + " ACTION_DOWN: " + event.getX() + "," + event.getY()); } int size = (int) getSize(event.getEventTime()); if ((mVertical && event.getX() < size) || event.getY() < size) { if (CHATTY) { - Log.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); + Slog.v(TAG, "consuming errant click: (" + event.getX() + "," + event.getY() + ")"); } if (mShouldFlash) { post(mDebugFlash); @@ -134,7 +134,7 @@ public class DeadZone extends View { public void poke(MotionEvent event) { mLastPokeTime = event.getEventTime(); if (DEBUG) - Log.v(TAG, "poked! size=" + getSize(mLastPokeTime)); + Slog.v(TAG, "poked! size=" + getSize(mLastPokeTime)); if (mShouldFlash) postInvalidate(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java index 55fb95d..718acc3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyButtonView.java @@ -36,6 +36,7 @@ import android.view.MotionEvent; import android.view.SoundEffectConstants; import android.view.View; import android.view.ViewConfiguration; +import android.view.ViewDebug; import android.view.accessibility.AccessibilityEvent; import android.widget.ImageView; @@ -53,10 +54,13 @@ public class KeyButtonView extends ImageView { int mTouchSlop; Drawable mGlowBG; int mGlowWidth, mGlowHeight; - float mGlowAlpha = 0f, mGlowScale = 1f, mDrawingAlpha = 1f; + float mGlowAlpha = 0f, mGlowScale = 1f; + @ViewDebug.ExportedProperty(category = "drawing") + float mDrawingAlpha = 1f; + @ViewDebug.ExportedProperty(category = "drawing") float mQuiescentAlpha = DEFAULT_QUIESCENT_ALPHA; boolean mSupportsLongpress = true; - RectF mRect = new RectF(0f,0f,0f,0f); + RectF mRect = new RectF(); AnimatorSet mPressedAnim; Animator mAnimateToQuiescent = new ObjectAnimator(); @@ -90,8 +94,8 @@ public class KeyButtonView extends ImageView { mSupportsLongpress = a.getBoolean(R.styleable.KeyButtonView_keyRepeat, true); mGlowBG = a.getDrawable(R.styleable.KeyButtonView_glowBackground); + setDrawingAlpha(mQuiescentAlpha); if (mGlowBG != null) { - setDrawingAlpha(mQuiescentAlpha); mGlowWidth = mGlowBG.getIntrinsicWidth(); mGlowHeight = mGlowBG.getIntrinsicHeight(); } @@ -126,16 +130,14 @@ public class KeyButtonView extends ImageView { public void setQuiescentAlpha(float alpha, boolean animate) { mAnimateToQuiescent.cancel(); alpha = Math.min(Math.max(alpha, 0), 1); - if (alpha == mQuiescentAlpha) return; + if (alpha == mQuiescentAlpha && alpha == mDrawingAlpha) return; mQuiescentAlpha = alpha; if (DEBUG) Log.d(TAG, "New quiescent alpha = " + mQuiescentAlpha); - if (mGlowBG != null) { - if (animate) { - mAnimateToQuiescent = animateToQuiescent(); - mAnimateToQuiescent.start(); - } else { - setDrawingAlpha(mQuiescentAlpha); - } + if (mGlowBG != null && animate) { + mAnimateToQuiescent = animateToQuiescent(); + mAnimateToQuiescent.start(); + } else { + setDrawingAlpha(mQuiescentAlpha); } } @@ -143,13 +145,15 @@ public class KeyButtonView extends ImageView { return ObjectAnimator.ofFloat(this, "drawingAlpha", mQuiescentAlpha); } + public float getQuiescentAlpha() { + return mQuiescentAlpha; + } + public float getDrawingAlpha() { - if (mGlowBG == null) return 0; return mDrawingAlpha; } public void setDrawingAlpha(float x) { - if (mGlowBG == null) return; // Calling setAlpha(int), which is an ImageView-specific // method that's different from setAlpha(float). This sets // the alpha on this ImageView's drawable directly diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java index a53b25a..dd13e31 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tv/TvStatusBar.java @@ -89,10 +89,6 @@ public class TvStatusBar extends BaseStatusBar { } @Override // CommandQueue - public void setNavigationIconHints(int hints) { - } - - @Override // CommandQueue public void setWindowState(int window, int state) { } diff --git a/packages/WallpaperCropper/res/values/strings.xml b/packages/WallpaperCropper/res/values/strings.xml index 2b8111d..091869a 100644 --- a/packages/WallpaperCropper/res/values/strings.xml +++ b/packages/WallpaperCropper/res/values/strings.xml @@ -17,4 +17,9 @@ <string name="crop_wallpaper">Crop wallpaper</string> <!-- Button label on Wallpaper picker screen; user selects this button to set a specific wallpaper --> <string name="wallpaper_instructions">Set wallpaper</string> + <!-- Error message when an image is selected as a wallpaper, + but the wallpaper cropper cannot load it. The user will + usually see this when using another app and trying to set + an image as the wallpaper --> + <string name="wallpaper_load_fail">Couldn\'t load image as wallpaper</string> </resources> diff --git a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java index 5f64018..8511de2 100644 --- a/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java +++ b/packages/WallpaperCropper/src/com/android/photos/BitmapRegionTileSource.java @@ -24,6 +24,9 @@ import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PorterDuff; import android.graphics.Rect; import android.net.Uri; import android.os.Build; @@ -31,14 +34,113 @@ import android.os.Build.VERSION_CODES; import android.util.Log; import com.android.gallery3d.common.BitmapUtils; +import com.android.gallery3d.common.Utils; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.glrenderer.BasicTexture; import com.android.gallery3d.glrenderer.BitmapTexture; import com.android.photos.views.TiledImageRenderer; import java.io.BufferedInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +interface SimpleBitmapRegionDecoder { + int getWidth(); + int getHeight(); + Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options); +} + +class SimpleBitmapRegionDecoderWrapper implements SimpleBitmapRegionDecoder { + BitmapRegionDecoder mDecoder; + private SimpleBitmapRegionDecoderWrapper(BitmapRegionDecoder decoder) { + mDecoder = decoder; + } + public static SimpleBitmapRegionDecoderWrapper newInstance( + String pathName, boolean isShareable) { + try { + BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(pathName, isShareable); + if (d != null) { + return new SimpleBitmapRegionDecoderWrapper(d); + } + } catch (IOException e) { + Log.w("BitmapRegionTileSource", "getting decoder failed for path " + pathName, e); + return null; + } + return null; + } + public static SimpleBitmapRegionDecoderWrapper newInstance( + InputStream is, boolean isShareable) { + try { + BitmapRegionDecoder d = BitmapRegionDecoder.newInstance(is, isShareable); + if (d != null) { + return new SimpleBitmapRegionDecoderWrapper(d); + } + } catch (IOException e) { + Log.w("BitmapRegionTileSource", "getting decoder failed", e); + return null; + } + return null; + } + public int getWidth() { + return mDecoder.getWidth(); + } + public int getHeight() { + return mDecoder.getHeight(); + } + public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) { + return mDecoder.decodeRegion(wantRegion, options); + } +} + +class DumbBitmapRegionDecoder implements SimpleBitmapRegionDecoder { + Bitmap mBuffer; + Canvas mTempCanvas; + Paint mTempPaint; + private DumbBitmapRegionDecoder(Bitmap b) { + mBuffer = b; + } + public static DumbBitmapRegionDecoder newInstance(String pathName) { + Bitmap b = BitmapFactory.decodeFile(pathName); + if (b != null) { + return new DumbBitmapRegionDecoder(b); + } + return null; + } + public static DumbBitmapRegionDecoder newInstance(InputStream is) { + Bitmap b = BitmapFactory.decodeStream(is); + if (b != null) { + return new DumbBitmapRegionDecoder(b); + } + return null; + } + public int getWidth() { + return mBuffer.getWidth(); + } + public int getHeight() { + return mBuffer.getHeight(); + } + public Bitmap decodeRegion(Rect wantRegion, BitmapFactory.Options options) { + if (mTempCanvas == null) { + mTempCanvas = new Canvas(); + mTempPaint = new Paint(); + mTempPaint.setFilterBitmap(true); + } + int sampleSize = Math.max(options.inSampleSize, 1); + Bitmap newBitmap = Bitmap.createBitmap( + wantRegion.width() / sampleSize, + wantRegion.height() / sampleSize, + Bitmap.Config.ARGB_8888); + mTempCanvas.setBitmap(newBitmap); + mTempCanvas.save(); + mTempCanvas.scale(1f / sampleSize, 1f / sampleSize); + mTempCanvas.drawBitmap(mBuffer, -wantRegion.left, -wantRegion.top, mTempPaint); + mTempCanvas.restore(); + mTempCanvas.setBitmap(null); + return newBitmap; + } +} + /** * A {@link com.android.photos.views.TiledImageRenderer.TileSource} using * {@link BitmapRegionDecoder} to wrap a local file @@ -53,9 +155,214 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { private static final int GL_SIZE_LIMIT = 2048; // This must be no larger than half the size of the GL_SIZE_LIMIT // due to decodePreview being allowed to be up to 2x the size of the target - private static final int MAX_PREVIEW_SIZE = 1024; + public static final int MAX_PREVIEW_SIZE = GL_SIZE_LIMIT / 2; + + public static abstract class BitmapSource { + private SimpleBitmapRegionDecoder mDecoder; + private Bitmap mPreview; + private int mPreviewSize; + private int mRotation; + public enum State { NOT_LOADED, LOADED, ERROR_LOADING }; + private State mState = State.NOT_LOADED; + public BitmapSource(int previewSize) { + mPreviewSize = previewSize; + } + public boolean loadInBackground() { + ExifInterface ei = new ExifInterface(); + if (readExif(ei)) { + Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); + if (ori != null) { + mRotation = ExifInterface.getRotationForOrientationValue(ori.shortValue()); + } + } + mDecoder = loadBitmapRegionDecoder(); + if (mDecoder == null) { + mState = State.ERROR_LOADING; + return false; + } else { + int width = mDecoder.getWidth(); + int height = mDecoder.getHeight(); + if (mPreviewSize != 0) { + int previewSize = Math.min(mPreviewSize, MAX_PREVIEW_SIZE); + BitmapFactory.Options opts = new BitmapFactory.Options(); + opts.inPreferredConfig = Bitmap.Config.ARGB_8888; + opts.inPreferQualityOverSpeed = true; + + float scale = (float) previewSize / Math.max(width, height); + opts.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); + opts.inJustDecodeBounds = false; + mPreview = loadPreviewBitmap(opts); + } + mState = State.LOADED; + return true; + } + } - BitmapRegionDecoder mDecoder; + public State getLoadingState() { + return mState; + } + + public SimpleBitmapRegionDecoder getBitmapRegionDecoder() { + return mDecoder; + } + + public Bitmap getPreviewBitmap() { + return mPreview; + } + + public int getPreviewSize() { + return mPreviewSize; + } + + public int getRotation() { + return mRotation; + } + + public abstract boolean readExif(ExifInterface ei); + public abstract SimpleBitmapRegionDecoder loadBitmapRegionDecoder(); + public abstract Bitmap loadPreviewBitmap(BitmapFactory.Options options); + } + + public static class FilePathBitmapSource extends BitmapSource { + private String mPath; + public FilePathBitmapSource(String path, int previewSize) { + super(previewSize); + mPath = path; + } + @Override + public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() { + SimpleBitmapRegionDecoder d; + d = SimpleBitmapRegionDecoderWrapper.newInstance(mPath, true); + if (d == null) { + d = DumbBitmapRegionDecoder.newInstance(mPath); + } + return d; + } + @Override + public Bitmap loadPreviewBitmap(BitmapFactory.Options options) { + return BitmapFactory.decodeFile(mPath, options); + } + @Override + public boolean readExif(ExifInterface ei) { + try { + ei.readExif(mPath); + return true; + } catch (IOException e) { + Log.w("BitmapRegionTileSource", "getting decoder failed", e); + return false; + } + } + } + + public static class UriBitmapSource extends BitmapSource { + private Context mContext; + private Uri mUri; + public UriBitmapSource(Context context, Uri uri, int previewSize) { + super(previewSize); + mContext = context; + mUri = uri; + } + private InputStream regenerateInputStream() throws FileNotFoundException { + InputStream is = mContext.getContentResolver().openInputStream(mUri); + return new BufferedInputStream(is); + } + @Override + public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() { + try { + InputStream is = regenerateInputStream(); + SimpleBitmapRegionDecoder regionDecoder = + SimpleBitmapRegionDecoderWrapper.newInstance(is, false); + Utils.closeSilently(is); + if (regionDecoder == null) { + is = regenerateInputStream(); + regionDecoder = DumbBitmapRegionDecoder.newInstance(is); + Utils.closeSilently(is); + } + return regionDecoder; + } catch (FileNotFoundException e) { + Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e); + return null; + } catch (IOException e) { + Log.e("BitmapRegionTileSource", "Failure while reading URI " + mUri, e); + return null; + } + } + @Override + public Bitmap loadPreviewBitmap(BitmapFactory.Options options) { + try { + InputStream is = regenerateInputStream(); + Bitmap b = BitmapFactory.decodeStream(is, null, options); + Utils.closeSilently(is); + return b; + } catch (FileNotFoundException e) { + Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e); + return null; + } + } + @Override + public boolean readExif(ExifInterface ei) { + InputStream is = null; + try { + is = regenerateInputStream(); + ei.readExif(is); + Utils.closeSilently(is); + return true; + } catch (FileNotFoundException e) { + Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e); + return false; + } catch (IOException e) { + Log.e("BitmapRegionTileSource", "Failed to load URI " + mUri, e); + return false; + } finally { + Utils.closeSilently(is); + } + } + } + + public static class ResourceBitmapSource extends BitmapSource { + private Resources mRes; + private int mResId; + public ResourceBitmapSource(Resources res, int resId, int previewSize) { + super(previewSize); + mRes = res; + mResId = resId; + } + private InputStream regenerateInputStream() { + InputStream is = mRes.openRawResource(mResId); + return new BufferedInputStream(is); + } + @Override + public SimpleBitmapRegionDecoder loadBitmapRegionDecoder() { + InputStream is = regenerateInputStream(); + SimpleBitmapRegionDecoder regionDecoder = + SimpleBitmapRegionDecoderWrapper.newInstance(is, false); + Utils.closeSilently(is); + if (regionDecoder == null) { + is = regenerateInputStream(); + regionDecoder = DumbBitmapRegionDecoder.newInstance(is); + Utils.closeSilently(is); + } + return regionDecoder; + } + @Override + public Bitmap loadPreviewBitmap(BitmapFactory.Options options) { + return BitmapFactory.decodeResource(mRes, mResId, options); + } + @Override + public boolean readExif(ExifInterface ei) { + try { + InputStream is = regenerateInputStream(); + ei.readExif(is); + Utils.closeSilently(is); + return true; + } catch (IOException e) { + Log.e("BitmapRegionTileSource", "Error reading resource", e); + return false; + } + } + } + + SimpleBitmapRegionDecoder mDecoder; int mWidth; int mHeight; int mTileSize; @@ -68,58 +375,33 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { private BitmapFactory.Options mOptions; private Canvas mCanvas; - public BitmapRegionTileSource(Context context, String path, int previewSize, int rotation) { - this(null, context, path, null, 0, previewSize, rotation); - } - - public BitmapRegionTileSource(Context context, Uri uri, int previewSize, int rotation) { - this(null, context, null, uri, 0, previewSize, rotation); - } - - public BitmapRegionTileSource(Resources res, - Context context, int resId, int previewSize, int rotation) { - this(res, context, null, null, resId, previewSize, rotation); - } - - private BitmapRegionTileSource(Resources res, - Context context, String path, Uri uri, int resId, int previewSize, int rotation) { + public BitmapRegionTileSource(Context context, BitmapSource source) { mTileSize = TiledImageRenderer.suggestedTileSize(context); - mRotation = rotation; - try { - if (path != null) { - mDecoder = BitmapRegionDecoder.newInstance(path, true); - } else if (uri != null) { - InputStream is = context.getContentResolver().openInputStream(uri); - BufferedInputStream bis = new BufferedInputStream(is); - mDecoder = BitmapRegionDecoder.newInstance(bis, true); - } else { - InputStream is = res.openRawResource(resId); - BufferedInputStream bis = new BufferedInputStream(is); - mDecoder = BitmapRegionDecoder.newInstance(bis, true); - } + mRotation = source.getRotation(); + mDecoder = source.getBitmapRegionDecoder(); + if (mDecoder != null) { mWidth = mDecoder.getWidth(); mHeight = mDecoder.getHeight(); - } catch (IOException e) { - Log.w("BitmapRegionTileSource", "ctor failed", e); - } - mOptions = new BitmapFactory.Options(); - mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; - mOptions.inPreferQualityOverSpeed = true; - mOptions.inTempStorage = new byte[16 * 1024]; - if (previewSize != 0) { - previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE); - // Although this is the same size as the Bitmap that is likely already - // loaded, the lifecycle is different and interactions are on a different - // thread. Thus to simplify, this source will decode its own bitmap. - Bitmap preview = decodePreview(res, context, path, uri, resId, previewSize); - if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) { - mPreview = new BitmapTexture(preview); - } else { - Log.w(TAG, String.format( - "Failed to create preview of apropriate size! " - + " in: %dx%d, out: %dx%d", - mWidth, mHeight, - preview.getWidth(), preview.getHeight())); + mOptions = new BitmapFactory.Options(); + mOptions.inPreferredConfig = Bitmap.Config.ARGB_8888; + mOptions.inPreferQualityOverSpeed = true; + mOptions.inTempStorage = new byte[16 * 1024]; + int previewSize = source.getPreviewSize(); + if (previewSize != 0) { + previewSize = Math.min(previewSize, MAX_PREVIEW_SIZE); + // Although this is the same size as the Bitmap that is likely already + // loaded, the lifecycle is different and interactions are on a different + // thread. Thus to simplify, this source will decode its own bitmap. + Bitmap preview = decodePreview(source, previewSize); + if (preview.getWidth() <= GL_SIZE_LIMIT && preview.getHeight() <= GL_SIZE_LIMIT) { + mPreview = new BitmapTexture(preview); + } else { + Log.w(TAG, String.format( + "Failed to create preview of apropriate size! " + + " in: %dx%d, out: %dx%d", + mWidth, mHeight, + preview.getWidth(), preview.getHeight())); + } } } } @@ -215,33 +497,15 @@ public class BitmapRegionTileSource implements TiledImageRenderer.TileSource { * Note that the returned bitmap may have a long edge that's longer * than the targetSize, but it will always be less than 2x the targetSize */ - private Bitmap decodePreview( - Resources res, Context context, String file, Uri uri, int resId, int targetSize) { - float scale = (float) targetSize / Math.max(mWidth, mHeight); - mOptions.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale); - mOptions.inJustDecodeBounds = false; - - Bitmap result = null; - if (file != null) { - result = BitmapFactory.decodeFile(file, mOptions); - } else if (uri != null) { - try { - InputStream is = context.getContentResolver().openInputStream(uri); - BufferedInputStream bis = new BufferedInputStream(is); - result = BitmapFactory.decodeStream(bis, null, mOptions); - } catch (IOException e) { - Log.w("BitmapRegionTileSource", "getting preview failed", e); - } - } else { - result = BitmapFactory.decodeResource(res, resId, mOptions); - } + private Bitmap decodePreview(BitmapSource source, int targetSize) { + Bitmap result = source.getPreviewBitmap(); if (result == null) { return null; } // We need to resize down if the decoder does not support inSampleSize // or didn't support the specified inSampleSize (some decoders only do powers of 2) - scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight())); + float scale = (float) targetSize / (float) (Math.max(result.getWidth(), result.getHeight())); if (scale <= 0.5) { result = BitmapUtils.resizeBitmapByScale(result, scale, true); diff --git a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java index 1209e56..57c0581 100644 --- a/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java +++ b/packages/WallpaperCropper/src/com/android/wallpapercropper/WallpaperCropActivity.java @@ -37,15 +37,16 @@ import android.graphics.RectF; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.util.FloatMath; import android.util.Log; import android.view.Display; import android.view.View; import android.view.WindowManager; +import android.widget.Toast; import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInterface; import com.android.photos.BitmapRegionTileSource; +import com.android.photos.BitmapRegionTileSource.BitmapSource; import java.io.BufferedInputStream; import java.io.ByteArrayInputStream; @@ -96,9 +97,6 @@ public class WallpaperCropActivity extends Activity { return; } - int rotation = getRotationFromExif(this, imageUri); - mCropView.setTileSource(new BitmapRegionTileSource(this, imageUri, 1024, rotation), null); - mCropView.setTouchEnabled(true); // Action bar // Show the custom action bar view final ActionBar actionBar = getActionBar(); @@ -111,6 +109,63 @@ public class WallpaperCropActivity extends Activity { cropImageAndSetWallpaper(imageUri, null, finishActivityWhenDone); } }); + + // Load image in background + final BitmapRegionTileSource.UriBitmapSource bitmapSource = + new BitmapRegionTileSource.UriBitmapSource(this, imageUri, 1024); + Runnable onLoad = new Runnable() { + public void run() { + if (bitmapSource.getLoadingState() != BitmapSource.State.LOADED) { + Toast.makeText(WallpaperCropActivity.this, + getString(R.string.wallpaper_load_fail), + Toast.LENGTH_LONG).show(); + finish(); + } + } + }; + setCropViewTileSource(bitmapSource, true, false, onLoad); + } + + public void setCropViewTileSource( + final BitmapRegionTileSource.BitmapSource bitmapSource, final boolean touchEnabled, + final boolean moveToLeft, final Runnable postExecute) { + final Context context = WallpaperCropActivity.this; + final View progressView = findViewById(R.id.loading); + final AsyncTask<Void, Void, Void> loadBitmapTask = new AsyncTask<Void, Void, Void>() { + protected Void doInBackground(Void...args) { + if (!isCancelled()) { + bitmapSource.loadInBackground(); + } + return null; + } + protected void onPostExecute(Void arg) { + if (!isCancelled()) { + progressView.setVisibility(View.INVISIBLE); + if (bitmapSource.getLoadingState() == BitmapSource.State.LOADED) { + mCropView.setTileSource( + new BitmapRegionTileSource(context, bitmapSource), null); + mCropView.setTouchEnabled(touchEnabled); + if (moveToLeft) { + mCropView.moveToLeft(); + } + } + } + if (postExecute != null) { + postExecute.run(); + } + } + }; + // We don't want to show the spinner every time we load an image, because that would be + // annoying; instead, only start showing the spinner if loading the image has taken + // longer than 1 sec (ie 1000 ms) + progressView.postDelayed(new Runnable() { + public void run() { + if (loadBitmapTask.getStatus() != AsyncTask.Status.FINISHED) { + progressView.setVisibility(View.VISIBLE); + } + } + }, 1000); + loadBitmapTask.execute(); } public boolean enableRotation() { @@ -192,16 +247,18 @@ public class WallpaperCropActivity extends Activity { private static int getRotationFromExifHelper( String path, Resources res, int resId, Context context, Uri uri) { ExifInterface ei = new ExifInterface(); + InputStream is = null; + BufferedInputStream bis = null; try { if (path != null) { ei.readExif(path); } else if (uri != null) { - InputStream is = context.getContentResolver().openInputStream(uri); - BufferedInputStream bis = new BufferedInputStream(is); + is = context.getContentResolver().openInputStream(uri); + bis = new BufferedInputStream(is); ei.readExif(bis); } else { - InputStream is = res.openRawResource(resId); - BufferedInputStream bis = new BufferedInputStream(is); + is = res.openRawResource(resId); + bis = new BufferedInputStream(is); ei.readExif(bis); } Integer ori = ei.getTagIntValue(ExifInterface.TAG_ORIENTATION); @@ -210,6 +267,9 @@ public class WallpaperCropActivity extends Activity { } } catch (IOException e) { Log.w(LOGTAG, "Getting exif data failed", e); + } finally { + Utils.closeSilently(bis); + Utils.closeSilently(is); } return 0; } @@ -269,40 +329,15 @@ public class WallpaperCropActivity extends Activity { // Get the crop boolean ltr = mCropView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR; - Point minDims = new Point(); - Point maxDims = new Point(); + Display d = getWindowManager().getDefaultDisplay(); - d.getCurrentSizeRange(minDims, maxDims); Point displaySize = new Point(); d.getSize(displaySize); - - int maxDim = Math.max(maxDims.x, maxDims.y); - final int minDim = Math.min(minDims.x, minDims.y); - int defaultWallpaperWidth; - if (isScreenLarge(getResources())) { - defaultWallpaperWidth = (int) (maxDim * - wallpaperTravelToScreenWidthRatio(maxDim, minDim)); - } else { - defaultWallpaperWidth = Math.max((int) - (minDim * WALLPAPER_SCREENS_SPAN), maxDim); - } - boolean isPortrait = displaySize.x < displaySize.y; - int portraitHeight; - if (isPortrait) { - portraitHeight = mCropView.getHeight(); - } else { - // TODO: how to actually get the proper portrait height? - // This is not quite right: - portraitHeight = Math.max(maxDims.x, maxDims.y); - } - if (android.os.Build.VERSION.SDK_INT >= - android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - Point realSize = new Point(); - d.getRealSize(realSize); - portraitHeight = Math.max(realSize.x, realSize.y); - } + + Point defaultWallpaperSize = getDefaultWallpaperSize(getResources(), + getWindowManager()); // Get the crop RectF cropRect = mCropView.getCrop(); int cropRotation = mCropView.getImageRotation(); @@ -321,7 +356,7 @@ public class WallpaperCropActivity extends Activity { // (or all the way to the left, in RTL) float extraSpace = ltr ? rotatedInSize[0] - cropRect.right : cropRect.left; // Cap the amount of extra width - float maxExtraSpace = defaultWallpaperWidth / cropScale - cropRect.width(); + float maxExtraSpace = defaultWallpaperSize.x / cropScale - cropRect.width(); extraSpace = Math.min(extraSpace, maxExtraSpace); if (ltr) { @@ -332,10 +367,10 @@ public class WallpaperCropActivity extends Activity { // ADJUST CROP HEIGHT if (isPortrait) { - cropRect.bottom = cropRect.top + portraitHeight / cropScale; + cropRect.bottom = cropRect.top + defaultWallpaperSize.y / cropScale; } else { // LANDSCAPE float extraPortraitHeight = - portraitHeight / cropScale - cropRect.height(); + defaultWallpaperSize.y / cropScale - cropRect.height(); float expandHeight = Math.min(Math.min(rotatedInSize[1] - cropRect.bottom, cropRect.top), extraPortraitHeight / 2); @@ -372,7 +407,6 @@ public class WallpaperCropActivity extends Activity { String mInFilePath; byte[] mInImageBytes; int mInResId = 0; - InputStream mInStream; RectF mCropBounds = null; int mOutWidth, mOutHeight; int mRotation; @@ -445,37 +479,36 @@ public class WallpaperCropActivity extends Activity { } // Helper to setup input stream - private void regenerateInputStream() { + private InputStream regenerateInputStream() { if (mInUri == null && mInResId == 0 && mInFilePath == null && mInImageBytes == null) { Log.w(LOGTAG, "cannot read original file, no input URI, resource ID, or " + "image byte array given"); } else { - Utils.closeSilently(mInStream); try { if (mInUri != null) { - mInStream = new BufferedInputStream( + return new BufferedInputStream( mContext.getContentResolver().openInputStream(mInUri)); } else if (mInFilePath != null) { - mInStream = mContext.openFileInput(mInFilePath); + return mContext.openFileInput(mInFilePath); } else if (mInImageBytes != null) { - mInStream = new BufferedInputStream( - new ByteArrayInputStream(mInImageBytes)); + return new BufferedInputStream(new ByteArrayInputStream(mInImageBytes)); } else { - mInStream = new BufferedInputStream( - mResources.openRawResource(mInResId)); + return new BufferedInputStream(mResources.openRawResource(mInResId)); } } catch (FileNotFoundException e) { Log.w(LOGTAG, "cannot read file: " + mInUri.toString(), e); } } + return null; } public Point getImageBounds() { - regenerateInputStream(); - if (mInStream != null) { + InputStream is = regenerateInputStream(); + if (is != null) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - BitmapFactory.decodeStream(mInStream, null, options); + BitmapFactory.decodeStream(is, null, options); + Utils.closeSilently(is); if (options.outWidth != 0 && options.outHeight != 0) { return new Point(options.outWidth, options.outHeight); } @@ -493,22 +526,26 @@ public class WallpaperCropActivity extends Activity { public boolean cropBitmap() { boolean failure = false; - regenerateInputStream(); WallpaperManager wallpaperManager = null; if (mSetWallpaper) { wallpaperManager = WallpaperManager.getInstance(mContext.getApplicationContext()); } - if (mSetWallpaper && mNoCrop && mInStream != null) { + + + if (mSetWallpaper && mNoCrop) { try { - wallpaperManager.setStream(mInStream); + InputStream is = regenerateInputStream(); + if (is != null) { + wallpaperManager.setStream(is); + Utils.closeSilently(is); + } } catch (IOException e) { Log.w(LOGTAG, "cannot write stream to wallpaper", e); failure = true; } return !failure; - } - if (mInStream != null) { + } else { // Find crop bounds (scaled to original image size) Rect roundedTrueCrop = new Rect(); Matrix rotateMatrix = new Matrix(); @@ -521,6 +558,11 @@ public class WallpaperCropActivity extends Activity { mCropBounds = new RectF(roundedTrueCrop); Point bounds = getImageBounds(); + if (bounds == null) { + Log.w(LOGTAG, "cannot get bounds for image"); + failure = true; + return false; + } float[] rotatedBounds = new float[] { bounds.x, bounds.y }; rotateMatrix.mapPoints(rotatedBounds); @@ -531,7 +573,6 @@ public class WallpaperCropActivity extends Activity { inverseRotateMatrix.mapRect(mCropBounds); mCropBounds.offset(bounds.x/2, bounds.y/2); - regenerateInputStream(); } mCropBounds.roundOut(roundedTrueCrop); @@ -543,15 +584,25 @@ public class WallpaperCropActivity extends Activity { } // See how much we're reducing the size of the image - int scaleDownSampleSize = Math.min(roundedTrueCrop.width() / mOutWidth, - roundedTrueCrop.height() / mOutHeight); - + int scaleDownSampleSize = Math.max(1, Math.min(roundedTrueCrop.width() / mOutWidth, + roundedTrueCrop.height() / mOutHeight)); // Attempt to open a region decoder BitmapRegionDecoder decoder = null; + InputStream is = null; try { - decoder = BitmapRegionDecoder.newInstance(mInStream, true); + is = regenerateInputStream(); + if (is == null) { + Log.w(LOGTAG, "cannot get input stream for uri=" + mInUri.toString()); + failure = true; + return false; + } + decoder = BitmapRegionDecoder.newInstance(is, false); + Utils.closeSilently(is); } catch (IOException e) { Log.w(LOGTAG, "cannot open region decoder for file: " + mInUri.toString(), e); + } finally { + Utils.closeSilently(is); + is = null; } Bitmap crop = null; @@ -567,14 +618,15 @@ public class WallpaperCropActivity extends Activity { if (crop == null) { // BitmapRegionDecoder has failed, try to crop in-memory - regenerateInputStream(); + is = regenerateInputStream(); Bitmap fullSize = null; - if (mInStream != null) { + if (is != null) { BitmapFactory.Options options = new BitmapFactory.Options(); if (scaleDownSampleSize > 1) { options.inSampleSize = scaleDownSampleSize; } - fullSize = BitmapFactory.decodeStream(mInStream, null, options); + fullSize = BitmapFactory.decodeStream(is, null, options); + Utils.closeSilently(is); } if (fullSize != null) { mCropBounds.left /= scaleDownSampleSize; @@ -686,7 +738,7 @@ public class WallpaperCropActivity extends Activity { protected void updateWallpaperDimensions(int width, int height) { String spKey = getSharedPreferencesKey(); - SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_PRIVATE); + SharedPreferences sp = getSharedPreferences(spKey, Context.MODE_MULTI_PROCESS); SharedPreferences.Editor editor = sp.edit(); if (width != 0 && height != 0) { editor.putInt(WALLPAPER_WIDTH_KEY, width); diff --git a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java index 596435a..10bcdad 100644 --- a/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java +++ b/packages/services/Proxy/src/com/android/proxyhandler/ProxyServer.java @@ -117,8 +117,8 @@ public class ProxyServer extends Thread { if (!proxy.equals(Proxy.NO_PROXY)) { // Only Inets created by PacProxySelector. InetSocketAddress inetSocketAddress = - (InetSocketAddress)list.get(0).address(); - server = new Socket(inetSocketAddress.getAddress(), + (InetSocketAddress)proxy.address(); + server = new Socket(inetSocketAddress.getHostName(), inetSocketAddress.getPort()); sendLine(server, requestLine); } else { |