/*
* Copyright 2013 Google Inc.
*
* 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.google.android.apps.dashclock.render;
import com.google.android.apps.dashclock.ExtensionManager;
import com.google.android.apps.dashclock.LogUtils;
import com.google.android.apps.dashclock.Utils;
import com.google.android.apps.dashclock.WidgetClickProxyActivity;
import com.google.android.apps.dashclock.configuration.AppChooserPreference;
import com.google.android.apps.dashclock.configuration.AppearanceConfig;
import com.google.android.apps.dashclock.configuration.ConfigurationActivity;
import net.nurik.roman.dashclock.BuildConfig;
import net.nurik.roman.dashclock.R;
import android.appwidget.AppWidgetManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.drawable.BitmapDrawable;
import android.preference.PreferenceManager;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import java.util.List;
import java.util.Locale;
import static com.google.android.apps.dashclock.ExtensionManager.ExtensionWithData;
/**
* Abstract helper class in charge of core rendering logic for DashClock widgets.
*/
public abstract class DashClockRenderer {
private static final String TAG = LogUtils.makeLogTag(DashClockRenderer.class);
private static final int MAX_COLLAPSED_EXTENSIONS = 3;
private static final int MIN_NORMAL_FONTSIZE_WIDTH_DP = 300;
public static final String PREF_CLOCK_SHORTCUT = "pref_clock_shortcut";
protected Context mContext;
protected ExtensionManager mExtensionManager;
protected Options mOptions;
protected DashClockRenderer(Context context) {
mContext = context;
mExtensionManager = ExtensionManager.getInstance(context);
}
public void setOptions(Options options) {
mOptions = options;
}
public Object renderWidget(Object container) {
ViewBuilder vb = onCreateViewBuilder();
Resources res = mContext.getResources();
// Load data from extensions
List<ExtensionWithData> extensions = mExtensionManager.getInternalActiveExtensionsWithData();
int activeExtensions = extensions.size();
// Determine if we're on a tablet or not (lock screen widgets can't be collapsed on
// tablets).
boolean isTablet = res.getConfiguration().smallestScreenWidthDp >= 600;
int shadeColor = AppearanceConfig.getBackgroundColor(mContext, mOptions.target);
boolean aggressiveCentering = AppearanceConfig.isAggressiveCenteringEnabled(mContext);
int minExpandedHeight = res.getDimensionPixelSize(
mOptions.target == Options.TARGET_LOCK_SCREEN
? R.dimen.min_expanded_height_lock_screen
: R.dimen.min_expanded_height);
boolean isExpanded = (mOptions.minHeightDp
>= minExpandedHeight / res.getDisplayMetrics().density);
// Step 1. Load the root layout
// TODO: merge
vb.loadRootLayout(container, isExpanded
? (aggressiveCentering
? R.layout.widget_main_expanded_forced_center
: R.layout.widget_main_expanded)
: (aggressiveCentering
? (mOptions.target == Options.TARGET_LOCK_SCREEN
? R.layout.widget_main_collapsed_forced_center_lockscreen
: R.layout.widget_main_collapsed_forced_center)
: R.layout.widget_main_collapsed));
// Step 2. Configure the shade, if it should exist
vb.setViewBackgroundColor(R.id.shade, shadeColor);
vb.setViewVisibility(R.id.shade, shadeColor == 0 ? View.GONE : View.VISIBLE);
// Step 3. Draw the basic clock face
boolean hideSettings;
boolean hideClock =
(mOptions.target == Options.TARGET_HOME_SCREEN
&& AppearanceConfig.isClockHiddenOnHomeScreen(mContext))
|| (mOptions.target == Options.TARGET_LOCK_SCREEN
&& AppearanceConfig.isClockHiddenOnLockScreen(mContext));
vb.setViewVisibility(R.id.clock_target, hideClock ? View.GONE : View.VISIBLE);
if (hideClock) {
hideSettings = true;
} else {
renderClockFace(vb, mOptions.foregroundColor);
hideSettings = AppearanceConfig.isSettingsButtonHidden(mContext);
}
// Step 4. Align the clock face and settings button (if shown)
boolean isPortrait = res.getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT;
if (aggressiveCentering) {
// Forced/aggressive centering rules
vb.setViewVisibility(R.id.settings_button_center_displacement,
hideSettings ? View.GONE : View.VISIBLE);
vb.setViewPadding(R.id.clock_row, 0, 0, 0, 0);
vb.setLinearLayoutGravity(R.id.clock_target, Gravity.CENTER_HORIZONTAL);
} else {
// Normal centering rules
boolean forceCentered = isTablet && isPortrait
&& mOptions.target != Options.TARGET_HOME_SCREEN;
int clockInnerGravity = Gravity.CENTER_HORIZONTAL;
if (activeExtensions > 0 && !forceCentered) {
// Extensions are visible, don't center clock
if (mOptions.target == Options.TARGET_LOCK_SCREEN) {
// lock screen doesn't look at expanded state; the UI should
// not jitter across expanded/collapsed states for lock screen
clockInnerGravity = isTablet ? Gravity.LEFT : Gravity.RIGHT;
} else {
// home screen
clockInnerGravity = (isExpanded && isTablet) ? Gravity.LEFT : Gravity.RIGHT;
}
}
vb.setLinearLayoutGravity(R.id.clock_target, clockInnerGravity);
boolean clockCentered = activeExtensions == 0 || forceCentered; // left otherwise
vb.setLinearLayoutGravity(R.id.clock_row,
clockCentered ? Gravity.CENTER_HORIZONTAL : Gravity.LEFT);
vb.setViewVisibility(R.id.settings_button_center_displacement,
hideSettings
? View.GONE
: (clockCentered ? View.INVISIBLE : View.GONE));
int clockLeftMargin = res.getDimensionPixelSize(R.dimen.clock_left_margin);
if (!isExpanded && mOptions.target == Options.TARGET_HOME_SCREEN) {
clockLeftMargin = 0;
}
vb.setViewPadding(R.id.clock_row, clockCentered ? 0 : clockLeftMargin,
0, 0, 0);
}
// Settings button
if (isExpanded) {
vb.setViewVisibility(R.id.settings_button, hideSettings ? View.GONE : View.VISIBLE);
vb.setViewClickIntent(R.id.settings_button,
new Intent(mContext, ConfigurationActivity.class)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS));
vb.setImageViewBitmap(R.id.settings_button,
Utils.recolorBitmap(
(BitmapDrawable) res.getDrawable(R.drawable.ic_widget_action_settings),
mOptions.foregroundColor));
}
// Step 6. Render the extensions (collapsed or expanded)
if (isExpanded) {
// Expanded style
final Intent onClickTemplateIntent = WidgetClickProxyActivity.getTemplate(mContext);
builderSetExpandedExtensionsAdapter(vb, R.id.expanded_extensions, false,
onClickTemplateIntent);
} else {
// Collapsed style
vb.setViewVisibility(R.id.collapsed_extensions_container,
activeExtensions > 0 ? View.VISIBLE : View.GONE);
vb.removeAllViews(R.id.collapsed_extensions_container);
// Disabled because list view can't wrap content horizontally, which breaks centering.
// if (mOptions.target != Options.TARGET_LOCK_SCREEN) {
// // On anything but the lock screen, support vertical scrolling in collapsed mode
// vb.addView(R.id.collapsed_extensions_container,
// vb.inflateChildLayout(R.layout.widget_include_collapsed_list,
// R.id.collapsed_extensions_container));
//
// final Intent onClickTemplateIntent = WidgetClickProxyActivity.getTemplate(mContext);
// builderSetExpandedExtensionsAdapter(vb, R.id.expanded_extensions, true,
// onClickTemplateIntent);
//
// } else {
// On the lock screen, no vertical scrolling.
boolean ellipsisVisible = false;
int slotIndex = 0;
for (ExtensionWithData ewd : extensions) {
if (!ewd.latestData.visible()) {
continue;
}
if (slotIndex >= MAX_COLLAPSED_EXTENSIONS) {
ellipsisVisible = true;
break;
}
vb.addView(R.id.collapsed_extensions_container,
renderCollapsedExtension(null, null, false, ewd));
++slotIndex;
}
if (ellipsisVisible) {
vb.addView(R.id.collapsed_extensions_container,
vb.inflateChildLayout(
R.layout.widget_include_collapsed_ellipsis,
R.id.collapsed_extensions_container));
vb.setImageViewBitmap(R.id.collapsed_extension_ellipsis,
Utils.recolorBitmap((BitmapDrawable)
res.getDrawable(R.drawable.collapsed_extension_ellipsis),
mOptions.foregroundColor));
}
// }
}
return vb.getRoot();
}
public void renderClockFace(ViewBuilder vb, int foregroundColor) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
vb.removeAllViews(R.id.time_container);
vb.addView(R.id.time_container,
vb.inflateChildLayout(
AppearanceConfig.getCurrentTimeLayout(mContext, foregroundColor),
R.id.time_container));
vb.removeAllViews(R.id.date_container);
vb.addView(R.id.date_container,
vb.inflateChildLayout(
AppearanceConfig.getCurrentDateLayout(mContext),
R.id.date_container));
if (BuildConfig.DEBUG) {
if (sp.getBoolean("demomode", false)) {
vb.setTextClockFormat(R.id.large_time_component_1, "10:08");
vb.setTextClockFormat(R.id.date_component_1, "FRI, OCT 05");
}
}
if (mOptions.minWidthDp < MIN_NORMAL_FONTSIZE_WIDTH_DP) {
Resources res = mContext.getResources();
int miniTextSizeLargePx = res.getDimensionPixelSize(R.dimen.mini_clock_text_size_large);
int miniTextSizeSmallPx = res.getDimensionPixelSize(R.dimen.mini_clock_text_size_small);
int miniDateTextSizePx = res.getDimensionPixelSize(R.dimen.mini_clock_date_text_size);
for (int id : LARGE_TIME_COMPONENT_IDS) {
vb.setTextViewTextSize(id, TypedValue.COMPLEX_UNIT_PX, miniTextSizeLargePx);
}
for (int id : SMALL_TIME_COMPONENT_IDS) {
vb.setTextViewTextSize(id, TypedValue.COMPLEX_UNIT_PX, miniTextSizeSmallPx);
}
for (int id : DATE_COMPONENT_IDS) {
vb.setTextViewTextSize(id, TypedValue.COMPLEX_UNIT_PX, miniDateTextSizePx);
}
int miniDatePaddingPx = res.getDimensionPixelSize(R.dimen.mini_clock_date_top_padding);
vb.setViewPadding(R.id.date_container, 0, miniDatePaddingPx, 0, 0);
}
for (int id : LARGE_TIME_COMPONENT_IDS) {
vb.setTextViewColor(id, mOptions.foregroundColor);
}
for (int id : SMALL_TIME_COMPONENT_IDS) {
vb.setTextViewColor(id, mOptions.foregroundColor);
}
for (int id : DATE_COMPONENT_IDS) {
vb.setTextViewColor(id, mOptions.foregroundColor);
}
Intent clockIntent = AppChooserPreference.getIntentValue(
sp.getString(PREF_CLOCK_SHORTCUT, null),
Utils.getDefaultClockIntent(mContext));
if (clockIntent != null) {
vb.setViewClickIntent(R.id.clock_target, clockIntent);
}
}
public Object renderCollapsedExtension(Object container, Object convertRoot, boolean inList,
ExtensionWithData ewd) {
ViewBuilder vb = onCreateViewBuilder();
if (convertRoot != null) {
vb.useRoot(convertRoot);
} else {
vb.loadRootLayout(container, inList
? R.layout.widget_include_collapsed_extension
: R.layout.widget_include_collapsed_extension_interactive);
}
Resources res = mContext.getResources();
int extensionCollapsedTextSizeSingleLine = res
.getDimensionPixelSize(R.dimen.extension_collapsed_text_size_single_line);
int extensionCollapsedTextSizeTwoLine = res
.getDimensionPixelSize(R.dimen.extension_collapsed_text_size_two_line);
String status = ewd.latestData.status();
if (TextUtils.isEmpty(status)) {
status = "";
}
if (status.indexOf("\n") > 0) {
vb.setTextViewSingleLine(R.id.collapsed_extension_text, false);
vb.setTextViewMaxLines(R.id.collapsed_extension_text, 2);
vb.setTextViewTextSize(R.id.collapsed_extension_text,
TypedValue.COMPLEX_UNIT_PX,
extensionCollapsedTextSizeTwoLine);
} else {
vb.setTextViewSingleLine(R.id.collapsed_extension_text, true);
vb.setTextViewMaxLines(R.id.collapsed_extension_text, 1);
vb.setTextViewTextSize(R.id.collapsed_extension_text,
TypedValue.COMPLEX_UNIT_PX,
extensionCollapsedTextSizeSingleLine);
}
vb.setTextViewText(R.id.collapsed_extension_text, status.toUpperCase(Locale.getDefault()));
vb.setTextViewColor(R.id.collapsed_extension_text, mOptions.foregroundColor);
String statusContentDescription = ewd.latestData.contentDescription();
if (TextUtils.isEmpty(statusContentDescription)) {
StringBuilder builder = new StringBuilder();
String expandedTitle = Utils.expandedTitleOrStatus(ewd.latestData);
if (!TextUtils.isEmpty(expandedTitle)) {
builder.append(expandedTitle);
}
String expandedBody = ewd.latestData.expandedBody();
if (!TextUtils.isEmpty(expandedBody)) {
builder.append(" ").append(expandedBody);
}
statusContentDescription = builder.toString();
}
vb.setViewContentDescription(R.id.collapsed_extension_text, statusContentDescription);
vb.setImageViewBitmap(R.id.collapsed_extension_icon,
Utils.loadExtensionIcon(mContext, ewd.listing.componentName(),
ewd.latestData.icon(), ewd.latestData.iconUri(), mOptions.foregroundColor));
vb.setViewContentDescription(R.id.collapsed_extension_icon, ewd.listing.title());
Intent clickIntent = ewd.latestData.clickIntent();
if (clickIntent != null) {
if (inList) {
vb.setViewClickFillInIntent(R.id.collapsed_extension_target,
WidgetClickProxyActivity.getFillIntent(clickIntent,
ewd.listing.componentName()));
} else {
vb.setViewClickIntent(R.id.collapsed_extension_target,
WidgetClickProxyActivity.wrap(mContext, clickIntent,
ewd.listing.componentName()));
}
}
return vb.getRoot();
}
public Object renderExpandedExtension(Object container, Object convertRoot, boolean inList,
ExtensionWithData ewd) {
ViewBuilder vb = onCreateViewBuilder();
if (convertRoot != null) {
vb.useRoot(convertRoot);
} else {
vb.loadRootLayout(container, R.layout.widget_list_item_expanded_extension);
}
if (ewd == null || ewd.latestData == null) {
vb.setTextViewText(R.id.text1, mContext.getResources()
.getText(R.string.status_none));
vb.setViewVisibility(R.id.text2, View.GONE);
return vb.getRoot();
}
vb.setTextViewText(R.id.text1, Utils.expandedTitleOrStatus(ewd.latestData));
vb.setTextViewColor(R.id.text1, mOptions.foregroundColor);
String expandedBody = ewd.latestData.expandedBody();
vb.setViewVisibility(R.id.text2, TextUtils.isEmpty(expandedBody)
? View.GONE : View.VISIBLE);
vb.setTextViewText(R.id.text2, ewd.latestData.expandedBody());
vb.setTextViewColor(R.id.text2, mOptions.foregroundColor);
vb.setImageViewBitmap(R.id.icon,
Utils.loadExtensionIcon(mContext, ewd.listing.componentName(),
ewd.latestData.icon(), ewd.latestData.iconUri(), mOptions.foregroundColor));
String contentDescription = ewd.latestData.contentDescription();
if (TextUtils.isEmpty(contentDescription)) {
// No specific content description provided. Just set the minimal extra content
// description for the icon.
vb.setViewContentDescription(R.id.icon, ewd.listing.title());
} else {
// Content description for the entire row provided. Use it!
vb.setViewContentDescription(R.id.list_item,
ewd.listing.title() + ". " + contentDescription);
vb.setViewContentDescription(R.id.text1, "."); // silence title
vb.setViewContentDescription(R.id.text2, "."); // silence body
}
Intent clickIntent = ewd.latestData.clickIntent();
if (clickIntent != null) {
if (inList) {
vb.setViewClickFillInIntent(R.id.list_item,
WidgetClickProxyActivity.getFillIntent(clickIntent,
ewd.listing.componentName()));
} else {
vb.setViewClickIntent(R.id.list_item,
WidgetClickProxyActivity.wrap(mContext, clickIntent,
ewd.listing.componentName()));
}
}
return vb.getRoot();
}
protected abstract ViewBuilder onCreateViewBuilder();
protected abstract void builderSetExpandedExtensionsAdapter(ViewBuilder builder,
int viewId, boolean mini, Intent onClickTemplateIntent);
public static class Options {
public static final int TARGET_HOME_SCREEN = 0;
public static final int TARGET_LOCK_SCREEN = 1;
public static final int TARGET_DAYDREAM = 2;
public int target;
public int minWidthDp;
public int minHeightDp;
public int foregroundColor = AppearanceConfig.DEFAULT_WIDGET_FOREGROUND_COLOR;
// Only used by WidgetRenderer
public int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; // optional
// Only used by SimpleRenderer
public boolean newTaskOnClick;
public OnClickListener onClickListener;
public Intent clickIntentTemplate;
}
public static interface OnClickListener {
void onClick();
}
private static int[] LARGE_TIME_COMPONENT_IDS = {
R.id.large_time_component_1,
R.id.large_time_component_2,
R.id.large_time_component_3,
};
private static int[] SMALL_TIME_COMPONENT_IDS = {
R.id.small_time_component_1,
R.id.small_time_component_2,
R.id.small_time_component_3,
};
private static int[] DATE_COMPONENT_IDS = {
R.id.date_component_1,
R.id.date_component_2,
R.id.date_component_3,
};
}