blob: 3ac67056ff809f0eb771e739f83900ff4ebffea9 [file] [log] [blame]
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -04001/*
2 * Copyright (C) 2013 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 */
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040016package com.android.systemui;
17
Charles He6a79b0d2017-09-18 09:50:58 +010018import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
19import static android.app.StatusBarManager.DISABLE_NONE;
Evan Lairdbcf631d2017-03-10 10:56:45 -050020import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
21
Jason Monk9a376bc2017-05-10 09:52:10 -040022import android.animation.ArgbEvaluator;
Jason Monka6f1db3a2017-08-30 19:18:00 -040023import android.app.ActivityManager;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040024import android.content.Context;
Jason Monkaa573e92017-01-27 17:00:29 -050025import android.content.res.Resources;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040026import android.content.res.TypedArray;
Dan Sandler055bb612017-02-08 16:21:49 -080027import android.database.ContentObserver;
Jason Monk9a376bc2017-05-10 09:52:10 -040028import android.graphics.Rect;
Dan Sandler055bb612017-02-08 16:21:49 -080029import android.net.Uri;
30import android.os.Handler;
31import android.provider.Settings;
Jason Monk9a376bc2017-05-10 09:52:10 -040032import android.util.ArraySet;
33import android.util.AttributeSet;
34import android.util.TypedValue;
35import android.view.ContextThemeWrapper;
Dan Sandler055bb612017-02-08 16:21:49 -080036import android.view.Gravity;
37import android.view.LayoutInflater;
Jason Monk3e189872016-01-12 09:10:34 -050038import android.view.View;
Dan Sandler055bb612017-02-08 16:21:49 -080039import android.view.ViewGroup;
Jason Monkabe19742015-09-29 09:47:06 -040040import android.widget.ImageView;
Jason Monkaa573e92017-01-27 17:00:29 -050041import android.widget.LinearLayout;
Dan Sandler055bb612017-02-08 16:21:49 -080042import android.widget.TextView;
Jason Monk9a376bc2017-05-10 09:52:10 -040043
44import com.android.settingslib.Utils;
Dan Sandler055bb612017-02-08 16:21:49 -080045import com.android.settingslib.graph.BatteryMeterDrawableBase;
Jason Monka6f1db3a2017-08-30 19:18:00 -040046import com.android.systemui.settings.CurrentUserTracker;
Jason Monk3e189872016-01-12 09:10:34 -050047import com.android.systemui.statusbar.phone.StatusBarIconController;
Jorim Jaggi708f7722014-08-20 17:30:38 +020048import com.android.systemui.statusbar.policy.BatteryController;
Jason Monkaa573e92017-01-27 17:00:29 -050049import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback;
50import com.android.systemui.statusbar.policy.ConfigurationController;
51import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
52import com.android.systemui.statusbar.policy.DarkIconDispatcher;
53import com.android.systemui.statusbar.policy.DarkIconDispatcher.DarkReceiver;
Jason Monkf8c2f7b2017-09-06 09:22:29 -040054import com.android.systemui.statusbar.policy.IconLogger;
Jason Monk3e189872016-01-12 09:10:34 -050055import com.android.systemui.tuner.TunerService;
Jason Monkaa573e92017-01-27 17:00:29 -050056import com.android.systemui.tuner.TunerService.Tunable;
Charles He6a79b0d2017-09-18 09:50:58 +010057import com.android.systemui.util.Utils.DisableStateTracker;
Evan Laird54ff81b2018-06-13 20:08:17 -040058import com.android.systemui.R;
Jorim Jaggi708f7722014-08-20 17:30:38 +020059
Lucas Dupinc510d412018-06-12 13:08:23 -070060import java.io.FileDescriptor;
61import java.io.PrintWriter;
Dan Sandler055bb612017-02-08 16:21:49 -080062import java.text.NumberFormat;
63
64public class BatteryMeterView extends LinearLayout implements
Jason Monkaa573e92017-01-27 17:00:29 -050065 BatteryStateChangeCallback, Tunable, DarkReceiver, ConfigurationListener {
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040066
Dan Sandler055bb612017-02-08 16:21:49 -080067 private final BatteryMeterDrawableBase mDrawable;
Jason Monk3e189872016-01-12 09:10:34 -050068 private final String mSlotBattery;
Dan Sandler055bb612017-02-08 16:21:49 -080069 private final ImageView mBatteryIconView;
Jason Monka6f1db3a2017-08-30 19:18:00 -040070 private final CurrentUserTracker mUserTracker;
Dan Sandler055bb612017-02-08 16:21:49 -080071 private TextView mBatteryPercentView;
72
Jorim Jaggi708f7722014-08-20 17:30:38 +020073 private BatteryController mBatteryController;
Dan Sandler055bb612017-02-08 16:21:49 -080074 private SettingObserver mSettingObserver;
75 private int mTextColor;
76 private int mLevel;
Jason Monkf13413e2017-02-15 15:49:32 -050077 private boolean mForceShowPercent;
Evan Laird54ff81b2018-06-13 20:08:17 -040078 private boolean mShowPercentAvailable;
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040079
Jason Monk9a376bc2017-05-10 09:52:10 -040080 private int mDarkModeBackgroundColor;
81 private int mDarkModeFillColor;
82
83 private int mLightModeBackgroundColor;
84 private int mLightModeFillColor;
Lucas Dupin987f1932017-05-13 21:02:52 -070085 private float mDarkIntensity;
Jason Monka6f1db3a2017-08-30 19:18:00 -040086 private int mUser;
Jason Monk9a376bc2017-05-10 09:52:10 -040087
Rohan Shahcc3d1d82018-03-30 21:24:17 +000088 /**
89 * Whether we should use colors that adapt based on wallpaper/the scrim behind quick settings.
90 */
91 private boolean mUseWallpaperTextColors;
92
93 private int mNonAdaptedForegroundColor;
94 private int mNonAdaptedBackgroundColor;
95
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -040096 public BatteryMeterView(Context context) {
97 this(context, null, 0);
98 }
99
100 public BatteryMeterView(Context context, AttributeSet attrs) {
101 this(context, attrs, 0);
102 }
103
104 public BatteryMeterView(Context context, AttributeSet attrs, int defStyle) {
105 super(context, attrs, defStyle);
106
Dan Sandler055bb612017-02-08 16:21:49 -0800107 setOrientation(LinearLayout.HORIZONTAL);
108 setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
109
John Spurlock29786fc2014-02-04 17:55:47 -0500110 TypedArray atts = context.obtainStyledAttributes(attrs, R.styleable.BatteryMeterView,
111 defStyle, 0);
112 final int frameColor = atts.getColor(R.styleable.BatteryMeterView_frameColor,
Daniel Nishi8af93cb2017-05-15 14:37:20 -0700113 context.getColor(R.color.meter_background_color));
Dan Sandler055bb612017-02-08 16:21:49 -0800114 mDrawable = new BatteryMeterDrawableBase(context, frameColor);
John Spurlock29786fc2014-02-04 17:55:47 -0500115 atts.recycle();
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400116
Dan Sandler055bb612017-02-08 16:21:49 -0800117 mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
Evan Laird54ff81b2018-06-13 20:08:17 -0400118 mShowPercentAvailable = context.getResources().getBoolean(
119 com.android.internal.R.bool.config_battery_percentage_setting_available);
120
Dan Sandler055bb612017-02-08 16:21:49 -0800121
Charles He6a79b0d2017-09-18 09:50:58 +0100122 addOnAttachStateChangeListener(
123 new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
124
Jason Monk3e189872016-01-12 09:10:34 -0500125 mSlotBattery = context.getString(
126 com.android.internal.R.string.status_bar_battery);
Dan Sandler055bb612017-02-08 16:21:49 -0800127 mBatteryIconView = new ImageView(context);
128 mBatteryIconView.setImageDrawable(mDrawable);
129 final MarginLayoutParams mlp = new MarginLayoutParams(
130 getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_width),
131 getResources().getDimensionPixelSize(R.dimen.status_bar_battery_icon_height));
132 mlp.setMargins(0, 0, 0,
133 getResources().getDimensionPixelOffset(R.dimen.battery_margin_bottom));
134 addView(mBatteryIconView, mlp);
135
136 updateShowPercent();
Evan Lairdef160f22018-01-29 14:08:45 -0500137 setColorsFromContext(context);
Jason Monkd866b8a2017-03-29 15:30:44 -0400138 // Init to not dark at all.
139 onDarkChanged(new Rect(), 0, DarkIconDispatcher.DEFAULT_ICON_TINT);
Evan Lairdef160f22018-01-29 14:08:45 -0500140
Jason Monka6f1db3a2017-08-30 19:18:00 -0400141 mUserTracker = new CurrentUserTracker(mContext) {
142 @Override
143 public void onUserSwitched(int newUserId) {
144 mUser = newUserId;
145 getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
146 getContext().getContentResolver().registerContentObserver(
147 Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver,
148 newUserId);
149 }
150 };
Evan Laird39ea8102018-05-18 19:49:07 -0400151
152 setClipChildren(false);
153 setClipToPadding(false);
Dan Sandler055bb612017-02-08 16:21:49 -0800154 }
155
Dan Sandlerdf14c202017-02-21 14:51:11 -0500156 public void setForceShowPercent(boolean show) {
157 mForceShowPercent = show;
Jason Monkf13413e2017-02-15 15:49:32 -0500158 updateShowPercent();
159 }
160
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000161 /**
162 * Sets whether the battery meter view uses the wallpaperTextColor. If we're not using it, we'll
163 * revert back to dark-mode-based/tinted colors.
164 *
165 * @param shouldUseWallpaperTextColor whether we should use wallpaperTextColor for all
166 * components
167 */
168 public void useWallpaperTextColor(boolean shouldUseWallpaperTextColor) {
169 if (shouldUseWallpaperTextColor == mUseWallpaperTextColors) {
170 return;
171 }
172
173 mUseWallpaperTextColors = shouldUseWallpaperTextColor;
174
175 if (mUseWallpaperTextColors) {
176 updateColors(
Jason Changb4e879d2018-04-11 11:17:58 +0800177 Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColor),
178 Utils.getColorAttrDefaultColor(mContext, R.attr.wallpaperTextColorSecondary));
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000179 } else {
180 updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor);
181 }
182 }
183
Evan Lairdef160f22018-01-29 14:08:45 -0500184 public void setColorsFromContext(Context context) {
185 if (context == null) {
186 return;
187 }
188
189 Context dualToneDarkTheme = new ContextThemeWrapper(context,
190 Utils.getThemeAttr(context, R.attr.darkIconTheme));
191 Context dualToneLightTheme = new ContextThemeWrapper(context,
192 Utils.getThemeAttr(context, R.attr.lightIconTheme));
Jason Changb4e879d2018-04-11 11:17:58 +0800193 mDarkModeBackgroundColor = Utils.getColorAttrDefaultColor(dualToneDarkTheme,
194 R.attr.backgroundColor);
195 mDarkModeFillColor = Utils.getColorAttrDefaultColor(dualToneDarkTheme,
196 R.attr.fillColor);
197 mLightModeBackgroundColor = Utils.getColorAttrDefaultColor(dualToneLightTheme,
198 R.attr.backgroundColor);
199 mLightModeFillColor = Utils.getColorAttrDefaultColor(dualToneLightTheme, R.attr.fillColor);
Evan Lairdef160f22018-01-29 14:08:45 -0500200 }
201
Jorim Jaggi0d266892014-07-28 14:49:13 +0200202 @Override
203 public boolean hasOverlappingRendering() {
204 return false;
205 }
206
Jason Monkabe19742015-09-29 09:47:06 -0400207 @Override
Jason Monk3e189872016-01-12 09:10:34 -0500208 public void onTuningChanged(String key, String newValue) {
209 if (StatusBarIconController.ICON_BLACKLIST.equals(key)) {
210 ArraySet<String> icons = StatusBarIconController.getIconBlacklist(newValue);
Jason Monkf8c2f7b2017-09-06 09:22:29 -0400211 boolean hidden = icons.contains(mSlotBattery);
212 Dependency.get(IconLogger.class).onIconVisibility(mSlotBattery, !hidden);
213 setVisibility(hidden ? View.GONE : View.VISIBLE);
Jason Monk3e189872016-01-12 09:10:34 -0500214 }
215 }
216
217 @Override
Jason Monkabe19742015-09-29 09:47:06 -0400218 public void onAttachedToWindow() {
219 super.onAttachedToWindow();
Jason Monk9c7844c2017-01-18 15:21:53 -0500220 mBatteryController = Dependency.get(BatteryController.class);
Jason Monk9c7844c2017-01-18 15:21:53 -0500221 mBatteryController.addCallback(this);
Jason Monka6f1db3a2017-08-30 19:18:00 -0400222 mUser = ActivityManager.getCurrentUser();
Dan Sandler055bb612017-02-08 16:21:49 -0800223 getContext().getContentResolver().registerContentObserver(
Jason Monka6f1db3a2017-08-30 19:18:00 -0400224 Settings.System.getUriFor(SHOW_BATTERY_PERCENT), false, mSettingObserver, mUser);
Dan Sandler055bb612017-02-08 16:21:49 -0800225 updateShowPercent();
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000226 Dependency.get(TunerService.class)
227 .addTunable(this, StatusBarIconController.ICON_BLACKLIST);
Jason Monkaa573e92017-01-27 17:00:29 -0500228 Dependency.get(ConfigurationController.class).addCallback(this);
Jason Monka6f1db3a2017-08-30 19:18:00 -0400229 mUserTracker.startTracking();
Jason Monkabe19742015-09-29 09:47:06 -0400230 }
John Spurlock3c875662013-08-31 15:07:25 -0400231
232 @Override
Jason Monkabe19742015-09-29 09:47:06 -0400233 public void onDetachedFromWindow() {
234 super.onDetachedFromWindow();
Jason Monka6f1db3a2017-08-30 19:18:00 -0400235 mUserTracker.stopTracking();
Jason Monk88529052016-11-04 13:29:58 -0400236 mBatteryController.removeCallback(this);
Dan Sandler055bb612017-02-08 16:21:49 -0800237 getContext().getContentResolver().unregisterContentObserver(mSettingObserver);
Jason Monkde850bb2017-02-01 19:26:30 -0500238 Dependency.get(TunerService.class).removeTunable(this);
Jason Monkaa573e92017-01-27 17:00:29 -0500239 Dependency.get(ConfigurationController.class).removeCallback(this);
John Spurlock3c875662013-08-31 15:07:25 -0400240 }
John Spurlockf40d08f2015-05-29 10:48:22 -0400241
Jason Monkabe19742015-09-29 09:47:06 -0400242 @Override
243 public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
Dan Sandler055bb612017-02-08 16:21:49 -0800244 mDrawable.setBatteryLevel(level);
jackqdyuleiff5bd942017-04-04 10:54:21 -0700245 mDrawable.setCharging(pluggedIn);
Dan Sandler055bb612017-02-08 16:21:49 -0800246 mLevel = level;
247 updatePercentText();
Jason Monkabe19742015-09-29 09:47:06 -0400248 setContentDescription(
Adrian Roos70dcf832016-04-20 15:51:42 -0700249 getContext().getString(charging ? R.string.accessibility_battery_level_charging
250 : R.string.accessibility_battery_level, level));
John Spurlockf40d08f2015-05-29 10:48:22 -0400251 }
252
Jason Monkabe19742015-09-29 09:47:06 -0400253 @Override
Jason Monkc06fbb12016-01-08 14:12:18 -0500254 public void onPowerSaveChanged(boolean isPowerSave) {
Dan Sandler055bb612017-02-08 16:21:49 -0800255 mDrawable.setPowerSave(isPowerSave);
256 }
John Spurlockf40d08f2015-05-29 10:48:22 -0400257
Dan Sandler055bb612017-02-08 16:21:49 -0800258 private TextView loadPercentView() {
259 return (TextView) LayoutInflater.from(getContext())
260 .inflate(R.layout.battery_percentage_view, null);
261 }
262
263 private void updatePercentText() {
264 if (mBatteryPercentView != null) {
265 mBatteryPercentView.setText(
Jason Monk9a376bc2017-05-10 09:52:10 -0400266 NumberFormat.getPercentInstance().format(mLevel / 100f));
Dan Sandler055bb612017-02-08 16:21:49 -0800267 }
268 }
269
270 private void updateShowPercent() {
271 final boolean showing = mBatteryPercentView != null;
Evan Laird54ff81b2018-06-13 20:08:17 -0400272 final boolean systemSetting = 0 != Settings.System
273 .getIntForUser(getContext().getContentResolver(),
274 SHOW_BATTERY_PERCENT, 0, mUser);
275
276 if ((mShowPercentAvailable && systemSetting) || mForceShowPercent) {
Dan Sandler055bb612017-02-08 16:21:49 -0800277 if (!showing) {
278 mBatteryPercentView = loadPercentView();
279 if (mTextColor != 0) mBatteryPercentView.setTextColor(mTextColor);
280 updatePercentText();
281 addView(mBatteryPercentView,
Dan Sandler055bb612017-02-08 16:21:49 -0800282 new ViewGroup.LayoutParams(
283 LayoutParams.WRAP_CONTENT,
284 LayoutParams.MATCH_PARENT));
285 }
286 } else {
287 if (showing) {
288 removeView(mBatteryPercentView);
289 mBatteryPercentView = null;
290 }
291 }
John Spurlockf40d08f2015-05-29 10:48:22 -0400292 }
293
Jason Monkaa573e92017-01-27 17:00:29 -0500294 @Override
295 public void onDensityOrFontScaleChanged() {
296 scaleBatteryMeterViews();
297 }
298
299 /**
300 * Looks up the scale factor for status bar icons and scales the battery view by that amount.
301 */
302 private void scaleBatteryMeterViews() {
303 Resources res = getContext().getResources();
304 TypedValue typedValue = new TypedValue();
305
306 res.getValue(R.dimen.status_bar_icon_scale_factor, typedValue, true);
307 float iconScaleFactor = typedValue.getFloat();
308
309 int batteryHeight = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_height);
310 int batteryWidth = res.getDimensionPixelSize(R.dimen.status_bar_battery_icon_width);
311 int marginBottom = res.getDimensionPixelSize(R.dimen.battery_margin_bottom);
312
313 LinearLayout.LayoutParams scaledLayoutParams = new LinearLayout.LayoutParams(
314 (int) (batteryWidth * iconScaleFactor), (int) (batteryHeight * iconScaleFactor));
Dan Sandler055bb612017-02-08 16:21:49 -0800315 scaledLayoutParams.setMargins(0, 0, 0, marginBottom);
Jason Monkaa573e92017-01-27 17:00:29 -0500316
Dan Sandler055bb612017-02-08 16:21:49 -0800317 mBatteryIconView.setLayoutParams(scaledLayoutParams);
Evan Lairdecc93f22017-06-16 09:57:29 -0400318 FontSizeUtils.updateFontSize(mBatteryPercentView, R.dimen.qs_time_expanded_size);
Jason Monkaa573e92017-01-27 17:00:29 -0500319 }
320
321 @Override
322 public void onDarkChanged(Rect area, float darkIntensity, int tint) {
Lucas Dupin987f1932017-05-13 21:02:52 -0700323 mDarkIntensity = darkIntensity;
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000324
Jason Monk9a376bc2017-05-10 09:52:10 -0400325 float intensity = DarkIconDispatcher.isInArea(area, this) ? darkIntensity : 0;
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000326 mNonAdaptedForegroundColor = getColorForDarkIntensity(
327 intensity, mLightModeFillColor, mDarkModeFillColor);
328 mNonAdaptedBackgroundColor = getColorForDarkIntensity(
329 intensity, mLightModeBackgroundColor,mDarkModeBackgroundColor);
330
331 if (!mUseWallpaperTextColors) {
332 updateColors(mNonAdaptedForegroundColor, mNonAdaptedBackgroundColor);
333 }
Dan Sandler055bb612017-02-08 16:21:49 -0800334 }
335
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000336 private void updateColors(int foregroundColor, int backgroundColor) {
337 mDrawable.setColors(foregroundColor, backgroundColor);
338 mTextColor = foregroundColor;
Dan Sandler055bb612017-02-08 16:21:49 -0800339 if (mBatteryPercentView != null) {
Rohan Shahcc3d1d82018-03-30 21:24:17 +0000340 mBatteryPercentView.setTextColor(foregroundColor);
Dan Sandler055bb612017-02-08 16:21:49 -0800341 }
Jason Monkabe19742015-09-29 09:47:06 -0400342 }
Jason Monk32508852017-01-18 09:17:13 -0500343
Lucas Dupin987f1932017-05-13 21:02:52 -0700344 public void setFillColor(int color) {
345 if (mLightModeFillColor == color) {
346 return;
347 }
348 mLightModeFillColor = color;
349 onDarkChanged(new Rect(), mDarkIntensity, DarkIconDispatcher.DEFAULT_ICON_TINT);
350 }
351
Jason Monk9a376bc2017-05-10 09:52:10 -0400352 private int getColorForDarkIntensity(float darkIntensity, int lightColor, int darkColor) {
353 return (int) ArgbEvaluator.getInstance().evaluate(darkIntensity, lightColor, darkColor);
Jason Monk32508852017-01-18 09:17:13 -0500354 }
Dan Sandler055bb612017-02-08 16:21:49 -0800355
Lucas Dupinc510d412018-06-12 13:08:23 -0700356 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
357 String powerSave = mDrawable == null ? null : mDrawable.getPowerSave() + "";
358 CharSequence percent = mBatteryPercentView == null ? null : mBatteryPercentView.getText();
359 pw.println(" BatteryMeterView:");
360 pw.println(" mDrawable.getPowerSave: " + powerSave);
361 pw.println(" mBatteryPercentView.getText(): " + percent);
362 pw.println(" mTextColor: #" + Integer.toHexString(mTextColor));
363 pw.println(" mLevel: " + mLevel);
364 pw.println(" mForceShowPercent: " + mForceShowPercent);
365 }
366
Dan Sandler055bb612017-02-08 16:21:49 -0800367 private final class SettingObserver extends ContentObserver {
368 public SettingObserver(Handler handler) {
369 super(handler);
370 }
371
372 @Override
373 public void onChange(boolean selfChange, Uri uri) {
374 super.onChange(selfChange, uri);
375 updateShowPercent();
376 }
377 }
Daniel Sandlerdfaf3bd2013-04-12 01:39:02 -0400378}