| /* |
| * Copyright (C) 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. |
| */ |
| |
| package android.gadget; |
| |
| import android.content.Context; |
| import android.content.pm.PackageManager; |
| import android.graphics.Color; |
| import android.util.Config; |
| import android.util.Log; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.view.animation.Animation; |
| import android.widget.FrameLayout; |
| import android.widget.RemoteViews; |
| import android.widget.TextView; |
| import android.widget.ViewAnimator; |
| |
| /** |
| * Provides the glue to show gadget views. This class offers automatic animation |
| * between updates, and will try recycling old views for each incoming |
| * {@link RemoteViews}. |
| */ |
| public class GadgetHostView extends ViewAnimator implements Animation.AnimationListener { |
| static final String TAG = "GadgetHostView"; |
| static final boolean LOGD = Config.LOGD || true; |
| |
| // When we're inflating the initialLayout for a gadget, we only allow |
| // views that are allowed in RemoteViews. |
| static final LayoutInflater.Filter sInflaterFilter = new LayoutInflater.Filter() { |
| public boolean onLoadClass(Class clazz) { |
| return clazz.isAnnotationPresent(RemoteViews.RemoteView.class); |
| } |
| }; |
| |
| Context mLocalContext; |
| |
| int mGadgetId; |
| GadgetProviderInfo mInfo; |
| |
| View mActiveView = null; |
| View mStaleView = null; |
| |
| int mActiveLayoutId = -1; |
| int mStaleLayoutId = -1; |
| |
| /** |
| * Last set of {@link RemoteViews} applied to {@link #mActiveView} |
| */ |
| RemoteViews mActiveActions = null; |
| |
| /** |
| * Flag indicating that {@link #mActiveActions} has been applied to |
| * {@link #mStaleView}, meaning it's readyto recycle. |
| */ |
| boolean mStalePrepared = false; |
| |
| /** |
| * Create a host view. Uses default fade animations. |
| */ |
| public GadgetHostView(Context context) { |
| this(context, android.R.anim.fade_in, android.R.anim.fade_out); |
| } |
| |
| /** |
| * Create a host view. Uses specified animations when pushing |
| * {@link #updateGadget(RemoteViews)}. |
| * |
| * @param animationIn Resource ID of in animation to use |
| * @param animationOut Resource ID of out animation to use |
| */ |
| public GadgetHostView(Context context, int animationIn, int animationOut) { |
| super(context); |
| mLocalContext = context; |
| |
| // Prepare our default transition animations |
| setAnimateFirstView(true); |
| setInAnimation(context, animationIn); |
| setOutAnimation(context, animationOut); |
| |
| // Watch for animation events to prepare recycling |
| Animation inAnimation = getInAnimation(); |
| if (inAnimation != null) { |
| inAnimation.setAnimationListener(this); |
| } |
| } |
| |
| /** |
| * Set the gadget that will be displayed by this view. |
| */ |
| public void setGadget(int gadgetId, GadgetProviderInfo info) { |
| if (mInfo != null) { |
| // TODO: remove the old view, or whatever |
| } |
| mGadgetId = gadgetId; |
| mInfo = info; |
| } |
| |
| public int getGadgetId() { |
| return mGadgetId; |
| } |
| |
| public GadgetProviderInfo getGadgetInfo() { |
| return mInfo; |
| } |
| |
| public void onAnimationEnd(Animation animation) { |
| // When our transition animation finishes, we should try bringing our |
| // newly-stale view up to the current view. |
| if (mActiveActions != null && |
| mStaleLayoutId == mActiveActions.getLayoutId()) { |
| if (LOGD) Log.d(TAG, "after animation, layoutId matched so we're recycling old view"); |
| mActiveActions.reapply(mLocalContext, mStaleView); |
| mStalePrepared = true; |
| } |
| } |
| |
| public void onAnimationRepeat(Animation animation) { |
| } |
| |
| public void onAnimationStart(Animation animation) { |
| } |
| |
| /** |
| * Process a set of {@link RemoteViews} coming in as an update from the |
| * gadget provider. Will animate into these new views as needed. |
| */ |
| public void updateGadget(RemoteViews remoteViews) { |
| if (LOGD) Log.d(TAG, "updateGadget called"); |
| |
| boolean recycled = false; |
| View newContent = null; |
| Exception exception = null; |
| |
| if (remoteViews == null) { |
| newContent = getDefaultView(); |
| } |
| |
| // If our stale view has been prepared to match active, and the new |
| // layout matches, try recycling it |
| if (newContent == null && mStalePrepared && |
| remoteViews.getLayoutId() == mStaleLayoutId) { |
| try { |
| remoteViews.reapply(mLocalContext, mStaleView); |
| newContent = mStaleView; |
| recycled = true; |
| if (LOGD) Log.d(TAG, "was able to recycled existing layout"); |
| } catch (RuntimeException e) { |
| exception = e; |
| } |
| } |
| |
| // Try normal RemoteView inflation |
| if (newContent == null) { |
| try { |
| newContent = remoteViews.apply(mLocalContext, this); |
| if (LOGD) Log.d(TAG, "had to inflate new layout"); |
| } catch (RuntimeException e) { |
| exception = e; |
| } |
| } |
| |
| if (exception != null && LOGD) { |
| Log.w(TAG, "Error inflating gadget " + getGadgetInfo(), exception); |
| } |
| |
| if (newContent == null) { |
| // TODO: Should we throw an exception here for the host activity to catch? |
| // Maybe we should show a generic error widget. |
| if (LOGD) Log.d(TAG, "updateGadget couldn't find any view, so inflating error"); |
| newContent = getErrorView(); |
| } |
| |
| if (!recycled) { |
| prepareView(newContent); |
| addView(newContent); |
| } |
| |
| showNext(); |
| |
| if (!recycled) { |
| removeView(mStaleView); |
| } |
| |
| mStalePrepared = false; |
| mActiveActions = remoteViews; |
| |
| mStaleView = mActiveView; |
| mActiveView = newContent; |
| |
| mStaleLayoutId = mActiveLayoutId; |
| mActiveLayoutId = (remoteViews == null) ? -1 : remoteViews.getLayoutId(); |
| } |
| |
| /** |
| * Prepare the given view to be shown. This might include adjusting |
| * {@link FrameLayout.LayoutParams} before inserting. |
| */ |
| protected void prepareView(View view) { |
| // Take requested dimensions from parent, but apply default gravity. |
| ViewGroup.LayoutParams requested = view.getLayoutParams(); |
| if (requested == null) { |
| requested = new FrameLayout.LayoutParams(LayoutParams.FILL_PARENT, |
| LayoutParams.FILL_PARENT); |
| } |
| |
| FrameLayout.LayoutParams params = |
| new FrameLayout.LayoutParams(requested.width, requested.height); |
| params.gravity = Gravity.CENTER; |
| view.setLayoutParams(params); |
| } |
| |
| /** |
| * Inflate and return the default layout requested by gadget provider. |
| */ |
| protected View getDefaultView() { |
| View defaultView = null; |
| Exception exception = null; |
| |
| try { |
| if (mInfo != null) { |
| Context theirContext = mLocalContext.createPackageContext( |
| mInfo.provider.getPackageName(), 0 /* no flags */); |
| LayoutInflater inflater = (LayoutInflater) |
| theirContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); |
| inflater = inflater.cloneInContext(theirContext); |
| inflater.setFilter(sInflaterFilter); |
| defaultView = inflater.inflate(mInfo.initialLayout, this, false); |
| } else { |
| Log.w(TAG, "can't inflate defaultView because mInfo is missing"); |
| } |
| } catch (PackageManager.NameNotFoundException e) { |
| exception = e; |
| } catch (RuntimeException e) { |
| exception = e; |
| } |
| |
| if (exception != null && LOGD) { |
| Log.w(TAG, "Error inflating gadget " + mInfo, exception); |
| } |
| |
| if (defaultView == null) { |
| if (LOGD) Log.d(TAG, "getDefaultView couldn't find any view, so inflating error"); |
| defaultView = getErrorView(); |
| } |
| |
| return defaultView; |
| } |
| |
| /** |
| * Inflate and return a view that represents an error state. |
| */ |
| protected View getErrorView() { |
| TextView tv = new TextView(mLocalContext); |
| // TODO: move this error string and background color into resources |
| tv.setText("Error inflating gadget"); |
| tv.setBackgroundColor(Color.argb(127, 0, 0, 0)); |
| return tv; |
| } |
| } |