/* * Copyright 2013 serso aka se.solovyev * * 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. * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * Contact details * * Email: se.solovyev@gmail.com * Site: http://se.solovyev.org */ package org.solovyev.android.calculator.widget; import android.annotation.TargetApi; import android.app.PendingIntent; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Typeface; import android.os.Build; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.text.SpannableString; import android.text.SpannableStringBuilder; import android.text.SpannedString; import android.text.TextUtils; import android.text.style.StyleSpan; import android.widget.RemoteViews; import org.solovyev.android.Check; import org.solovyev.android.calculator.*; import org.solovyev.android.calculator.Preferences.SimpleTheme; import org.solovyev.android.calculator.buttons.CppButton; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.inject.Inject; import java.util.EnumMap; import static android.content.Intent.ACTION_CONFIGURATION_CHANGED; import static android.os.Build.VERSION_CODES.JELLY_BEAN; import static org.solovyev.android.calculator.App.cast; import static org.solovyev.android.calculator.Broadcaster.*; import static org.solovyev.android.calculator.WidgetReceiver.newButtonClickedIntent; public class CalculatorWidget extends AppWidgetProvider { private static final int WIDGET_CATEGORY_KEYGUARD = 2; private static final String OPTION_APPWIDGET_HOST_CATEGORY = "appWidgetCategory"; @Nonnull private static final Intents intents = new Intents(); @Nullable private static SpannedString cursorString; @Inject Editor editor; @Inject Display display; @Inject Engine engine; public CalculatorWidget() { } @Override public void onEnabled(Context context) { super.onEnabled(context); initCursorString(context); } @Nonnull private SpannedString getCursorString(@Nonnull Context context) { return initCursorString(context); } @Nonnull private SpannedString initCursorString(@Nonnull Context context) { if (cursorString == null) { final SpannableString s = App.colorString("|", ContextCompat.getColor(context, R.color.cpp_widget_cursor)); // this will override any other style span (f.e. italic) s.setSpan(new StyleSpan(Typeface.NORMAL), 0, 1, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE); cursorString = new SpannedString(s); } return cursorString; } @Override public void onUpdate(@Nonnull Context context, @Nonnull AppWidgetManager appWidgetManager, @Nonnull int[] appWidgetIds) { super.onUpdate(context, appWidgetManager, appWidgetIds); updateWidget(context, appWidgetManager, appWidgetIds, false); } public void updateWidget(@Nonnull Context context, boolean partially) { final AppWidgetManager manager = AppWidgetManager.getInstance(context); final int[] widgetIds = manager.getAppWidgetIds(new ComponentName(context, CalculatorWidget.class)); updateWidget(context, manager, widgetIds, partially); } private void updateWidget(@Nonnull Context context, @Nonnull AppWidgetManager manager, @Nonnull int[] widgetIds, boolean partially) { final EditorState editorState = editor.getState(); final DisplayState displayState = display.getState(); final Resources resources = context.getResources(); final SimpleTheme theme = App.getWidgetTheme().resolveThemeFor(App.getTheme()); for (int widgetId : widgetIds) { final RemoteViews views = new RemoteViews(context.getPackageName(), getLayout(manager, widgetId, resources, theme)); if (!partially || Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { for (CppButton button : CppButton.values()) { final PendingIntent intent = intents.get(context, button); if (intent != null) { final int buttonId; if (button == CppButton.settings_widget) { // overriding default settings button behavior buttonId = CppButton.settings.id; } else { buttonId = button.id; } views.setOnClickPendingIntent(buttonId, intent); } } } updateEditorState(context, views, editorState, theme); updateDisplayState(context, views, displayState, theme); views.setTextViewText(R.id.cpp_button_multiplication, engine.getMultiplicationSign()); if (partially && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { manager.partiallyUpdateAppWidget(widgetId, views); } else { manager.updateAppWidget(widgetId, views); } } } private int getLayout(@Nonnull AppWidgetManager manager, int widgetId, @Nonnull Resources resources, @Nonnull SimpleTheme theme) { if (Build.VERSION.SDK_INT >= JELLY_BEAN) { return getLayoutJellyBean(manager, widgetId, resources, theme); } return getDefaultLayout(theme); } private int getDefaultLayout(@Nonnull SimpleTheme theme) { return theme.getWidgetLayout(App.getTheme()); } @TargetApi(JELLY_BEAN) private int getLayoutJellyBean(@Nonnull AppWidgetManager manager, int widgetId, Resources resources, @Nonnull SimpleTheme theme) { final Bundle options = manager.getAppWidgetOptions(widgetId); if (options == null) { return getDefaultLayout(theme); } final int category = options.getInt(OPTION_APPWIDGET_HOST_CATEGORY, -1); if (category == -1) { return getDefaultLayout(theme); } final boolean keyguard = category == WIDGET_CATEGORY_KEYGUARD; if (!keyguard) { return getDefaultLayout(theme); } final int widgetMinHeight = App.toPixels(resources.getDisplayMetrics(), options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT, 0)); final int lockScreenMinHeight = resources.getDimensionPixelSize(R.dimen.min_expanded_height_lock_screen); final boolean expanded = widgetMinHeight >= lockScreenMinHeight; if (expanded) { return R.layout.widget_layout_lockscreen; } else { return R.layout.widget_layout_lockscreen_collapsed; } } @Override public void onReceive(@Nonnull Context context, @Nonnull Intent intent) { cast(context).getComponent().inject(this); super.onReceive(context, intent); final String action = intent.getAction(); if (TextUtils.isEmpty(action)) { return; } switch (action) { case ACTION_EDITOR_STATE_CHANGED: case ACTION_DISPLAY_STATE_CHANGED: updateWidget(context, true); break; case ACTION_CONFIGURATION_CHANGED: case ACTION_THEME_CHANGED: case ACTION_INIT: case AppWidgetManager.ACTION_APPWIDGET_UPDATE: case AppWidgetManager.ACTION_APPWIDGET_OPTIONS_CHANGED: updateWidget(context, false); break; } } private void updateDisplayState(@Nonnull Context context, @Nonnull RemoteViews views, @Nonnull DisplayState displayState, @Nonnull SimpleTheme theme) { final boolean error = !displayState.valid; if (!error) { views.setTextViewText(R.id.calculator_display, displayState.text); } views.setTextColor(R.id.calculator_display, ContextCompat.getColor(context, theme.getDisplayTextColor(error))); } private void updateEditorState(@Nonnull Context context, @Nonnull RemoteViews views, @Nonnull EditorState state, @Nonnull SimpleTheme theme) { final boolean unspan = App.getTheme().light != theme.light; final CharSequence text = state.text; final int selection = state.selection; if (selection < 0 || selection > text.length()) { views.setTextViewText(R.id.calculator_editor, unspan ? App.unspan(text) : text); return; } final SpannableStringBuilder result; // inject cursor if (unspan) { final CharSequence beforeCursor = text.subSequence(0, selection); final CharSequence afterCursor = text.subSequence(selection, text.length()); result = new SpannableStringBuilder(); result.append(App.unspan(beforeCursor)); result.append(getCursorString(context)); result.append(App.unspan(afterCursor)); } else { result = new SpannableStringBuilder(text); result.insert(selection, getCursorString(context)); } views.setTextViewText(R.id.calculator_editor, result); } private static class Intents { @Nonnull private final EnumMap<CppButton, PendingIntent> map = new EnumMap<>(CppButton.class); @Nullable PendingIntent get(@Nonnull Context context, @Nonnull CppButton button) { Check.isMainThread(); PendingIntent intent = map.get(button); if (intent != null) { return intent; } intent = PendingIntent.getBroadcast(context, button.id, newButtonClickedIntent(context, button), PendingIntent.FLAG_UPDATE_CURRENT); if (intent == null) { return null; } map.put(button, intent); return intent; } } }