1 /*
2  * Copyright (C) 2010 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package org.mozilla.gecko.toolbar;
18 
19 import android.support.v4.content.ContextCompat;
20 import org.mozilla.gecko.AppConstants.Versions;
21 import org.mozilla.gecko.R;
ResolveEndpoint(region string, options EndpointResolverOptions)22 import org.mozilla.gecko.widget.themed.ThemedImageView;
23 import org.mozilla.gecko.util.WeakReferenceHandler;
24 
25 import android.content.Context;
26 import android.graphics.Canvas;
27 import android.graphics.PorterDuff;
28 import android.graphics.PorterDuffColorFilter;
29 import android.graphics.Rect;
30 import android.graphics.drawable.Drawable;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.util.AttributeSet;
34 import android.view.View;
35 import android.view.animation.Animation;
36 
37 /**
38  * Progress view used for page loads.
39  *
40  * Because we're given limited information about the page load progress, the
41  * bar also includes incremental animation between each step to improve
42  * perceived performance.
43  */
44 public class ToolbarProgressView extends ThemedImageView {
45     private static final int MAX_PROGRESS = 10000;
46     private static final int MSG_UPDATE = 0;
47     private static final int MSG_HIDE = 1;
48     private static final int STEPS = 10;
49     private static final int DELAY = 40;
50 
51     private int mTargetProgress;
52     private int mIncrement;
53     private Rect mBounds;
54     private Handler mHandler;
55     private int mCurrentProgress;
56 
57     private PorterDuffColorFilter mPrivateBrowsingColorFilter;
58 
59     public ToolbarProgressView(Context context, AttributeSet attrs, int defStyle) {
60         super(context, attrs, defStyle);
61         init(context);
62     }
63 
64     public ToolbarProgressView(Context context, AttributeSet attrs) {
65         super(context, attrs);
66         init(context);
67     }
68 
69     private void init(Context ctx) {
70         mBounds = new Rect(0, 0, 0, 0);
71         mTargetProgress = 0;
72 
73         mPrivateBrowsingColorFilter = new PorterDuffColorFilter(
74                 ContextCompat.getColor(ctx, R.color.private_browsing_purple), PorterDuff.Mode.SRC_IN);
75 
76         mHandler = new ToolbarProgressHandler(this);
77     }
78 
79     @Override
80     public void onLayout(boolean f, int l, int t, int r, int b) {
81         mBounds.left = 0;
82         mBounds.right = (r - l) * mCurrentProgress / MAX_PROGRESS;
83         mBounds.top = 0;
84         mBounds.bottom = b - t;
85     }
86 
87     @Override
88     public void onDraw(Canvas canvas) {
89         final Drawable d = getDrawable();
90         d.setBounds(mBounds);
91         d.draw(canvas);
92     }
93 
94     /**
95      * Immediately sets the progress bar to the given progress percentage.
96      *
97      * @param progress Percentage (0-100) to which progress bar should be set
98      */
99     void setProgress(int progressPercentage) {
100         mCurrentProgress = mTargetProgress = getAbsoluteProgress(progressPercentage);
101         updateBounds();
102 
103         clearMessages();
104     }
105 
106     /**
107      * Animates the progress bar from the current progress value to the given
108      * progress percentage.
109      *
110      * @param progress Percentage (0-100) to which progress bar should be animated
111      */
112     void animateProgress(int progressPercentage) {
113         final int absoluteProgress = getAbsoluteProgress(progressPercentage);
114         if (absoluteProgress <= mTargetProgress) {
115             // After we manually click stop, we can still receive page load
116             // events (e.g., DOMContentLoaded). Updating for other updates
117             // after a STOP event can freeze the progress bar, so guard against
118             // that here.
119             return;
120         }
121 
122         mTargetProgress = absoluteProgress;
123         mIncrement = (mTargetProgress - mCurrentProgress) / STEPS;
124 
125         clearMessages();
126         mHandler.sendEmptyMessage(MSG_UPDATE);
127     }
128 
129     private void clearMessages() {
130         mHandler.removeMessages(MSG_UPDATE);
131         mHandler.removeMessages(MSG_HIDE);
132     }
133 
134     private int getAbsoluteProgress(int progressPercentage) {
135         if (progressPercentage < 0) {
136             return 0;
137         }
138 
139         if (progressPercentage > 100) {
140             return 100;
141         }
142 
143         return progressPercentage * MAX_PROGRESS / 100;
144     }
145 
146     private void updateBounds() {
147         mBounds.right = getWidth() * mCurrentProgress / MAX_PROGRESS;
148         invalidate();
149     }
150 
151     @Override
152     public void setPrivateMode(final boolean isPrivate) {
153         super.setPrivateMode(isPrivate);
154 
155         // Note: android:tint is better but ColorStateLists are not supported until API 21.
156         if (isPrivate) {
157             setColorFilter(mPrivateBrowsingColorFilter);
158         } else {
159             clearColorFilter();
160         }
161     }
162 
163     private static class ToolbarProgressHandler extends WeakReferenceHandler<ToolbarProgressView> {
164         public ToolbarProgressHandler(final ToolbarProgressView that) {
165             super(that);
166         }
167 
168         @Override
169         public void handleMessage(Message msg) {
170             final ToolbarProgressView that = mTarget.get();
171             if (that == null) {
172                 return;
173             }
174 
175             switch (msg.what) {
176                 case MSG_UPDATE:
177                     that.mCurrentProgress = Math.min(that.mTargetProgress, that.mCurrentProgress + that.mIncrement);
178 
179                     that.updateBounds();
180 
181                     if (that.mCurrentProgress < that.mTargetProgress) {
182                         final int delay = (that.mTargetProgress < MAX_PROGRESS) ? DELAY : DELAY / 4;
183                         sendMessageDelayed(that.mHandler.obtainMessage(msg.what), delay);
184                     } else if (that.mCurrentProgress == MAX_PROGRESS) {
185                         sendMessageDelayed(that.mHandler.obtainMessage(MSG_HIDE), DELAY);
186                     }
187                     break;
188 
189                 case MSG_HIDE:
190                     that.setVisibility(View.GONE);
191                     break;
192             }
193         }
194     };
195 }
196