1/*
2 * Copyright (C) 2015 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 */
16package com.android.systemui.qs.tiles;
17
18import android.content.BroadcastReceiver;
19import android.content.Context;
20import android.content.Intent;
21import android.content.IntentFilter;
22import android.graphics.drawable.Drawable;
23import android.os.Handler;
24import android.os.Looper;
25import android.text.SpannableStringBuilder;
26import android.text.Spanned;
27import android.text.style.RelativeSizeSpan;
28import android.util.TypedValue;
29import android.view.LayoutInflater;
30import android.view.View;
31import android.view.View.OnAttachStateChangeListener;
32import android.view.View.OnClickListener;
33import android.view.ViewGroup;
34import android.widget.Button;
35import android.widget.Checkable;
36import android.widget.ImageView;
37import android.widget.TextView;
38import com.android.internal.logging.MetricsProto.MetricsEvent;
39import com.android.settingslib.BatteryInfo;
40import com.android.settingslib.graph.UsageView;
41import com.android.systemui.BatteryMeterDrawable;
42import com.android.systemui.R;
43import com.android.systemui.qs.QSTile;
44import com.android.systemui.statusbar.policy.BatteryController;
45
46import java.text.NumberFormat;
47
48public class BatteryTile extends QSTile<QSTile.State> implements BatteryController.BatteryStateChangeCallback {
49
50    private final BatteryController mBatteryController;
51    private final BatteryDetail mBatteryDetail = new BatteryDetail();
52
53    private int mLevel;
54    private boolean mPowerSave;
55    private boolean mCharging;
56    private boolean mDetailShown;
57    private boolean mPluggedIn;
58
59    public BatteryTile(Host host) {
60        super(host);
61        mBatteryController = host.getBatteryController();
62    }
63
64    @Override
65    public State newTileState() {
66        return new QSTile.State();
67    }
68
69    @Override
70    public DetailAdapter getDetailAdapter() {
71        return mBatteryDetail;
72    }
73
74    @Override
75    public int getMetricsCategory() {
76        return MetricsEvent.QS_BATTERY_TILE;
77    }
78
79    @Override
80    public void setListening(boolean listening) {
81        if (listening) {
82            mBatteryController.addStateChangedCallback(this);
83        } else {
84            mBatteryController.removeStateChangedCallback(this);
85        }
86    }
87
88    @Override
89    public void setDetailListening(boolean listening) {
90        super.setDetailListening(listening);
91        if (!listening) {
92            mBatteryDetail.mCurrentView = null;
93        }
94    }
95
96    @Override
97    public Intent getLongClickIntent() {
98        return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
99    }
100
101    @Override
102    protected void handleClick() {
103        showDetail(true);
104    }
105
106    @Override
107    public CharSequence getTileLabel() {
108        return mContext.getString(R.string.battery);
109    }
110
111    @Override
112    protected void handleUpdateState(State state, Object arg) {
113        int level = (arg != null) ? (Integer) arg : mLevel;
114        String percentage = NumberFormat.getPercentInstance().format((double) level / 100.0);
115
116        state.icon = new Icon() {
117            @Override
118            public Drawable getDrawable(Context context) {
119                BatteryMeterDrawable drawable =
120                        new BatteryMeterDrawable(context, new Handler(Looper.getMainLooper()),
121                        context.getColor(R.color.batterymeter_frame_color));
122                drawable.onBatteryLevelChanged(mLevel, mPluggedIn, mCharging);
123                drawable.onPowerSaveChanged(mPowerSave);
124                return drawable;
125            }
126
127            @Override
128            public int getPadding() {
129                return mHost.getContext().getResources().getDimensionPixelSize(
130                        R.dimen.qs_battery_padding);
131            }
132        };
133        state.label = percentage;
134        state.contentDescription = mContext.getString(R.string.accessibility_quick_settings_battery,
135                percentage) + "," +
136                (mPowerSave ? mContext.getString(R.string.battery_saver_notification_title)
137                        : mCharging ? mContext.getString(R.string.expanded_header_battery_charging)
138                                : "")
139                + "," + mContext.getString(R.string.accessibility_battery_details);
140        state.minimalAccessibilityClassName = state.expandedAccessibilityClassName
141                = Button.class.getName();
142    }
143
144    @Override
145    public void onBatteryLevelChanged(int level, boolean pluggedIn, boolean charging) {
146        mLevel = level;
147        mPluggedIn = pluggedIn;
148        mCharging = charging;
149        refreshState((Integer) level);
150        if (mDetailShown) {
151            mBatteryDetail.postBindView();
152        }
153    }
154
155    @Override
156    public void onPowerSaveChanged(boolean isPowerSave) {
157        mPowerSave = isPowerSave;
158        refreshState(null);
159        if (mDetailShown) {
160            mBatteryDetail.postBindView();
161        }
162    }
163
164    private final class BatteryDetail implements DetailAdapter, OnClickListener,
165            OnAttachStateChangeListener {
166        private final BatteryMeterDrawable mDrawable = new BatteryMeterDrawable(mHost.getContext(),
167                new Handler(), mHost.getContext().getColor(R.color.batterymeter_frame_color));
168        private View mCurrentView;
169
170        @Override
171        public CharSequence getTitle() {
172            return mContext.getString(R.string.battery_panel_title, mLevel);
173        }
174
175        @Override
176        public Boolean getToggleState() {
177            return null;
178        }
179
180        @Override
181        public View createDetailView(Context context, View convertView, ViewGroup parent) {
182            if (convertView == null) {
183                convertView = LayoutInflater.from(mContext).inflate(R.layout.battery_detail, parent,
184                        false);
185            }
186            mCurrentView = convertView;
187            mCurrentView.addOnAttachStateChangeListener(this);
188            bindView();
189            return convertView;
190        }
191
192        private void postBindView() {
193            if (mCurrentView == null) return;
194            mCurrentView.post(new Runnable() {
195                @Override
196                public void run() {
197                    bindView();
198                }
199            });
200        }
201
202        private void bindView() {
203            if (mCurrentView == null) {
204                return;
205            }
206            mDrawable.onBatteryLevelChanged(100, false, false);
207            mDrawable.onPowerSaveChanged(true);
208            mDrawable.disableShowPercent();
209            ((ImageView) mCurrentView.findViewById(android.R.id.icon)).setImageDrawable(mDrawable);
210            Checkable checkbox = (Checkable) mCurrentView.findViewById(android.R.id.toggle);
211            checkbox.setChecked(mPowerSave);
212            BatteryInfo.getBatteryInfo(mContext, new BatteryInfo.Callback() {
213                @Override
214                public void onBatteryInfoLoaded(BatteryInfo info) {
215                    if (mCurrentView != null) {
216                        bindBatteryInfo(info);
217                    }
218                }
219            });
220            final TextView batterySaverTitle =
221                    (TextView) mCurrentView.findViewById(android.R.id.title);
222            final TextView batterySaverSummary =
223                    (TextView) mCurrentView.findViewById(android.R.id.summary);
224            if (mCharging) {
225                mCurrentView.findViewById(R.id.switch_container).setAlpha(.7f);
226                batterySaverTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14);
227                batterySaverTitle.setText(R.string.battery_detail_charging_summary);
228                mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.GONE);
229                mCurrentView.findViewById(R.id.switch_container).setClickable(false);
230            } else {
231                mCurrentView.findViewById(R.id.switch_container).setAlpha(1);
232                batterySaverTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16);
233                batterySaverTitle.setText(R.string.battery_detail_switch_title);
234                batterySaverSummary.setText(R.string.battery_detail_switch_summary);
235                mCurrentView.findViewById(android.R.id.toggle).setVisibility(View.VISIBLE);
236                mCurrentView.findViewById(R.id.switch_container).setClickable(true);
237                mCurrentView.findViewById(R.id.switch_container).setOnClickListener(this);
238            }
239        }
240
241        private void bindBatteryInfo(BatteryInfo info) {
242            SpannableStringBuilder builder = new SpannableStringBuilder();
243            builder.append(info.batteryPercentString, new RelativeSizeSpan(2.6f),
244                    Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
245            if (info.remainingLabel != null) {
246                if (mContext.getResources().getBoolean(R.bool.quick_settings_wide)) {
247                    builder.append(' ');
248                } else {
249                    builder.append('\n');
250                }
251                builder.append(info.remainingLabel);
252            }
253            ((TextView) mCurrentView.findViewById(R.id.charge_and_estimation)).setText(builder);
254
255            info.bindHistory((UsageView) mCurrentView.findViewById(R.id.battery_usage));
256        }
257
258        @Override
259        public void onClick(View v) {
260            mBatteryController.setPowerSaveMode(!mPowerSave);
261        }
262
263        @Override
264        public Intent getSettingsIntent() {
265            return new Intent(Intent.ACTION_POWER_USAGE_SUMMARY);
266        }
267
268        @Override
269        public void setToggleState(boolean state) {
270            // No toggle state.
271        }
272
273        @Override
274        public int getMetricsCategory() {
275            return MetricsEvent.QS_BATTERY_DETAIL;
276        }
277
278        @Override
279        public void onViewAttachedToWindow(View v) {
280            if (!mDetailShown) {
281                mDetailShown = true;
282                v.getContext().registerReceiver(mReceiver,
283                        new IntentFilter(Intent.ACTION_TIME_TICK));
284            }
285        }
286
287        @Override
288        public void onViewDetachedFromWindow(View v) {
289            if (mDetailShown) {
290                mDetailShown = false;
291                v.getContext().unregisterReceiver(mReceiver);
292            }
293        }
294
295        private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
296            @Override
297            public void onReceive(Context context, Intent intent) {
298                postBindView();
299            }
300        };
301    }
302}
303