Merge "Moving widget reinflation to push model" into ub-launcher3-master
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 7688b9d..ba31926 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -867,17 +867,10 @@
         if (mOnResumeState == State.WORKSPACE) {
             showWorkspace(false);
         } else if (mOnResumeState == State.APPS) {
-            boolean launchedFromApp = (mWaitingForResume != null);
-            // Don't update the predicted apps if the user is returning to launcher in the apps
-            // view after launching an app, as they may be depending on the UI to be static to
-            // switch to another app, otherwise, if it was
-            showAppsView(false /* animated */, !launchedFromApp /* updatePredictedApps */);
+            showAppsView(false /* animated */);
         } else if (mOnResumeState == State.WIDGETS) {
             showWidgetsView(false, false);
         }
-        if (mOnResumeState != State.APPS) {
-            tryAndUpdatePredictedApps();
-        }
         mOnResumeState = State.NONE;
 
         mPaused = false;
@@ -909,7 +902,6 @@
         }
 
         updateInteraction(Workspace.State.NORMAL, mWorkspace.getState());
-        mWorkspace.onResume();
 
         // Process any items that were added while Launcher was away.
         InstallShortcutReceiver.disableAndFlushInstallQueue(
@@ -2100,7 +2092,7 @@
         if (!isAppsViewVisible()) {
             getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
                     ControlType.ALL_APPS_BUTTON);
-            showAppsView(true /* animated */, true /* updatePredictedApps */);
+            showAppsView(true /* animated */);
         } else {
             showWorkspace(true);
         }
@@ -2665,11 +2657,8 @@
     /**
      * Shows the apps view.
      */
-    public void showAppsView(boolean animated, boolean updatePredictedApps) {
+    public void showAppsView(boolean animated) {
         markAppsViewShown();
-        if (updatePredictedApps) {
-            tryAndUpdatePredictedApps();
-        }
         showAppsOrWidgets(State.APPS, animated);
     }
 
@@ -2789,7 +2778,7 @@
 
     public void exitSpringLoadedDragMode() {
         if (mState == State.APPS_SPRING_LOADED) {
-            showAppsView(true /* animated */, false /* updatePredictedApps */);
+            showAppsView(true /* animated */);
         } else if (mState == State.WIDGETS_SPRING_LOADED) {
             showWidgetsView(true, false);
         } else if (mState == State.WORKSPACE_SPRING_LOADED) {
@@ -2797,19 +2786,6 @@
         }
     }
 
-    /**
-     * Updates the set of predicted apps if it hasn't been updated since the last time Launcher was
-     * resumed.
-     */
-    public void tryAndUpdatePredictedApps() {
-        if (mLauncherCallbacks != null) {
-            List<ComponentKeyMapper<AppInfo>> apps = mLauncherCallbacks.getPredictedApps();
-            if (apps != null) {
-                mAppsView.setPredictedApps(apps);
-            }
-        }
-    }
-
     @Override
     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
         final boolean result = super.dispatchPopulateAccessibilityEvent(event);
@@ -3527,7 +3503,6 @@
         // Update AllApps
         if (mAppsView != null) {
             mAppsView.removeApps(appInfos);
-            tryAndUpdatePredictedApps();
         }
     }
 
@@ -3691,7 +3666,7 @@
             switch (keyCode) {
                 case KeyEvent.KEYCODE_A:
                     if (mState == State.WORKSPACE) {
-                        showAppsView(true, true);
+                        showAppsView(true);
                         return true;
                     }
                     break;
diff --git a/src/com/android/launcher3/LauncherCallbacks.java b/src/com/android/launcher3/LauncherCallbacks.java
index d1e2b62..2c9a23f 100644
--- a/src/com/android/launcher3/LauncherCallbacks.java
+++ b/src/com/android/launcher3/LauncherCallbacks.java
@@ -19,14 +19,10 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.view.Menu;
-import android.view.View;
-
-import com.android.launcher3.util.ComponentKeyMapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * LauncherCallbacks is an interface used to extend the Launcher activity. It includes many hooks
@@ -87,5 +83,4 @@
      */
     boolean shouldMoveToDefaultScreenOnHomeIntent();
     boolean hasSettings();
-    List<ComponentKeyMapper<AppInfo>> getPredictedApps();
 }
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 7297ee1..6564874 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1430,10 +1430,6 @@
         mWallpaperOffset.setWindowToken(null);
     }
 
-    protected void onResume() {
-        mWallpaperOffset.onResume();
-    }
-
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (mUnlockWallpaperFromDefaultPageOnLayout) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 4eba5c6..246a77a 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -115,13 +115,6 @@
     }
 
     /**
-     * Sets the current set of predicted apps.
-     */
-    public void setPredictedApps(List<ComponentKeyMapper<AppInfo>> apps) {
-        mApps.setPredictedApps(apps);
-    }
-
-    /**
      * Sets the current set of apps.
      */
     public void setApps(List<AppInfo> apps) {
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index f249c90..2cc0781 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -216,7 +216,7 @@
                             Action.Direction.UP,
                             containerType);
                 }
-                mLauncher.showAppsView(true /* animated */, false /* updatePredictedApps */);
+                mLauncher.showAppsView(true /* animated */);
                 if (hasSpringAnimationHandler()) {
                     mSpringAnimationHandler.add(mSearchSpring, true /* setDefaultValues */);
                     // The icons are moving upwards, so we go to 0 from 1. (y-axis 1 is below 0.)
@@ -239,7 +239,7 @@
                             Action.Direction.UP,
                             containerType);
                 }
-                mLauncher.showAppsView(true, /* animated */ false /* updatePredictedApps */);
+                mLauncher.showAppsView(true /* animated */);
             }
         }
     }
@@ -256,10 +256,7 @@
             // Initialize values that should not change until #onDragEnd
             mStatusBarHeight = mLauncher.getDragLayer().getInsets().top;
             mHotseat.setVisibility(View.VISIBLE);
-            if (!mLauncher.isAllAppsVisible()) {
-                mLauncher.tryAndUpdatePredictedApps();
-                mAppsView.setVisibility(View.VISIBLE);
-            }
+            mAppsView.setVisibility(View.VISIBLE);
         }
     }
 
diff --git a/src/com/android/launcher3/testing/LauncherExtension.java b/src/com/android/launcher3/testing/LauncherExtension.java
index 355963b..a1a4d75 100644
--- a/src/com/android/launcher3/testing/LauncherExtension.java
+++ b/src/com/android/launcher3/testing/LauncherExtension.java
@@ -7,13 +7,10 @@
 import com.android.launcher3.AppInfo;
 import com.android.launcher3.Launcher;
 import com.android.launcher3.LauncherCallbacks;
-import com.android.launcher3.util.ComponentKey;
-import com.android.launcher3.util.ComponentKeyMapper;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * This class represents a very trivial LauncherExtension. It primarily serves as a simple
@@ -150,12 +147,6 @@
         }
 
         @Override
-        public List<ComponentKeyMapper<AppInfo>> getPredictedApps() {
-            // To debug app predictions, enable AlphabeticalAppsList#DEBUG_PREDICTIONS
-            return new ArrayList<>();
-        }
-
-        @Override
         public void onAttachedToWindow() {
         }
 
diff --git a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
index f99efce..ec494f1 100644
--- a/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
+++ b/src/com/android/launcher3/util/WallpaperOffsetInterpolator.java
@@ -1,9 +1,15 @@
 package com.android.launcher3.util;
 
 import android.app.WallpaperManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
 import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemClock;
 import android.util.Log;
-import android.view.Choreographer;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
@@ -13,60 +19,30 @@
 /**
  * Utility class to handle wallpaper scrolling along with workspace.
  */
-public class WallpaperOffsetInterpolator implements Choreographer.FrameCallback {
+public class WallpaperOffsetInterpolator extends BroadcastReceiver {
+
+    private static final int[] sTempInt = new int[2];
     private static final String TAG = "WPOffsetInterpolator";
     private static final int ANIMATION_DURATION = 250;
 
     // Don't use all the wallpaper for parallax until you have at least this many pages
     private static final int MIN_PARALLAX_PAGE_SPAN = 4;
 
-    private final Choreographer mChoreographer;
-    private final Interpolator mInterpolator;
-    private final WallpaperManager mWallpaperManager;
     private final Workspace mWorkspace;
     private final boolean mIsRtl;
+    private final Handler mHandler;
 
+    private boolean mRegistered = false;
     private IBinder mWindowToken;
     private boolean mWallpaperIsLiveWallpaper;
-    private float mLastSetWallpaperOffsetSteps = 0;
 
-    private float mFinalOffset = 0.0f;
-    private float mCurrentOffset = 0.5f; // to force an initial update
-    private boolean mWaitingForUpdate;
     private boolean mLockedToDefaultPage;
-
-    private boolean mAnimating;
-    private long mAnimationStartTime;
-    private float mAnimationStartOffset;
-    int mNumScreens;
-    int mNumPagesForWallpaperParallax;
+    private int mNumScreens;
 
     public WallpaperOffsetInterpolator(Workspace workspace) {
-        mChoreographer = Choreographer.getInstance();
-        mInterpolator = new DecelerateInterpolator(1.5f);
-
         mWorkspace = workspace;
-        mWallpaperManager = WallpaperManager.getInstance(workspace.getContext());
         mIsRtl = Utilities.isRtl(workspace.getResources());
-    }
-
-    @Override
-    public void doFrame(long frameTimeNanos) {
-        updateOffset(false);
-    }
-
-    private void updateOffset(boolean force) {
-        if (mWaitingForUpdate || force) {
-            mWaitingForUpdate = false;
-            if (computeScrollOffset() && mWindowToken != null) {
-                try {
-                    mWallpaperManager.setWallpaperOffsets(mWindowToken, getCurrX(), 0.5f);
-                    setWallpaperOffsetSteps();
-                } catch (IllegalArgumentException e) {
-                    Log.e(TAG, "Error updating wallpaper offset: " + e);
-                }
-            }
-        }
+        mHandler = new OffsetHandler(workspace.getContext());
     }
 
     /**
@@ -80,46 +56,25 @@
         return mLockedToDefaultPage;
     }
 
-    public boolean computeScrollOffset() {
-        final float oldOffset = mCurrentOffset;
-        if (mAnimating) {
-            long durationSinceAnimation = System.currentTimeMillis() - mAnimationStartTime;
-            float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
-            float t1 = mInterpolator.getInterpolation(t0);
-            mCurrentOffset = mAnimationStartOffset +
-                    (mFinalOffset - mAnimationStartOffset) * t1;
-            mAnimating = durationSinceAnimation < ANIMATION_DURATION;
-        } else {
-            mCurrentOffset = mFinalOffset;
-        }
-
-        if (Math.abs(mCurrentOffset - mFinalOffset) > 0.0000001f) {
-            scheduleUpdate();
-        }
-        if (Math.abs(oldOffset - mCurrentOffset) > 0.0000001f) {
-            return true;
-        }
-        return false;
-    }
-
     /**
+     * Computes the wallpaper offset as an int ratio (out[0] / out[1])
+     *
      * TODO: do different behavior if it's  a live wallpaper?
      */
-    public float wallpaperOffsetForScroll(int scroll) {
+    private void wallpaperOffsetForScroll(int scroll, int numScrollingPages, final int[] out) {
+        out[1] = 1;
+
         // To match the default wallpaper behavior in the system, we default to either the left
         // or right edge on initialization
-        int numScrollingPages = getNumScreensExcludingEmpty();
         if (mLockedToDefaultPage || numScrollingPages <= 1) {
-            return mIsRtl ? 1f : 0f;
+            out[0] =  mIsRtl ? 1 : 0;
+            return;
         }
 
         // Distribute the wallpaper parallax over a minimum of MIN_PARALLAX_PAGE_SPAN workspace
         // screens, not including the custom screen, and empty screens (if > MIN_PARALLAX_PAGE_SPAN)
-        if (mWallpaperIsLiveWallpaper) {
-            mNumPagesForWallpaperParallax = numScrollingPages;
-        } else {
-            mNumPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages);
-        }
+        int numPagesForWallpaperParallax = mWallpaperIsLiveWallpaper ? numScrollingPages :
+                        Math.max(MIN_PARALLAX_PAGE_SPAN, numScrollingPages);
 
         // Offset by the custom screen
         int leftPageIndex;
@@ -136,106 +91,184 @@
         int leftPageScrollX = mWorkspace.getScrollForPage(leftPageIndex);
         int rightPageScrollX = mWorkspace.getScrollForPage(rightPageIndex);
         int scrollRange = rightPageScrollX - leftPageScrollX;
-        if (scrollRange == 0) {
-            return 0f;
+        if (scrollRange <= 0) {
+            out[0] = 0;
+            return;
         }
 
         // Sometimes the left parameter of the pages is animated during a layout transition;
         // this parameter offsets it to keep the wallpaper from animating as well
         int adjustedScroll = scroll - leftPageScrollX -
                 mWorkspace.getLayoutTransitionOffsetForPage(0);
-        float offset = Utilities.boundToRange((float) adjustedScroll / scrollRange, 0f, 1f);
+        adjustedScroll = Utilities.boundToRange(adjustedScroll, 0, scrollRange);
+        out[1] = (numPagesForWallpaperParallax - 1) * scrollRange;
 
         // The offset is now distributed 0..1 between the left and right pages that we care about,
         // so we just map that between the pages that we are using for parallax
-        float rtlOffset = 0;
+        int rtlOffset = 0;
         if (mIsRtl) {
             // In RTL, the pages are right aligned, so adjust the offset from the end
-            rtlOffset = (float) ((mNumPagesForWallpaperParallax - 1) - (numScrollingPages - 1)) /
-                    (mNumPagesForWallpaperParallax - 1);
+            rtlOffset = out[1] - (numScrollingPages - 1) * scrollRange;
         }
-        return rtlOffset + offset *
-                ((float) (numScrollingPages - 1) / (mNumPagesForWallpaperParallax - 1));
+        out[0] = rtlOffset + adjustedScroll * (numScrollingPages - 1);
     }
 
-    private float wallpaperOffsetForCurrentScroll() {
-        return wallpaperOffsetForScroll(mWorkspace.getScrollX());
-    }
-
-    private int numEmptyScreensToIgnore() {
-        int numScrollingPages = mWorkspace.getChildCount();
-        if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) {
-            return 1;
-        } else {
-            return 0;
-        }
+    public float wallpaperOffsetForScroll(int scroll) {
+        wallpaperOffsetForScroll(scroll, getNumScreensExcludingEmpty(), sTempInt);
+        return ((float) sTempInt[0]) / sTempInt[1];
     }
 
     private int getNumScreensExcludingEmpty() {
-        return mWorkspace.getChildCount() - numEmptyScreensToIgnore();
+        int numScrollingPages = mWorkspace.getChildCount();
+        if (numScrollingPages >= MIN_PARALLAX_PAGE_SPAN && mWorkspace.hasExtraEmptyScreen()) {
+            return numScrollingPages - 1;
+        } else {
+            return numScrollingPages;
+        }
     }
 
     public void syncWithScroll() {
-        float offset = wallpaperOffsetForCurrentScroll();
-        setFinalX(offset);
-        updateOffset(true);
-    }
-
-    public float getCurrX() {
-        return mCurrentOffset;
-    }
-
-    public float getFinalX() {
-        return mFinalOffset;
-    }
-
-    private void animateToFinal() {
-        mAnimating = true;
-        mAnimationStartOffset = mCurrentOffset;
-        mAnimationStartTime = System.currentTimeMillis();
-    }
-
-    private void setWallpaperOffsetSteps() {
-        // Set wallpaper offset steps (1 / (number of screens - 1))
-        float xOffset = 1.0f / (mNumPagesForWallpaperParallax - 1);
-        if (xOffset != mLastSetWallpaperOffsetSteps) {
-            mWallpaperManager.setWallpaperOffsetSteps(xOffset, 1.0f);
-            mLastSetWallpaperOffsetSteps = xOffset;
-        }
-    }
-
-    public void setFinalX(float x) {
-        scheduleUpdate();
-        mFinalOffset = Math.max(0f, Math.min(x, 1f));
-        if (getNumScreensExcludingEmpty() != mNumScreens) {
-            if (mNumScreens > 0 && Float.compare(mCurrentOffset, mFinalOffset) != 0) {
-                // Don't animate if we're going from 0 screens, or if the final offset is the same
-                // as the current offset
-                animateToFinal();
+        int numScreens = getNumScreensExcludingEmpty();
+        wallpaperOffsetForScroll(mWorkspace.getScrollX(), numScreens, sTempInt);
+        Message msg = Message.obtain(mHandler, MSG_UPDATE_OFFSET, sTempInt[0], sTempInt[1],
+                mWindowToken);
+        if (numScreens != mNumScreens) {
+            if (mNumScreens > 0) {
+                // Don't animate if we're going from 0 screens
+                msg.what = MSG_START_ANIMATION;
             }
-            mNumScreens = getNumScreensExcludingEmpty();
+            mNumScreens = numScreens;
+            updateOffset();
         }
+        msg.sendToTarget();
     }
 
-    private void scheduleUpdate() {
-        if (!mWaitingForUpdate) {
-            mChoreographer.postFrameCallback(this);
-            mWaitingForUpdate = true;
+    private void updateOffset() {
+        int numPagesForWallpaperParallax;
+        if (mWallpaperIsLiveWallpaper) {
+            numPagesForWallpaperParallax = mNumScreens;
+        } else {
+            numPagesForWallpaperParallax = Math.max(MIN_PARALLAX_PAGE_SPAN, mNumScreens);
         }
+        Message.obtain(mHandler, MSG_SET_NUM_PARALLAX, numPagesForWallpaperParallax, 0,
+                mWindowToken).sendToTarget();
     }
 
     public void jumpToFinal() {
-        mCurrentOffset = mFinalOffset;
-    }
-
-    public void onResume() {
-        mWallpaperIsLiveWallpaper = mWallpaperManager.getWallpaperInfo() != null;
-        // Force the wallpaper offset steps to be set again, because another app might have changed
-        // them
-        mLastSetWallpaperOffsetSteps = 0f;
+        Message.obtain(mHandler, MSG_JUMP_TO_FINAL, mWindowToken).sendToTarget();
     }
 
     public void setWindowToken(IBinder token) {
         mWindowToken = token;
+        if (mWindowToken == null && mRegistered) {
+            mWorkspace.getContext().unregisterReceiver(this);
+            mRegistered = false;
+        } else if (mWindowToken != null && !mRegistered) {
+            mWorkspace.getContext()
+                    .registerReceiver(this, new IntentFilter(Intent.ACTION_WALLPAPER_CHANGED));
+            onReceive(mWorkspace.getContext(), null);
+            mRegistered = true;
+        }
+    }
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mWallpaperIsLiveWallpaper =
+                WallpaperManager.getInstance(mWorkspace.getContext()).getWallpaperInfo() != null;
+        updateOffset();
+    }
+
+    private static final int MSG_START_ANIMATION = 1;
+    private static final int MSG_UPDATE_OFFSET = 2;
+    private static final int MSG_APPLY_OFFSET = 3;
+    private static final int MSG_SET_NUM_PARALLAX = 4;
+    private static final int MSG_JUMP_TO_FINAL = 5;
+
+    private static class OffsetHandler extends Handler {
+
+        private final Interpolator mInterpolator;
+        private final WallpaperManager mWM;
+
+        private float mCurrentOffset = 0.5f; // to force an initial update
+        private boolean mAnimating;
+        private long mAnimationStartTime;
+        private float mAnimationStartOffset;
+
+        private float mFinalOffset;
+        private float mOffsetX;
+
+        public OffsetHandler(Context context) {
+            super(UiThreadHelper.getBackgroundLooper());
+            mInterpolator = new DecelerateInterpolator(1.5f);
+            mWM = WallpaperManager.getInstance(context);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            final IBinder token = (IBinder) msg.obj;
+            if (token == null) {
+                return;
+            }
+
+            switch (msg.what) {
+                case MSG_START_ANIMATION: {
+                    mAnimating = true;
+                    mAnimationStartOffset = mCurrentOffset;
+                    mAnimationStartTime = msg.getWhen();
+                    // Follow through
+                }
+                case MSG_UPDATE_OFFSET:
+                    mFinalOffset = ((float) msg.arg1) / msg.arg2;
+                    // Follow through
+                case MSG_APPLY_OFFSET: {
+                    float oldOffset = mCurrentOffset;
+                    if (mAnimating) {
+                        long durationSinceAnimation = SystemClock.uptimeMillis()
+                                - mAnimationStartTime;
+                        float t0 = durationSinceAnimation / (float) ANIMATION_DURATION;
+                        float t1 = mInterpolator.getInterpolation(t0);
+                        mCurrentOffset = mAnimationStartOffset +
+                                (mFinalOffset - mAnimationStartOffset) * t1;
+                        mAnimating = durationSinceAnimation < ANIMATION_DURATION;
+                    } else {
+                        mCurrentOffset = mFinalOffset;
+                    }
+
+                    if (Float.compare(mCurrentOffset, oldOffset) != 0) {
+                        setOffsetSafely(token);
+                        // Force the wallpaper offset steps to be set again, because another app
+                        // might have changed them
+                        mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
+                    }
+                    if (mAnimating) {
+                        // If we are animating, keep updating the offset
+                        Message.obtain(this, MSG_APPLY_OFFSET, token).sendToTarget();
+                    }
+                    return;
+                }
+                case MSG_SET_NUM_PARALLAX: {
+                    // Set wallpaper offset steps (1 / (number of screens - 1))
+                    mOffsetX = 1.0f / (msg.arg1 - 1);
+                    mWM.setWallpaperOffsetSteps(mOffsetX, 1.0f);
+                    return;
+                }
+                case MSG_JUMP_TO_FINAL: {
+                    if (Float.compare(mCurrentOffset, mFinalOffset) != 0) {
+                        mCurrentOffset = mFinalOffset;
+                        setOffsetSafely(token);
+                    }
+                    mAnimating = false;
+                    return;
+                }
+            }
+        }
+
+        private void setOffsetSafely(IBinder token) {
+            try {
+                mWM.setWallpaperOffsets(token, mCurrentOffset, 0.5f);
+            } catch (IllegalArgumentException e) {
+                Log.e(TAG, "Error updating wallpaper offset: " + e);
+            }
+        }
     }
 }
\ No newline at end of file