diff options
-rw-r--r-- | packages/SystemUI/AndroidManifest.xml | 1 | ||||
-rw-r--r-- | packages/SystemUI/res/anim/recent_app_enter.xml | 30 | ||||
-rw-r--r-- | packages/SystemUI/res/anim/recent_app_leave.xml | 30 | ||||
-rw-r--r-- | packages/SystemUI/res/drawable/recent_overlay.png | bin | 0 -> 162325 bytes | |||
-rw-r--r-- | packages/SystemUI/res/drawable/recent_rez_border.png | bin | 0 -> 21739 bytes | |||
-rw-r--r-- | packages/SystemUI/res/layout/recents_detail_view.xml | 40 | ||||
-rw-r--r-- | packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java | 297 |
7 files changed, 330 insertions, 68 deletions
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index f1f31cf..6057023 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -32,6 +32,7 @@ <activity android:name=".recent.RecentApplicationsActivity" android:theme="@android:style/Theme.NoTitleBar" android:excludeFromRecents="true" + android:launchMode="singleInstance" android:exported="true"> </activity> diff --git a/packages/SystemUI/res/anim/recent_app_enter.xml b/packages/SystemUI/res/anim/recent_app_enter.xml new file mode 100644 index 0000000..4947eee --- /dev/null +++ b/packages/SystemUI/res/anim/recent_app_enter.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- Special window zoom animation: this is the element that enters the screen, + it starts at 200% and scales down. Goes with zoom_exit.xml. --> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/decelerate_interpolator"> + <scale android:fromXScale="0.25" android:toXScale="1.0" + android:fromYScale="0.25" android:toYScale="1.0" + android:pivotX="0%p" android:pivotY="0%p" + android:duration="500" /> + <alpha android:fromAlpha="0.0" android:toAlpha="1.0" + android:duration="500"/> +</set> diff --git a/packages/SystemUI/res/anim/recent_app_leave.xml b/packages/SystemUI/res/anim/recent_app_leave.xml new file mode 100644 index 0000000..3d83988 --- /dev/null +++ b/packages/SystemUI/res/anim/recent_app_leave.xml @@ -0,0 +1,30 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2009, The Android Open Source Project +** +** Licensed under the Apache License, Version 2.0 (the "License"); +** you may not use this file except in compliance with the License. +** You may obtain a copy of the License at +** +** http://www.apache.org/licenses/LICENSE-2.0 +** +** Unless required by applicable law or agreed to in writing, software +** distributed under the License is distributed on an "AS IS" BASIS, +** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +** See the License for the specific language governing permissions and +** limitations under the License. +*/ +--> + +<!-- Special window zoom animation: this is the element that enters the screen, + it starts at 200% and scales down. Goes with zoom_exit.xml. --> +<set xmlns:android="http://schemas.android.com/apk/res/android" + android:interpolator="@android:anim/decelerate_interpolator"> + <scale android:fromXScale="1.0" android:toXScale="0.25" + android:fromYScale="1.0" android:toYScale="0.25" + android:pivotX="0%p" android:pivotY="0%p" + android:duration="500" /> + <alpha android:fromAlpha="1.0" android:toAlpha="0.0" + android:duration="500"/> +</set> diff --git a/packages/SystemUI/res/drawable/recent_overlay.png b/packages/SystemUI/res/drawable/recent_overlay.png Binary files differnew file mode 100644 index 0000000..4dfa3d9 --- /dev/null +++ b/packages/SystemUI/res/drawable/recent_overlay.png diff --git a/packages/SystemUI/res/drawable/recent_rez_border.png b/packages/SystemUI/res/drawable/recent_rez_border.png Binary files differnew file mode 100644 index 0000000..ad025f5 --- /dev/null +++ b/packages/SystemUI/res/drawable/recent_rez_border.png diff --git a/packages/SystemUI/res/layout/recents_detail_view.xml b/packages/SystemUI/res/layout/recents_detail_view.xml new file mode 100644 index 0000000..879d0f2 --- /dev/null +++ b/packages/SystemUI/res/layout/recents_detail_view.xml @@ -0,0 +1,40 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- +/* +** Copyright 2008, 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="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> + + <!-- Application Title --> + <TextView android:id="@+id/app_title" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceMedium" + android:singleLine="true"/> + + <!-- Application Details --> + <TextView + android:id="@+id/app_description" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textAppearance="?android:attr/textAppearanceSmall"/> + +</LinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java index 9cc24be..bf24a1f 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java @@ -20,7 +20,9 @@ package com.android.systemui.recent; import com.android.systemui.R; import com.android.ex.carousel.CarouselView; +import com.android.ex.carousel.CarouselViewHelper; import com.android.ex.carousel.CarouselRS.CarouselCallback; +import com.android.ex.carousel.CarouselViewHelper.DetailTextureParameters; import java.util.ArrayList; import java.util.List; @@ -38,37 +40,104 @@ import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.PorterDuffXfermode; +import android.graphics.PorterDuff; import android.graphics.Bitmap.Config; import android.graphics.drawable.Drawable; import android.graphics.PixelFormat; import android.os.Bundle; +import android.os.Handler; import android.os.RemoteException; import android.util.Log; import android.view.View; +import android.view.View.MeasureSpec; +import android.widget.TextView; public class RecentApplicationsActivity extends Activity { private static final String TAG = "RecentApplicationsActivity"; - private static boolean DBG = true; + private static boolean DBG = false; private static final int CARD_SLOTS = 56; private static final int VISIBLE_SLOTS = 7; private static final int MAX_TASKS = VISIBLE_SLOTS * 2; + + // TODO: these should be configurable + private static final int DETAIL_TEXTURE_MAX_WIDTH = 200; + private static final int DETAIL_TEXTURE_MAX_HEIGHT = 80; + private static final int TEXTURE_WIDTH = 256; + private static final int TEXTURE_HEIGHT = 256; + private ActivityManager mActivityManager; private List<RunningTaskInfo> mRunningTaskList; private boolean mPortraitMode = true; private ArrayList<ActivityDescription> mActivityDescriptions = new ArrayList<ActivityDescription>(); private CarouselView mCarouselView; + private LocalCarouselViewHelper mHelper; private View mNoRecentsView; - private Bitmap mBlankBitmap = Bitmap.createBitmap( - new int[] {0xff808080, 0xffffffff, 0xff808080, 0xffffffff}, 2, 2, Config.RGB_565); + private Bitmap mLoadingBitmap; + private Bitmap mRecentOverlay; + private boolean mHidden = false; + private boolean mHiding = false; + private DetailInfo mDetailInfo; + + /** + * This class is a container for all items associated with the DetailView we'll + * be drawing to a bitmap and sending to Carousel. + * + */ + static final class DetailInfo { + public DetailInfo(View _view, TextView _title, TextView _desc) { + view = _view; + title = _title; + description = _desc; + } + + /** + * Draws view into the given bitmap, if provided + * @param bitmap + */ + public Bitmap draw(Bitmap bitmap) { + resizeView(view, DETAIL_TEXTURE_MAX_WIDTH, DETAIL_TEXTURE_MAX_HEIGHT); + int desiredWidth = view.getWidth(); + int desiredHeight = view.getHeight(); + if (bitmap == null || desiredWidth != bitmap.getWidth() + || desiredHeight != bitmap.getHeight()) { + bitmap = Bitmap.createBitmap(desiredWidth, desiredHeight, Config.ARGB_8888); + } + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + return bitmap; + } + + /** + * Force a layout pass on the given view. + */ + private void resizeView(View view, int maxWidth, int maxHeight) { + int widthSpec = MeasureSpec.getMode(MeasureSpec.AT_MOST) + | MeasureSpec.getSize(maxWidth); + int heightSpec = MeasureSpec.getMode(MeasureSpec.AT_MOST) + | MeasureSpec.getSize(maxHeight); + view.measure(widthSpec, heightSpec); + view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); + Log.v(TAG, "RESIZED VIEW: " + view.getWidth() + ", " + view.getHeight()); + } + + public View view; + public TextView title; + public TextView description; + } static class ActivityDescription { int id; Bitmap thumbnail; // generated by Activity.onCreateThumbnail() Drawable icon; // application package icon String label; // application package label - String description; // generated by Activity.onCreateDescription() + CharSequence description; // generated by Activity.onCreateDescription() Intent intent; // launch intent for application Matrix matrix; // arbitrary rotation matrix to correct orientation int position; // position in list @@ -106,14 +175,17 @@ public class RecentApplicationsActivity extends Activity { return null; } - final CarouselCallback mCarouselCallback = new CarouselCallback() { - - public void onAnimationFinished() { + private class LocalCarouselViewHelper extends CarouselViewHelper { + private Paint mPaint = new Paint(); + private DetailTextureParameters mDetailParams = new DetailTextureParameters(10.0f, 20.0f); + public LocalCarouselViewHelper(Context context) { + super(context); } - public void onAnimationStarted() { - + @Override + public DetailTextureParameters getDetailTextureParameters(int id) { + return mDetailParams; } public void onCardSelected(int n) { @@ -125,7 +197,7 @@ public class RecentApplicationsActivity extends Activity { try { if (DBG) Log.v(TAG, "Starting intent " + item.intent); startActivity(item.intent); - //overridePendingTransition(R.anim.zoom_enter, R.anim.zoom_exit); + overridePendingTransition(R.anim.recent_app_enter, R.anim.recent_app_leave); } catch (ActivityNotFoundException e) { if (DBG) Log.w("Recent", "Unable to launch recent task", e); } @@ -134,48 +206,88 @@ public class RecentApplicationsActivity extends Activity { } } - public void onInvalidateTexture(int n) { - - } - - public void onRequestGeometry(int n) { - - } - - public void onInvalidateGeometry(int n) { - + @Override + public Bitmap getTexture(final int id) { + if (DBG) Log.v(TAG, "onRequestTexture(" + id + ")"); + ActivityDescription info; + synchronized(mActivityDescriptions) { + info = mActivityDescriptions.get(id); + } + Bitmap bitmap = null; + if (info != null) { + bitmap = compositeBitmap(info); + } + return bitmap; } - public void onRequestTexture(final int n) { - if (DBG) Log.v(TAG, "onRequestTexture(" + n + ")"); + @Override + public Bitmap getDetailTexture(int n) { + Bitmap bitmap = null; if (n < mActivityDescriptions.size()) { - mCarouselView.post(new Runnable() { - public void run() { - ActivityDescription info = mActivityDescriptions.get(n); - if (info != null) { - if (DBG) Log.v(TAG, "FOUND ACTIVITY THUMBNAIL " + info.thumbnail); - Bitmap bitmap = info.thumbnail == null ? mBlankBitmap : info.thumbnail; - mCarouselView.setTextureForItem(n, bitmap); - } else { - if (DBG) Log.v(TAG, "FAILED TO GET ACTIVITY THUMBNAIL FOR ITEM " + n); - } - } - }); + ActivityDescription item = mActivityDescriptions.get(n); + mDetailInfo.title.setText(item.label); + mDetailInfo.description.setText(item.description); + bitmap = mDetailInfo.draw(null); } + return bitmap; } + }; - public void onInvalidateDetailTexture(int n) { - + private Bitmap compositeBitmap(ActivityDescription info) { + final int targetWidth = TEXTURE_WIDTH; + final int targetHeight = TEXTURE_HEIGHT; + final int border = 3; // inset along the edge for thumnnail content + final int overlap = 1; // how many pixels of overlap between border and thumbnail + final Resources res = getResources(); + if (mRecentOverlay == null) { + mRecentOverlay = BitmapFactory.decodeResource(res, R.drawable.recent_overlay); } - public void onRequestDetailTexture(int n) { - + // Create a bitmap of the proper size/format and set the canvas to draw to it + final Bitmap result = Bitmap.createBitmap(targetWidth, targetHeight, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(result); + canvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG, Paint.FILTER_BITMAP_FLAG)); + Paint paint = new Paint(); + paint.setFilterBitmap(false); + + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + canvas.save(); + if (info.thumbnail != null) { + // Draw the thumbnail + int sourceWidth = targetWidth - 2 * (border - overlap); + int sourceHeight = targetHeight - 2 * (border - overlap); + final float scaleX = (float) sourceWidth / info.thumbnail.getWidth(); + final float scaleY = (float) sourceHeight / info.thumbnail.getHeight(); + canvas.translate(border * 0.5f, border * 0.5f); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(info.thumbnail, 0, 0, paint); + } else { + // Draw the Loading bitmap placeholder, TODO: Remove when RS handles blending + final float scaleX = (float) targetWidth / mLoadingBitmap.getWidth(); + final float scaleY = (float) targetHeight / mLoadingBitmap.getHeight(); + canvas.scale(scaleX, scaleY); + canvas.drawBitmap(mLoadingBitmap, 0, 0, paint); } - - public void onReportFirstCardPosition(int n) { - + canvas.restore(); + + // Draw overlay + canvas.save(); + final float scaleOverlayX = (float) targetWidth / mRecentOverlay.getWidth(); + final float scaleOverlayY = (float) targetHeight / mRecentOverlay.getHeight(); + canvas.scale(scaleOverlayX, scaleOverlayY); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD)); + canvas.drawBitmap(mRecentOverlay, 0, 0, paint); + canvas.restore(); + + // Draw icon + if (info.icon != null) { + canvas.save(); + info.icon.draw(canvas); + canvas.restore(); } - }; + + return result; + } private final IThumbnailReceiver mThumbnailReceiver = new IThumbnailReceiver.Stub() { @@ -192,6 +304,7 @@ public class RecentApplicationsActivity extends Activity { ActivityDescription info = findActivityDescription(id); if (info != null) { info.thumbnail = bitmap; + info.description = description; final int thumbWidth = bitmap.getWidth(); final int thumbHeight = bitmap.getHeight(); if ((mPortraitMode && thumbWidth > thumbHeight) @@ -202,13 +315,34 @@ public class RecentApplicationsActivity extends Activity { } else { info.matrix = null; } - mCarouselView.setTextureForItem(info.position, info.thumbnail); + mCarouselView.setTextureForItem(info.position, compositeBitmap(info)); } else { if (DBG) Log.v(TAG, "Can't find view for id " + id); } } }; + /** + * We never really finish() RecentApplicationsActivity, since we don't want to + * get destroyed and pay the start-up cost to restart it. + */ + @Override + public void finish() { + moveTaskToBack(true); + } + + @Override + protected void onNewIntent(Intent intent) { + mHidden = !mHidden; + if (mHidden) { + mHiding = true; + moveTaskToBack(true); + } else { + mHiding = false; + } + super.onNewIntent(intent); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -218,25 +352,35 @@ public class RecentApplicationsActivity extends Activity { getWindow().getDecorView().setBackgroundColor(0x80000000); setContentView(R.layout.recent_apps_activity); - mCarouselView = (CarouselView)findViewById(R.id.carousel); - mNoRecentsView = (View) findViewById(R.id.no_applications_message); - //mCarouselView = new CarouselView(this); - //setContentView(mCarouselView); - mCarouselView.setSlotCount(CARD_SLOTS); - mCarouselView.setVisibleSlots(VISIBLE_SLOTS); - mCarouselView.createCards(1); - mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS)); - mCarouselView.setDefaultBitmap(mBlankBitmap); - mCarouselView.setLoadingBitmap(mBlankBitmap); - mCarouselView.setCallback(mCarouselCallback); - mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT); - - mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); - mPortraitMode = decorView.getHeight() > decorView.getWidth(); - refresh(); + if (mCarouselView == null) { + mLoadingBitmap = BitmapFactory.decodeResource(res, R.drawable.recent_rez_border); + mCarouselView = (CarouselView)findViewById(R.id.carousel); + mHelper = new LocalCarouselViewHelper(this); + mHelper.setCarouselView(mCarouselView); + + mCarouselView.setSlotCount(CARD_SLOTS); + mCarouselView.setVisibleSlots(VISIBLE_SLOTS); + mCarouselView.createCards(0); + mCarouselView.setStartAngle((float) -(2.0f*Math.PI * 5 / CARD_SLOTS)); + mCarouselView.setDefaultBitmap(mLoadingBitmap); + mCarouselView.setLoadingBitmap(mLoadingBitmap); + mCarouselView.getHolder().setFormat(PixelFormat.TRANSLUCENT); + + mNoRecentsView = (View) findViewById(R.id.no_applications_message); + + mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + mPortraitMode = decorView.getHeight() > decorView.getWidth(); + + // Load detail view which will be used to render text + View detail = getLayoutInflater().inflate(R.layout.recents_detail_view, null); + TextView title = (TextView) detail.findViewById(R.id.app_title); + TextView description = (TextView) detail.findViewById(R.id.app_description); + mDetailInfo = new DetailInfo(detail, title, description); + refresh(); + } } @Override @@ -264,7 +408,7 @@ public class RecentApplicationsActivity extends Activity { ActivityDescription desc = findActivityDescription(r.id); if (desc != null) { desc.thumbnail = r.thumbnail; - desc.label = r.topActivity.flattenToShortString(); + desc.description = r.description; if ((mPortraitMode && thumbWidth > thumbHeight) || (!mPortraitMode && thumbWidth < thumbHeight)) { Matrix matrix = new Matrix(); @@ -336,17 +480,34 @@ public class RecentApplicationsActivity extends Activity { } } - private void refresh() { - updateRecentTasks(); - updateRunningTasks(); - if (mActivityDescriptions.size() == 0) { - // show "No Recent Takss" - mNoRecentsView.setVisibility(View.VISIBLE); - mCarouselView.setVisibility(View.GONE); - } else { + private final Runnable mRefreshRunnable = new Runnable() { + public void run() { + updateRecentTasks(); + updateRunningTasks(); + showCarousel(mActivityDescriptions.size() > 0); + } + }; + + private void showCarousel(boolean show) { + if (show) { + // Make carousel visible mNoRecentsView.setVisibility(View.GONE); mCarouselView.setVisibility(View.VISIBLE); mCarouselView.createCards(mActivityDescriptions.size()); + } else { + // show "No Recent Tasks" + mNoRecentsView.setVisibility(View.VISIBLE); + mCarouselView.setVisibility(View.GONE); + } + } + + private void refresh() { + if (!mHiding && mCarouselView != null) { + // Don't update the view now. Instead, post a request so it happens next time + // we reach the looper after a delay. This way we can fold multiple refreshes + // into just the latest. + mCarouselView.removeCallbacks(mRefreshRunnable); + mCarouselView.postDelayed(mRefreshRunnable, 50); } } } |