Reduce jank around loading view when opening data usage UI

Change-Id: I3d23d8160b046de8fe125ba0697b7b3d7786453c
Fix: 28181319
Test: robotests
diff --git a/src/com/android/settings/SettingsPreferenceFragment.java b/src/com/android/settings/SettingsPreferenceFragment.java
index 5f22545..a3d26af 100644
--- a/src/com/android/settings/SettingsPreferenceFragment.java
+++ b/src/com/android/settings/SettingsPreferenceFragment.java
@@ -49,6 +49,7 @@
 import com.android.settings.core.InstrumentedPreferenceFragment;
 import com.android.settings.core.instrumentation.Instrumentable;
 import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
+import com.android.settings.widget.LoadingViewController;
 import com.android.settingslib.CustomDialogPreference;
 import com.android.settingslib.CustomEditTextPreference;
 import com.android.settingslib.HelpUtils;
@@ -240,14 +241,11 @@
         unregisterObserverIfNeeded();
     }
 
-    public void showLoadingWhenEmpty() {
-        View loading = getView().findViewById(R.id.loading_container);
-        setEmptyView(loading);
-    }
-
     public void setLoading(boolean loading, boolean animate) {
-        View loading_container = getView().findViewById(R.id.loading_container);
-        Utils.handleLoadingContainer(loading_container, getListView(), !loading, animate);
+        View loadingContainer = getView().findViewById(R.id.loading_container);
+        LoadingViewController.handleLoadingContainer(loadingContainer, getListView(),
+                !loading /* done */,
+                animate);
     }
 
     public void registerObserverIfNeeded() {
diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java
index cb8d3c2..b6a958c 100644
--- a/src/com/android/settings/Utils.java
+++ b/src/com/android/settings/Utils.java
@@ -949,43 +949,6 @@
         return result;
     }
 
-    // TODO: move this out of Utils to a mixin or a controller or a helper class.
-    @Deprecated
-    public static void handleLoadingContainer(View loading, View doneLoading, boolean done,
-            boolean animate) {
-        setViewShown(loading, !done, animate);
-        setViewShown(doneLoading, done, animate);
-    }
-
-    private static void setViewShown(final View view, boolean shown, boolean animate) {
-        if (animate) {
-            Animation animation = AnimationUtils.loadAnimation(view.getContext(),
-                    shown ? android.R.anim.fade_in : android.R.anim.fade_out);
-            if (shown) {
-                view.setVisibility(View.VISIBLE);
-            } else {
-                animation.setAnimationListener(new AnimationListener() {
-                    @Override
-                    public void onAnimationStart(Animation animation) {
-                    }
-
-                    @Override
-                    public void onAnimationRepeat(Animation animation) {
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animation animation) {
-                        view.setVisibility(View.INVISIBLE);
-                    }
-                });
-            }
-            view.startAnimation(animation);
-        } else {
-            view.clearAnimation();
-            view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
-        }
-    }
-
     /**
      * Returns the application info of the currently installed MDM package.
      */
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
index 07632da..720f826 100644
--- a/src/com/android/settings/applications/ManageApplications.java
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -81,6 +81,7 @@
 import com.android.settings.notification.ConfigureNotificationSettings;
 import com.android.settings.notification.NotificationBackend;
 import com.android.settings.notification.NotificationBackend.AppRow;
+import com.android.settings.widget.LoadingViewController;
 import com.android.settingslib.HelpUtils;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
@@ -848,6 +849,7 @@
         private final AppStateBaseBridge mExtraInfoBridge;
         private final Handler mBgHandler;
         private final Handler mFgHandler;
+        private final LoadingViewController mLoadingViewController;
 
         private int mFilterMode;
         private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
@@ -893,12 +895,6 @@
             }
         };
 
-        private Runnable mShowLoadingContainerRunnable = new Runnable() {
-            public void run() {
-                Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
-                        mManageApplications.mListContainer, false /* done */, false /* animate */);
-            }
-        };
 
         public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
                 int filterMode) {
@@ -907,6 +903,10 @@
             mBgHandler = new Handler(mState.getBackgroundLooper());
             mSession = state.newSession(this);
             mManageApplications = manageApplications;
+            mLoadingViewController = new LoadingViewController(
+                    mManageApplications.mLoadingContainer,
+                    mManageApplications.mListContainer
+            );
             mContext = manageApplications.getActivity();
             mPm = mContext.getPackageManager();
             mFilterMode = filterMode;
@@ -1108,11 +1108,7 @@
 
             if (mSession.getAllApps().size() != 0
                     && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
-                // Cancel any pending task to show the loading animation and show the list of
-                // apps directly.
-                mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
-                Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
-                        mManageApplications.mListContainer, true, true);
+                mLoadingViewController.showContent(true /* animate */);
             }
             if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
                 // No enabled or disabled filters for usage access.
@@ -1166,11 +1162,9 @@
         void updateLoading() {
             final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0;
             if (appLoaded) {
-                Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
-                        mManageApplications.mListContainer, true /* done */, false /* animate */);
+                mLoadingViewController.showContent(false /* animate */);
             } else {
-                mFgHandler.postDelayed(
-                        mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS);
+                mLoadingViewController.showLoadingViewDelayed();
             }
         }
 
diff --git a/src/com/android/settings/applications/RunningServices.java b/src/com/android/settings/applications/RunningServices.java
index 736eafb..634fefd 100644
--- a/src/com/android/settings/applications/RunningServices.java
+++ b/src/com/android/settings/applications/RunningServices.java
@@ -15,7 +15,6 @@
  */
 package com.android.settings.applications;
 
-import android.app.Fragment;
 import android.os.Bundle;
 import android.view.LayoutInflater;
 import android.view.Menu;
@@ -27,7 +26,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.Utils;
+import com.android.settings.widget.LoadingViewController;
 
 public class RunningServices extends SettingsPreferenceFragment {
 
@@ -37,6 +36,7 @@
     private RunningProcessesView mRunningProcessesView;
     private Menu mOptionsMenu;
     private View mLoadingContainer;
+    private LoadingViewController mLoadingViewController;
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
@@ -47,12 +47,13 @@
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                Bundle savedInstanceState) {
+            Bundle savedInstanceState) {
         View rootView = inflater.inflate(R.layout.manage_applications_running, null);
-        mRunningProcessesView = (RunningProcessesView) rootView.findViewById(
-                R.id.running_processes);
+        mRunningProcessesView = rootView.findViewById(R.id.running_processes);
         mRunningProcessesView.doCreate();
         mLoadingContainer = rootView.findViewById(R.id.loading_container);
+        mLoadingViewController = new LoadingViewController(
+                mLoadingContainer, mRunningProcessesView);
 
         return rootView;
     }
@@ -71,7 +72,7 @@
     public void onResume() {
         super.onResume();
         boolean haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail);
-        Utils.handleLoadingContainer(mLoadingContainer, mRunningProcessesView, haveData, false);
+        mLoadingViewController.handleLoadingContainer(haveData /* done */, false /* animate */);
     }
 
     @Override
@@ -115,7 +116,7 @@
     private final Runnable mRunningProcessesAvail = new Runnable() {
         @Override
         public void run() {
-            Utils.handleLoadingContainer(mLoadingContainer, mRunningProcessesView, true, true);
+            mLoadingViewController.showContent(true /* animate */);
         }
     };
 
diff --git a/src/com/android/settings/datausage/DataUsageList.java b/src/com/android/settings/datausage/DataUsageList.java
index de92154..f0048a3 100644
--- a/src/com/android/settings/datausage/DataUsageList.java
+++ b/src/com/android/settings/datausage/DataUsageList.java
@@ -50,6 +50,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settings.R;
 import com.android.settings.datausage.CycleAdapter.SpinnerInterface;
+import com.android.settings.widget.LoadingViewController;
 import com.android.settingslib.AppItem;
 import com.android.settingslib.net.ChartData;
 import com.android.settingslib.net.ChartDataLoader;
@@ -96,13 +97,13 @@
             };
 
     private INetworkStatsSession mStatsSession;
-
     private ChartDataUsagePreference mChart;
 
     private NetworkTemplate mTemplate;
     private int mSubId;
     private ChartData mChartData;
 
+    private LoadingViewController mLoadingViewController;
     private UidDetailProvider mUidDetailProvider;
     private CycleAdapter mCycleAdapter;
     private Spinner mCycleSpinner;
@@ -110,6 +111,7 @@
     private PreferenceGroup mApps;
     private View mHeader;
 
+
     @Override
     public int getMetricsCategory() {
         return MetricsEvent.DATA_USAGE_LIST;
@@ -176,7 +178,10 @@
                 mCycleSpinner.setSelection(position);
             }
         }, mCycleListener, true);
-        setLoading(true, false);
+
+        mLoadingViewController = new LoadingViewController(
+                getView().findViewById(R.id.loading_container), getListView());
+        mLoadingViewController.showLoadingViewDelayed();
     }
 
     @Override
@@ -523,7 +528,7 @@
 
         @Override
         public void onLoadFinished(Loader<ChartData> loader, ChartData data) {
-            setLoading(false, true);
+            mLoadingViewController.showContent(false /* animate */);
             mChartData = data;
             mChart.setNetworkStats(mChartData.network);
 
diff --git a/src/com/android/settings/widget/LoadingViewController.java b/src/com/android/settings/widget/LoadingViewController.java
new file mode 100644
index 0000000..294e55e
--- /dev/null
+++ b/src/com/android/settings/widget/LoadingViewController.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 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.settings.widget;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.view.View;
+import android.view.animation.Animation;
+import android.view.animation.AnimationUtils;
+
+/**
+ * A helper class that manages show/hide loading spinner.
+ */
+public class LoadingViewController {
+
+    private static final long DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS = 100L;
+
+    public final Handler mFgHandler;
+    public final View mLoadingView;
+    public final View mContentView;
+
+    public LoadingViewController(View loadingView, View contentView) {
+        mLoadingView = loadingView;
+        mContentView = contentView;
+        mFgHandler = new Handler(Looper.getMainLooper());
+    }
+
+    private Runnable mShowLoadingContainerRunnable = new Runnable() {
+        public void run() {
+            handleLoadingContainer(false /* done */, false /* animate */);
+        }
+    };
+
+    public void showContent(boolean animate) {
+        // Cancel any pending task to show the loading animation and show the list of
+        // apps directly.
+        mFgHandler.removeCallbacks(mShowLoadingContainerRunnable);
+        handleLoadingContainer(true /* show */, animate);
+    }
+
+    public void showLoadingViewDelayed() {
+        mFgHandler.postDelayed(
+                mShowLoadingContainerRunnable, DELAY_SHOW_LOADING_CONTAINER_THRESHOLD_MS);
+    }
+
+    public void handleLoadingContainer(boolean done, boolean animate) {
+        handleLoadingContainer(mLoadingView, mContentView, done, animate);
+    }
+
+    /**
+     * Show/hide loading view and content view.
+     *
+     * @param loading The loading spinner view
+     * @param content The content view
+     * @param done    If true, content is set visible and loading is set invisible.
+     * @param animate Whether or not content/loading views should animate in/out.
+     */
+    public static void handleLoadingContainer(View loading, View content, boolean done,
+            boolean animate) {
+        setViewShown(loading, !done, animate);
+        setViewShown(content, done, animate);
+    }
+
+    private static void setViewShown(final View view, boolean shown, boolean animate) {
+        if (animate) {
+            Animation animation = AnimationUtils.loadAnimation(view.getContext(),
+                    shown ? android.R.anim.fade_in : android.R.anim.fade_out);
+            if (shown) {
+                view.setVisibility(View.VISIBLE);
+            } else {
+                animation.setAnimationListener(new Animation.AnimationListener() {
+                    @Override
+                    public void onAnimationStart(Animation animation) {
+                    }
+
+                    @Override
+                    public void onAnimationRepeat(Animation animation) {
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animation animation) {
+                        view.setVisibility(View.INVISIBLE);
+                    }
+                });
+            }
+            view.startAnimation(animation);
+        } else {
+            view.clearAnimation();
+            view.setVisibility(shown ? View.VISIBLE : View.INVISIBLE);
+        }
+    }
+}