/*
* Copyright 2015. Appsi Mobile
*
* 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.appsimobile.appsii.module.home;
import android.Manifest;
import android.appwidget.AppWidgetHostView;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.bluetooth.BluetoothAdapter;
import android.content.BroadcastReceiver;
import android.content.ContentProviderOperation;
import android.content.ContentUris;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.OperationApplicationException;
import android.content.res.TypedArray;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Message;
import android.os.RemoteException;
import android.provider.ContactsContract;
import android.provider.Settings;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.util.SimpleArrayMap;
import android.support.v7.graphics.Palette;
import android.support.v7.widget.CardView;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.util.Log;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Advanceable;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.TextView;
import com.appsimobile.appsii.AnalyticsManager;
import com.appsimobile.appsii.BitmapUtils;
import com.appsimobile.appsii.DrawableCompat;
import com.appsimobile.appsii.DrawableStartTintPainter;
import com.appsimobile.appsii.PermissionDeniedException;
import com.appsimobile.appsii.PopupMenuHelper;
import com.appsimobile.appsii.R;
import com.appsimobile.appsii.annotation.VisibleForTesting;
import com.appsimobile.appsii.appwidget.AppWidgetUtils;
import com.appsimobile.appsii.appwidget.AppsiiAppWidgetHost;
import com.appsimobile.appsii.dagger.AppInjector;
import com.appsimobile.appsii.module.home.appwidget.WidgetChooserActivity;
import com.appsimobile.appsii.module.home.config.HomeItemConfiguration;
import com.appsimobile.appsii.module.home.config.HomeItemConfigurationHelper;
import com.appsimobile.appsii.module.home.provider.HomeContract;
import com.appsimobile.appsii.module.weather.WeatherUtils;
import com.appsimobile.appsii.module.weather.loader.WeatherData;
import com.appsimobile.appsii.permissions.PermissionUtils;
import com.appsimobile.paintjob.PaintJob;
import com.appsimobile.paintjob.ViewPainters;
import com.crashlytics.android.Crashlytics;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;
import java.util.TimeZone;
import javax.inject.Inject;
/**
* The adapter used on the Home-Page and in the HomeEditorActivity.
* Created by nick on 20/01/15.
*/
public class HomeAdapter extends RecyclerView.Adapter<AbsHomeViewHolder> {
static final String PERMISSION_ERROR_LOCATION_WEATHER = "_location_weather_";
static final String PERMISSION_ERROR_CONTACTS_PROFILE = "_contact_profile_image_";
/**
* A place to support very basic caching of the host-views. The cache is automatically
* cleared when the host stops listening.
*/
static final SparseArray<AppWidgetHostView> sAppWidgetViewCache = new SparseArray<>();
/**
* The list of home items in the adapter.
*/
final ArrayList<HomeItem> mHomeItems = new ArrayList<>(24);
/**
* A cached layout-inflater to prevent having to get it all the time
*/
final LayoutInflater mLayoutInflater;
/**
* The context to which we are bound
*/
final Context mContext;
/**
* The factory to create the view-wrappers. Can be overridden to create
* a custom view-wrapper.
*/
final ViewWrapperFactory mViewWrapperFactory;
/**
* The internal lookup for the home-spans
*/
final HomeSpanSizeLookup mHomeSpanSizeLookup;
/**
* The id of the home-page we are bound to (or editing for)
*/
final long mHomeId;
/**
* A helper that auto-advances the views that need to be auto-advanced
*/
final AutoAdvanceHelper mAutoAdvanceHelper = new AutoAdvanceHelper();
/**
* True in case app widgets must be enabled. false otherwise. Setting this
* to false, will use a different view-holder that only loads the preview
* image instead of the normal widget view.
*/
final boolean mAppWidgetsEnabled;
/**
* The base height of the widgets. This is set to 72dp in the constructor
*/
final int mBaseHeight;
/**
* The height to increase the widget height with, for each step.
*/
final int mHeightPerStep;
/**
* The app-widget-host. Can be null in case mAppWidgetsEnabled is false.
*/
@Inject
AppsiiAppWidgetHost mAppWidgetHost;
/**
* The app-widget manager. Used by widget view-holders to load the
* AppWidgetInfo for the bound app-widget. In the disable variant it
* is used to load the image.
*/
@Inject
AppWidgetManager mAppWidgetManager;
@Inject
HomeItemConfiguration mHomeItemConfiguration;
@Inject
PermissionUtils mPermissionUtils;
@Inject
WeatherUtils mWeatherUtils;
@Inject
AppWidgetUtils mAppWidgetUtils;
long mUnsetId = -1L;
/**
* True if the home-adapter is started. This specifically impacts the widgets
* which are started and stopped when this state changes.
*/
boolean mStarted;
PermissionErrorListener mPermissionErrorListener;
@Inject
AnalyticsManager mAnalyticsManager;
public HomeAdapter(Context context, long homeId) {
this(context, new DefaultViewWrapperFactory(), homeId, true);
}
public HomeAdapter(Context context, ViewWrapperFactory viewWrapperFactory, long homeId,
boolean appWidgetsEnabled) {
mContext = context;
mHomeId = homeId;
mAppWidgetsEnabled = appWidgetsEnabled;
mViewWrapperFactory = viewWrapperFactory;
mBaseHeight = (int) (context.getResources().getDisplayMetrics().density * 72);
mHeightPerStep = (int) (context.getResources().getDisplayMetrics().density * 24);
mLayoutInflater = LayoutInflater.from(context);
mHomeSpanSizeLookup = new HomeSpanSizeLookup(context);
setHasStableIds(true);
AppInjector.inject(this);
}
long nextUnsetId() {
return mUnsetId--;
}
public HomeItem getItemAt(int position) {
return mHomeItems.get(position);
}
public void getItemsInRow(long rowId, List<HomeItem> homeItems) {
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowId == rowId) {
homeItems.add(homeItem);
}
}
}
@Override
public AbsHomeViewHolder onCreateViewHolder(ViewGroup parent, int encodedViewType) {
int viewType = encodedViewType & 0x0000FFFF;
int rowHeight = (encodedViewType & 0xFFFF0000) >> 16;
int heightPx = heightForRowHeight(rowHeight);
switch (viewType) {
case HomeContract.Cells.DISPLAY_TYPE_UNSET:
return createEmptyViewHolder(heightPx);
case HomeContract.Cells.DISPLAY_TYPE_WEATHER_TEMP:
return createPlainTemperatureViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_WEATHER_TEMP_WALLPAPER:
return createWallpaperTemperatureViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_WEATHER_WIND_WALLPAPER:
// return createWallpaperWindViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_WEATHER_WIND:
return createPlainWindViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_WEATHER_SUNRISE_WALLPAPER:
// return createWallpaperSunViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_WEATHER_SUNRISE:
return createPlainSunViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_CLOCK:
return createClockViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_BLUETOOTH_TOGGLE:
return createBluetoothViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_INTENT:
return createIntentTypeViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_PROFILE_IMAGE:
return createProfileImageViewHolder(parent, heightPx);
case HomeContract.Cells.DISPLAY_TYPE_APP_WIDGET:
// for widgets, when they are not enabled, use a disabled
// version of the viewholder.
if (mAppWidgetsEnabled) {
return createAppWidgetViewHolder(parent, heightPx);
} else {
return createDisabledAppWidgetViewHolder(parent, heightPx);
}
}
return createDummyViewHolder(heightPx);
}
int heightForRowHeight(int rowHeight) {
return mBaseHeight + (mHeightPerStep * rowHeight);
}
private AbsHomeViewHolder createEmptyViewHolder(int height) {
View result = new View(mContext);
HomeViewWrapper wrapped = wrapView(mContext, result, height);
return new EmptyViewViewHolder(wrapped);
}
private AbsHomeViewHolder createPlainTemperatureViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_weather, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new WeatherTemperaturePlainViewHolder(wrapped);
}
private AbsHomeViewHolder createWallpaperTemperatureViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_weather_wp, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new WeatherTemperatureWallpaperViewHolder(wrapped);
}
private AbsHomeViewHolder createPlainWindViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_weather_wind, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new PlainWeatherWindViewHolder(wrapped);
}
private AbsHomeViewHolder createPlainSunViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_weather_sun, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new PlainWeatherSunViewHolder(wrapped);
}
private AbsHomeViewHolder createClockViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_analog_clock, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new ClockViewHolder(wrapped, mHomeItemConfiguration);
}
private AbsHomeViewHolder createBluetoothViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_bluetooth_toggle, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new BluetoothViewHolder(wrapped, mHomeItemConfiguration);
}
private AbsHomeViewHolder createIntentTypeViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_app, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new IntentViewHolder(wrapped);
}
private AbsHomeViewHolder createProfileImageViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_image, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new ProfileImageViewHolder(wrapped);
}
private AbsHomeViewHolder createAppWidgetViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_appwidget, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
AppWidgetViewHolder holder =
new AppWidgetViewHolder(wrapped, mAppWidgetHost, mAppWidgetManager,
mAutoAdvanceHelper);
holder.postCreate();
return holder;
}
private AbsHomeViewHolder createDisabledAppWidgetViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_appwidget_disabled, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new DisabledAppWidgetViewHolder(wrapped, mAppWidgetManager, mAppWidgetUtils);
}
private AbsHomeViewHolder createDummyViewHolder(int height) {
TextView result = new TextView(mContext);
HomeViewWrapper wrapped = wrapView(mContext, result, height);
return new SimpleTextViewViewHolder(wrapped);
}
HomeViewWrapper wrapView(Context context, View child, int heightPx) {
return mViewWrapperFactory.wrapView(context, child, heightPx);
}
@Override
public void onBindViewHolder(AbsHomeViewHolder absHomeViewHolder, int i) {
HomeItem item = mHomeItems.get(i);
int heightPx = heightForRowHeight(item.mRowHeight);
absHomeViewHolder.bind(item, heightPx);
}
@Override
public int getItemViewType(int position) {
HomeItem homeItem = mHomeItems.get(position);
return (homeItem.mDisplayType & 0x0000FFFF) | (homeItem.mRowHeight << 16);
}
@Override
public long getItemId(int position) {
if (position < 0) return RecyclerView.NO_ID;
if (position >= mHomeItems.size()) return RecyclerView.NO_ID;
return mHomeItems.get(position).mId;
}
@Override
public int getItemCount() {
return mHomeItems.size();
}
@Override
public void onViewRecycled(AbsHomeViewHolder holder) {
super.onViewRecycled(holder);
if (holder instanceof AbsWeatherWindViewHolder) {
((AbsWeatherWindViewHolder) holder).onRecycled();
}
}
private AbsHomeViewHolder createWallpaperWindViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_weather_wind, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new WallpaperWeatherWindViewHolder(wrapped);
}
private AbsHomeViewHolder createWallpaperSunViewHolder(ViewGroup parent, int height) {
View view = mLayoutInflater.inflate(R.layout.home_item_weather_sun_wp, parent, false);
HomeViewWrapper wrapped = wrapView(mContext, view, height);
return new WallpaperWeatherSunViewHolder(wrapped);
}
public void setHomeItems(List<HomeItem> data) {
mHomeItems.clear();
int N = data.size();
for (int i = 0; i < N; i++) {
HomeItem item = data.get(i);
if (item.mPageId == mHomeId) {
mHomeItems.add(item);
}
}
try {
mHomeSpanSizeLookup.setup(mHomeItems);
} catch (InconsistentRowsException e) {
fixHomeItemRows(mHomeItems);
}
notifyDataSetChanged();
}
private void fixHomeItemRows(List<HomeItem> homeItems) {
int lastRowPosition = -1;
int newRowIdx = 0;
int N = homeItems.size();
ArrayList<ContentProviderOperation> ops = new ArrayList<>(8);
boolean done;
for (int i = 0; i < N; i++) {
HomeItem item = homeItems.get(i);
done = false;
if (lastRowPosition == -1 || lastRowPosition != item.mRowPosition) {
lastRowPosition = item.mRowPosition;
Uri uri = ContentUris.withAppendedId(
HomeContract.Rows.CONTENT_URI, item.mRowId);
ops.add(ContentProviderOperation.newUpdate(uri).
withValue(HomeContract.Rows.POSITION, newRowIdx).
build());
newRowIdx++;
done = true;
}
// handle the last row
if (i == mHomeItems.size() - 1 && !done) {
Uri uri = ContentUris.withAppendedId(
HomeContract.Rows.CONTENT_URI, item.mRowId);
ops.add(ContentProviderOperation.newUpdate(uri).
withValue(HomeContract.Rows.POSITION, newRowIdx).
build());
}
}
try {
mContext.getContentResolver().applyBatch(HomeContract.AUTHORITY, ops);
} catch (RemoteException | OperationApplicationException e) {
Log.w("HomeAdapter", "error fixing bad row numbers", e);
}
}
public HomeSpanSizeLookup getHomeSpanSizeLookup() {
return mHomeSpanSizeLookup;
}
HomeAdapterEditor getEditor() {
return new HomeAdapterEditor();
}
public int getPositionOfId(long lastId) {
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem item = mHomeItems.get(i);
if (item.mId == lastId) {
return i;
}
}
return -1;
}
public void setStarted(boolean started) {
if (mAppWidgetHost != null && mAppWidgetsEnabled) {
if (mStarted != started) {
mStarted = started;
if (started) {
mAppWidgetHost.startListening();
mAutoAdvanceHelper.start();
} else {
mAppWidgetHost.stopListening();
mAutoAdvanceHelper.stop();
}
}
}
if (mAppWidgetsEnabled) {
if (!started) {
sAppWidgetViewCache.clear();
}
}
}
public HomeItem getHomeItemForAppWidgetId(int appWidgetId) {
HomeItemConfiguration helper = mHomeItemConfiguration;
long cellId = helper.
findCellWithPropertyValue("app_widget_id", String.valueOf(appWidgetId));
if (cellId != -1) {
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mId == cellId) {
return homeItem;
}
}
}
return null;
}
public void onTrimMemory(int level) {
}
void onPermissionDenied(PermissionDeniedException e, String permission, String id) {
// This allows the HomePage to implement something to handle this
mPermissionErrorListener.onPermissionDenied(
permission, id, R.string.permission_reason_contacts_profile_image);
}
public void setPermissionErrorListener(
PermissionErrorListener permissionErrorListener) {
mPermissionErrorListener = permissionErrorListener;
}
interface ViewWrapperFactory {
/**
* Wraps an home item view in a view that is used by the ViewHolder to manage the
* height of the view in it's row.
*/
HomeViewWrapper wrapView(Context context, View child, int heightPx);
}
interface OpsBuilder {
Operation newInsert(Uri uri);
Operation newDelete(Uri uri);
Operation newUpdate(Uri uri);
}
interface Operation {
Operation withValue(String position, int value);
Operation withValue(String position, long value);
Operation withValueBackReference(String rowId, int resultIdx);
Operation withSelection(String selection, String[] selectionArgs);
ContentProviderOperation build();
}
interface PermissionErrorListener {
/**
* Informs about a permission error.
*
* @param id The id of the error. This is unique for the combination
* of the cell and the permission that was denied
*/
void onPermissionDenied(String permission, String id, @StringRes int textResId);
}
static class DefaultViewWrapperFactory implements ViewWrapperFactory {
@Override
public HomeViewWrapper wrapView(Context context, View child, int heightPx) {
HomeViewWrapper result = new HomeViewWrapper(context);
RecyclerView.LayoutParams params =
new RecyclerView.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, heightPx);
result.addView(child, params);
return result;
}
}
static class ClockViewHolder extends BaseViewHolder implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener, PopupMenu.OnMenuItemClickListener {
final AnalogClock mAnalogClock;
final HomeItemConfiguration mConfigurationHelper;
final TextView mTextView;
public ClockViewHolder(HomeViewWrapper view, HomeItemConfiguration conf) {
super(view);
mConfigurationHelper = conf;
mAnalogClock = (AnalogClock) view.findViewById(R.id.analog_clock);
mTextView = (TextView) view.findViewById(R.id.primary_text);
mOverflow.setOnClickListener(this);
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.overflow) {
showOverflowMenu(v);
}
}
private void showOverflowMenu(View v) {
PopupMenuHelper.showPopupMenu(v, R.menu.home_item_clock, this);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_cell_weather_prefs) {
Context context = itemView.getContext();
Intent intent = new Intent(context, CellClockActivity.class);
intent.putExtra(CellClockActivity.EXTRA_CELL_ID, mHomeItem.mId);
context.startActivity(intent);
return true;
}
return false;
}
@Override
void updateConfiguration() {
long cellId = mHomeItem.mId;
String timezone = mConfigurationHelper.getProperty(cellId, "timezone_id", null);
String title = mConfigurationHelper.getProperty(cellId, "title", null);
if (timezone == null) {
mAnalogClock.clearTimezone();
} else {
mAnalogClock.setTimezone(timezone);
}
if (TextUtils.isEmpty(title)) {
mTextView.setText(R.string.no_title);
} else {
mTextView.setText(title);
}
}
@Override
void bind(HomeItem item, int heightPx) {
super.bind(item, heightPx);
mChildView.setOnClickListener(this);
updateConfiguration();
}
}
static class BluetoothViewHolder extends BaseViewHolder implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener, PopupMenu.OnMenuItemClickListener {
final HomeItemConfiguration mConfigurationHelper;
final TextView mTextView;
final Drawable mBluetoothDrawable;
final ImageView mImageView;
private final int mPrimaryColor;
private final int mWidgetColor;
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR);
switch (state) {
case BluetoothAdapter.STATE_OFF:
showOffStatus();
break;
case BluetoothAdapter.STATE_TURNING_OFF:
mTextView.setText(R.string.turning_off);
break;
case BluetoothAdapter.STATE_ON:
showOnStatus();
break;
case BluetoothAdapter.STATE_TURNING_ON:
mTextView.setText(R.string.turning_on);
break;
}
}
}
};
private final BluetoothAdapter mBluetoothAdapter;
public BluetoothViewHolder(HomeViewWrapper view, HomeItemConfiguration conf) {
super(view);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mConfigurationHelper = conf;
mImageView = (ImageView) view.findViewById(R.id.bluetooth_image);
mBluetoothDrawable = mImageView.getDrawable();
mTextView = (TextView) view.findViewById(R.id.primary_text);
Context context = view.getContext();
final TypedArray a = context.obtainStyledAttributes(
new int[]{R.attr.colorPrimary, R.attr.colorAccent,
R.attr.colorPrimaryDark,
R.attr.appsiHomeWidgetPrimaryColor,
});
mPrimaryColor = a.getColor(0, Color.BLACK);
mWidgetColor = a.getColor(3, Color.BLACK);
a.recycle();
mOverflow.setOnClickListener(this);
}
@Override
public void onAllowLoads() {
super.onAllowLoads();
IntentFilter f = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
itemView.getContext().registerReceiver(mReceiver, f);
}
@Override
public boolean onMenuItemClick(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_bluetooth_settings) {
Intent intent = new Intent(Settings.ACTION_BLUETOOTH_SETTINGS);
try {
itemView.getContext().startActivity(intent);
} catch (Exception e) {
// TODO: show toast
}
return true;
}
return false;
}
@Override
public void onDisallowLoads() {
super.onDisallowLoads();
itemView.getContext().unregisterReceiver(mReceiver);
}
void showOnStatus() {
DrawableCompat.setTintColorCompat(mBluetoothDrawable, mPrimaryColor);
mTextView.setText(R.string.enabled);
}
void showOffStatus() {
DrawableCompat.setTintColorCompat(mBluetoothDrawable, mWidgetColor);
mTextView.setText(R.string.disabled);
}
@Override
void updateConfiguration() {
if (mBluetoothAdapter == null) {
mBluetoothDrawable.setAlpha(128);
mTextView.setText(R.string.no_bluetooth);
} else {
mBluetoothDrawable.setAlpha(255);
if (mBluetoothAdapter.isEnabled()) {
showOnStatus();
} else {
showOffStatus();
}
}
}
@Override
void bind(HomeItem item, int heightPx) {
super.bind(item, heightPx);
mChildView.setOnClickListener(this);
updateConfiguration();
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.overflow) {
showOverflowMenu(v);
} else {
if (mBluetoothAdapter != null) {
if (mBluetoothAdapter.isEnabled()) {
mBluetoothAdapter.disable();
} else {
mBluetoothAdapter.enable();
}
}
}
}
private void showOverflowMenu(View v) {
PopupMenuHelper.showPopupMenu(v, R.menu.home_item_bluetooth, this);
}
}
static abstract class AbsWeatherTemperatureViewHolder extends AbsWeatherViewHolder
implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener {
@Nullable
final
ImageView mWeatherConditionView;
final TextView mTextView;
final TextView mTemperatureView;
final WeatherUtils mWeatherUtils;
public AbsWeatherTemperatureViewHolder(HomeViewWrapper view, boolean showsWallpaper) {
super(view, showsWallpaper);
mWeatherUtils = AppInjector.provideWeatherUtils();
mWeatherConditionView = (ImageView) view.findViewById(R.id.weather_condition_image);
mTextView = (TextView) view.findViewById(R.id.primary_text);
mTemperatureView = (TextView) view.findViewById(R.id.temperature);
}
@Override
void bind(HomeItem item, int heightPx) {
mHomeItem = item;
applySuggestedHeight(heightPx);
mChildView.setOnClickListener(this);
updateConfiguration();
}
abstract void bindWeatherData(long cellId, WeatherData weatherData, boolean isDay,
String temperature);
@Override
void onWeatherDataReady(WeatherData weatherData) {
long cellId = mHomeItem.mId;
String timezone = mConfigurationHelper.getProperty(
cellId, WeatherFragment.PREFERENCE_WEATHER_TIMEZONE, null);
if (timezone == null) {
timezone = TimeZone.getDefault().getID();
}
boolean isDay = mWeatherUtils.isDay(timezone, weatherData);
String unit = weatherData.unit;
String defaultUnit = mPreferenceHelper.getDefaultWeatherTemperatureUnit();
String displayUnit = mConfigurationHelper.getProperty(cellId,
WeatherFragment.PREFERENCE_WEATHER_UNIT, defaultUnit);
String temperature = mWeatherUtils.formatTemperature(itemView.getContext(),
weatherData.nowTemperature, unit, displayUnit,
WeatherUtils.FLAG_TEMPERATURE_NO_UNIT);
bindWeatherData(cellId, weatherData, isDay, temperature);
}
@Override
void onNoWeatherDataAvailable() {
if (mWeatherConditionView != null) {
mWeatherConditionView.setImageResource(R.drawable.ic_weather_unknown);
}
mTextView.setText(R.string.unknown);
mTemperatureView.setText("");
}
@Override
public void onClick(View v) {
super.onClick(v);
}
}
static class WeatherTemperaturePlainViewHolder extends AbsWeatherTemperatureViewHolder
implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener {
public WeatherTemperaturePlainViewHolder(HomeViewWrapper view) {
super(view, false /* showsWallpaper */);
}
@Override
void bindWeatherData(long cellId, WeatherData weatherData, boolean isDay,
String temperature) {
int iconResId =
mWeatherUtils.getConditionCodeIconResId(weatherData.nowConditionCode, isDay);
mWeatherConditionView.setImageResource(iconResId);
setupTitle(mTextView, weatherData, cellId);
mTemperatureView.setText(temperature);
}
}
static class WeatherTemperatureWallpaperViewHolder extends AbsWeatherTemperatureViewHolder
implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener {
public WeatherTemperatureWallpaperViewHolder(HomeViewWrapper view) {
super(view, true /* showsWallpaper */);
}
@Override
protected void configurePaintJob(PaintJob.Builder paintJob) {
paintJob.paintWithSwatch(PaintJob.SWATCH_DARK_MUTED,
ViewPainters.text(R.id.primary_text, R.id.temperature),
ViewPainters.argb(128, R.id.primary_text, R.id.temperature),
DrawableStartTintPainter.forIds(R.id.primary_text));
}
@Override
void bindWeatherData(long cellId, WeatherData weatherData, boolean isDay,
String temperature) {
// TODO: implement properly
int iconResId =
mWeatherUtils
.getConditionCodeTinyIconResId(weatherData.nowConditionCode, isDay);
mTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(iconResId, 0, 0, 0);
setupTitle(mTextView, weatherData, cellId);
mTemperatureView.setText(temperature);
}
}
static class AutoAdvanceHelper implements Handler.Callback {
final SimpleArrayMap<View, AppWidgetProviderInfo> mWidgetsToAdvance = new SimpleArrayMap<>();
private final int ADVANCE_MSG = 1;
private final int mAdvanceInterval = 20000;
private final int mAdvanceStagger = 250;
private final Handler mHandler;
boolean mVisible;
boolean mAutoAdvanceRunning;
private long mAutoAdvanceSentTime;
private long mAutoAdvanceTimeLeft = -1;
AutoAdvanceHelper() {
mHandler = new Handler(this);
}
void addWidgetToAutoAdvanceIfNeeded(View hostView, AppWidgetProviderInfo appWidgetInfo) {
if (appWidgetInfo == null || appWidgetInfo.autoAdvanceViewId == -1) return;
View v = hostView.findViewById(appWidgetInfo.autoAdvanceViewId);
if (v instanceof Advanceable) {
mWidgetsToAdvance.put(hostView, appWidgetInfo);
((Advanceable) v).fyiWillBeAdvancedByHostKThx();
updateRunning();
}
}
private void updateRunning() {
boolean autoAdvanceRunning = mVisible && !mWidgetsToAdvance.isEmpty();
if (autoAdvanceRunning != mAutoAdvanceRunning) {
mAutoAdvanceRunning = autoAdvanceRunning;
if (autoAdvanceRunning) {
long delay =
mAutoAdvanceTimeLeft == -1 ? mAdvanceInterval : mAutoAdvanceTimeLeft;
sendAdvanceMessage(delay);
} else {
if (!mWidgetsToAdvance.isEmpty()) {
mAutoAdvanceTimeLeft = Math.max(0, mAdvanceInterval -
(System.currentTimeMillis() - mAutoAdvanceSentTime));
}
mHandler.removeMessages(ADVANCE_MSG);
mHandler.removeMessages(0); // Remove messages sent using postDelayed()
}
}
}
private void sendAdvanceMessage(long delay) {
mHandler.removeMessages(ADVANCE_MSG);
Message msg = mHandler.obtainMessage(ADVANCE_MSG);
mHandler.sendMessageDelayed(msg, delay);
mAutoAdvanceSentTime = System.currentTimeMillis();
}
void removeWidgetToAutoAdvance(View hostView) {
if (mWidgetsToAdvance.containsKey(hostView)) {
mWidgetsToAdvance.remove(hostView);
updateRunning();
}
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == ADVANCE_MSG) {
int count = mWidgetsToAdvance.size();
for (int i = 0; i < count; i++) {
View key = mWidgetsToAdvance.keyAt(i);
final View v = key.findViewById(mWidgetsToAdvance.get(key).autoAdvanceViewId);
final int delay = mAdvanceStagger * i;
if (v instanceof Advanceable) {
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
((Advanceable) v).advance();
}
}, delay);
}
}
sendAdvanceMessage(mAdvanceInterval);
return true;
}
return false;
}
public void stop() {
mVisible = false;
updateRunning();
}
public void start() {
mVisible = true;
updateRunning();
}
}
static class DisabledAppWidgetViewHolder extends BaseViewHolder {
final AppWidgetManager mAppWidgetManager;
final ImageView mWidgetPreview;
final TextView mNoWidgetTextView;
int mAppWidgetId;
AppWidgetUtils mAppWidgetUtils;
public DisabledAppWidgetViewHolder(HomeViewWrapper view,
AppWidgetManager appWidgetManager, AppWidgetUtils appWidgetUtils) {
super(view);
mWidgetPreview = (ImageView) view.findViewById(R.id.widget_preview);
mNoWidgetTextView = (TextView) view.findViewById(R.id.no_widget_selected_text);
mAppWidgetManager = appWidgetManager;
}
@Override
void updateConfiguration() {
long cellId = mHomeItem.mId;
String appWidgetId = mConfigurationHelper.getProperty(cellId, "app_widget_id", null);
mAppWidgetId = appWidgetId == null ? -1 : Integer.parseInt(appWidgetId);
if (mAppWidgetId == -1) {
mWidgetPreview.setImageResource(0);
mNoWidgetTextView.setVisibility(View.VISIBLE);
} else {
mNoWidgetTextView.setVisibility(View.GONE);
AppWidgetProviderInfo appWidgetInfo = mAppWidgetManager.getAppWidgetInfo(
mAppWidgetId);
Bitmap image = mAppWidgetUtils.getWidgetPreviewBitmap(appWidgetInfo, null);
mWidgetPreview.setImageBitmap(image);
}
}
}
static class AppWidgetViewHolder extends BaseViewHolder
implements View.OnClickListener, AppsiiAppWidgetHost.HostStatusListener {
final AppsiiAppWidgetHost mAppWidgetHost;
final AppWidgetManager mAppWidgetManager;
final ViewGroup mContainer;
final AutoAdvanceHelper mAutoAdvanceHelper;
final int mTouchSlop;
final View mChooseWidgetButton;
final View mErrorLoadingWidgetButton;
final CardView mCardView;
final int mDefaultColor;
int mAppWidgetId;
@Nullable
AppWidgetHostView mAppWidgetHostView;
AppWidgetProviderInfo mAppWidgetInfo;
int mWidth;
int mHeight;
boolean mInitializedSinceLastStop;
public AppWidgetViewHolder(HomeViewWrapper view, AppsiiAppWidgetHost appWidgetHost,
AppWidgetManager appWidgetManager, AutoAdvanceHelper autoAdvanceHelper) {
super(view);
mAutoAdvanceHelper = autoAdvanceHelper;
mAppWidgetHost = appWidgetHost;
mAppWidgetManager = appWidgetManager;
mChooseWidgetButton = view.findViewById(R.id.choose_app_widget_button);
mErrorLoadingWidgetButton = view.findViewById(R.id.error_loading_widget_button);
mContainer = (ViewGroup) view.findViewById(R.id.widget_container);
mCardView = (CardView) view.findViewById(R.id.widget_card);
mErrorLoadingWidgetButton.setVisibility(View.GONE);
TypedArray a = view.getContext().obtainStyledAttributes(
new int[]{R.attr.appsiAppWidgetCardBackground});
mDefaultColor = a.getColor(0, Color.BLACK);
a.recycle();
mTouchSlop = ViewConfiguration.get(view.getContext()).getScaledTouchSlop();
}
static void updateWidgetSizeRanges(AppWidgetHostView widgetView, int w, int h) {
Context context = widgetView.getContext();
float density = context.getResources().getDisplayMetrics().density;
widgetView.updateAppWidgetSize(null,
(int) (w / density),
(int) (h / density),
(int) (w / density),
(int) (h / density));
}
public void postCreate() {
mAppWidgetHost.registerHostStatusListener(this);
mChooseWidgetButton.setOnClickListener(this);
mErrorLoadingWidgetButton.setOnClickListener(this);
}
@Override
public void onStartedListening() {
updateConfiguration();
}
/**
* Adds the widget to the container, returning true when successful. May fail
* in cases where there is a RemoteException. In that case no widget is added.
*/
private boolean addWidgetToContainer(Context context) {
// We have a simple cache for the app-widget host-views. This
// cache is only valid at the time where the host is listening
// The cache is automatically cleared when the host is stopped
// so this is just an optimization, to improve performance
AppWidgetHostView cached = sAppWidgetViewCache.get(mAppWidgetId);
if (cached == null) {
mAppWidgetHostView = safelyCreateView(context, mAppWidgetId, mAppWidgetInfo);
} else {
mAppWidgetHostView = cached;
}
if (mAppWidgetHostView == null) {
return false;
}
mAppWidgetHostView.setPadding(0, 0, 0, 0);
mContainer.removeAllViews();
// we need to remove from the parent if the view is still attached
// to a view that is in the recycler-cache.
ViewGroup parent = (ViewGroup) mAppWidgetHostView.getParent();
if (parent != null) {
parent.removeView(mAppWidgetHostView);
}
mContainer.addView(mAppWidgetHostView);
if (cached == null) {
mContainer.setAlpha(0);
mContainer.animate().alpha(1).withEndAction(null);
}
return true;
}
private AppWidgetHostView safelyCreateView(
Context context, int appWidgetId, AppWidgetProviderInfo info) {
// This is an IPC call, and may cause a crash such as
// java.lang.RuntimeException: system server dead?
// Caused by: android.os.TransactionTooLargeException
try {
AppWidgetHostView result = mAppWidgetHost.createView(context, appWidgetId, info);
// add it to the cache
sAppWidgetViewCache.put(appWidgetId, result);
return result;
} catch (RuntimeException e) {
return null;
}
}
@Override
public void onStoppedListening() {
mInitializedSinceLastStop = false;
}
@Override
public void onViewsCleared() {
clearAppWidget();
}
void clearAppWidget() {
mAppWidgetId = -1;
mAppWidgetInfo = null;
if (mAppWidgetHostView != null) {
mAutoAdvanceHelper.removeWidgetToAutoAdvance(mAppWidgetHostView);
}
mContainer.animate().alpha(0).withEndAction(new Runnable() {
@Override
public void run() {
mContainer.removeAllViews();
}
});
mAppWidgetHostView = null;
}
@Override
void bind(HomeItem item, int heightPx) {
super.bind(item, heightPx);
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
if (mAppWidgetHostView != null) {
updateWidgetSizeRanges(mAppWidgetHostView, w, h);
}
}
@Override
void updateConfiguration() {
int old = mAppWidgetId;
long cellId = mHomeItem.mId;
Context context = itemView.getContext();
String appWidgetId = mConfigurationHelper.getProperty(cellId, "app_widget_id", null);
mAppWidgetId = appWidgetId == null ? -1 : Integer.parseInt(appWidgetId);
boolean widgetActive;
if (mAppWidgetId == -1) {
mChooseWidgetButton.setVisibility(View.VISIBLE);
widgetActive = false;
} else if (mAppWidgetId != old) {
mChooseWidgetButton.setVisibility(View.GONE);
if (mAppWidgetHostView != null) {
mAutoAdvanceHelper.removeWidgetToAutoAdvance(mAppWidgetHostView);
}
mAppWidgetInfo = mAppWidgetManager.getAppWidgetInfo(mAppWidgetId);
widgetActive = addWidgetToContainer(context);
} else {
mChooseWidgetButton.setVisibility(View.GONE);
// nothing changed, so we don't need to do anything
// we only re-create the view to ensure it is up-to-date
widgetActive = addWidgetToContainer(context);
}
if (mHomeItem.mEffectColor == Color.TRANSPARENT) {
mCardView.setCardBackgroundColor(mDefaultColor);
} else {
mCardView.setCardBackgroundColor(mHomeItem.mEffectColor);
}
if (widgetActive) {
updateWidgetSizeRanges(mAppWidgetHostView, mWidth, mHeight);
mAutoAdvanceHelper.addWidgetToAutoAdvanceIfNeeded(mAppWidgetHostView,
mAppWidgetInfo);
} else if (mAppWidgetId != -1) {
mErrorLoadingWidgetButton.setVisibility(View.VISIBLE);
}
}
@Override
public void onAttached(boolean allowLoads) {
super.onAttached(allowLoads);
if (mAppWidgetHost.isListening() && !mInitializedSinceLastStop) {
mInitializedSinceLastStop = true;
updateConfiguration();
}
if (mAppWidgetHostView != null) {
mAutoAdvanceHelper.addWidgetToAutoAdvanceIfNeeded(mAppWidgetHostView,
mAppWidgetInfo);
}
}
@Override
public void onDetached(boolean allowLoads) {
super.onDetached(allowLoads);
if (mAppWidgetHostView != null) {
mAutoAdvanceHelper.removeWidgetToAutoAdvance(mAppWidgetHostView);
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.error_loading_widget_button) {
mErrorLoadingWidgetButton.setVisibility(View.GONE);
updateConfiguration();
} else {
Context context = itemView.getContext();
Intent intent = new Intent(context, WidgetChooserActivity.class);
intent.putExtra(WidgetChooserActivity.EXTRA_CELL_ID, mHomeItem.mId);
context.startActivity(intent);
}
}
}
static class PlainWeatherWindViewHolder extends AbsWeatherWindViewHolder {
public PlainWeatherWindViewHolder(HomeViewWrapper view) {
super(view, false /* showsWallpaper */);
}
@Override
void applyWeatherData(String unit, String windSpeed, WeatherData weatherData) {
// properly convert the speed if needed
if (!"c".equals(unit)) {
int speed = Math.round(mWeatherUtils.toKph(weatherData.windSpeed));
mLargeWindmillDrawable.setWindSpeedKmh(speed);
mSmallWindmillDrawable.setWindSpeedKmh(speed);
} else {
mLargeWindmillDrawable.setWindSpeedKmh(weatherData.windSpeed);
mSmallWindmillDrawable.setWindSpeedKmh(weatherData.windSpeed);
}
mWindSpeedView.setText(windSpeed);
mLargeWindmillDrawable.start();
mSmallWindmillDrawable.start();
}
}
static class WallpaperWeatherWindViewHolder extends AbsWeatherWindViewHolder {
public WallpaperWeatherWindViewHolder(HomeViewWrapper view) {
super(view, true /* showsWallpaper */);
}
@Override
void applyWeatherData(String unit, String windSpeed, WeatherData weatherData) {
// properly convert the speed if needed
if (!"c".equals(unit)) {
int speed = Math.round(mWeatherUtils.toKph(weatherData.windSpeed));
mLargeWindmillDrawable.setWindSpeedKmh(speed);
mSmallWindmillDrawable.setWindSpeedKmh(speed);
} else {
mLargeWindmillDrawable.setWindSpeedKmh(weatherData.windSpeed);
mSmallWindmillDrawable.setWindSpeedKmh(weatherData.windSpeed);
}
mWindSpeedView.setText(windSpeed);
mLargeWindmillDrawable.start();
mSmallWindmillDrawable.start();
}
}
static abstract class AbsWeatherWindViewHolder extends AbsWeatherViewHolder
implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener {
final WindmillDrawable mLargeWindmillDrawable;
final WindmillDrawable mSmallWindmillDrawable;
final ImageView mLargeWindmill;
final ImageView mSmallWindmill;
final TextView mTextView;
final TextView mWindSpeedView;
public AbsWeatherWindViewHolder(HomeViewWrapper view, boolean showsWallpaper) {
super(view, showsWallpaper);
Context context = view.getContext();
mLargeWindmill = (ImageView) view.findViewById(R.id.weather_windmill_large);
mSmallWindmill = (ImageView) view.findViewById(R.id.weather_windmill_medium);
mTextView = (TextView) view.findViewById(R.id.primary_text);
mWindSpeedView = (TextView) view.findViewById(R.id.wind_speed);
mLargeWindmillDrawable = new WindmillDrawable(context);
mSmallWindmillDrawable = new WindmillDrawable(context);
mLargeWindmill.setImageDrawable(mLargeWindmillDrawable);
mSmallWindmill.setImageDrawable(mSmallWindmillDrawable);
}
public void onRecycled() {
mLargeWindmillDrawable.stop();
mSmallWindmillDrawable.stop();
}
@Override
void bind(HomeItem item, int heightPx) {
mHomeItem = item;
applySuggestedHeight(heightPx);
mChildView.setOnClickListener(this);
updateConfiguration();
}
@Override
public void onAttached(boolean allowLoads) {
super.onAttached(allowLoads);
mLargeWindmillDrawable.start();
mSmallWindmillDrawable.start();
}
@Override
public void onDetached(boolean allowLoads) {
super.onDetached(allowLoads);
mLargeWindmillDrawable.stop();
mSmallWindmillDrawable.stop();
}
@Override
void onWeatherDataReady(WeatherData weatherData) {
String defaultUnit = mPreferenceHelper.getDefaultWeatherTemperatureUnit();
String displayUnit = mConfigurationHelper.getProperty(mHomeItem.mId,
WeatherFragment.PREFERENCE_WEATHER_UNIT, defaultUnit);
long cellId = mHomeItem.mId;
setupTitle(mTextView, weatherData, cellId);
String unit = weatherData.unit;
String windSpeed = mWeatherUtils.formatWindSpeed(
itemView.getContext(), weatherData.windSpeed, unit, displayUnit);
applyWeatherData(unit, windSpeed, weatherData);
}
abstract void applyWeatherData(String unit, String windSpeed, WeatherData weatherData);
@Override
void onNoWeatherDataAvailable() {
mTextView.setText(R.string.unknown);
mWindSpeedView.setText("-");
mLargeWindmillDrawable.stop();
mSmallWindmillDrawable.stop();
}
@Override
public void onClick(View v) {
super.onClick(v);
}
}
static class PlainWeatherSunViewHolder extends AbsWeatherSunViewHolder {
public PlainWeatherSunViewHolder(HomeViewWrapper view) {
super(view, false /* showsWallpaper */);
}
@Override
void applyWeatherData(Context context, long cellId, int minuteNow,
int sunriseMinuteOfDay, int sunsetMinuteOfDay, WeatherData weatherData) {
setupTitle(mTextView, weatherData, cellId);
// set the values on the drawable
mSunriseDrawable.setTime(sunriseMinuteOfDay, sunsetMinuteOfDay, minuteNow);
String time = formatAsTime(context, sunriseMinuteOfDay);
mSunriseView.setText(time);
time = formatAsTime(context, sunsetMinuteOfDay);
mSunsetView.setText(time);
}
}
static class WallpaperWeatherSunViewHolder extends AbsWeatherSunViewHolder {
public WallpaperWeatherSunViewHolder(HomeViewWrapper view) {
super(view, true /* showsWallpaper */);
setIsRecyclable(false);
}
@Override
protected void configurePaintJob(PaintJob.Builder paintJob) {
paintJob.paintWithSwatch(PaintJob.SWATCH_DARK_VIBRANT,
ViewPainters.argb(128, R.id.primary_text),
ViewPainters.text(R.id.primary_text, R.id.sunset, R.id.sunrise),
new DrawablePainter(R.id.weather_sun)
);
}
@Override
void applyWeatherData(Context context, long cellId, int minuteNow,
int sunriseMinuteOfDay, int sunsetMinuteOfDay, WeatherData weatherData) {
setupTitle(mTextView, weatherData, cellId);
// set the values on the drawable
mSunriseDrawable.setTime(sunriseMinuteOfDay, sunsetMinuteOfDay, minuteNow);
String time = formatAsTime(context, sunriseMinuteOfDay);
mSunriseView.setText(time);
time = formatAsTime(context, sunsetMinuteOfDay);
mSunsetView.setText(time);
}
static class DrawablePainter extends PaintJob.BaseViewPainter {
protected DrawablePainter(int... viewIds) {
super(255, viewIds);
}
@Override
public boolean canAnimate() {
return false;
}
@Override
protected int getTargetColorFromSwatch(Palette.Swatch swatch) {
return swatch.getBodyTextColor();
}
@Override
protected boolean applyColorsToView(View view, Palette.Swatch swatch) {
SunriseDrawable drawable = (SunriseDrawable) ((ImageView) view).getDrawable();
drawable.customTheme(R.drawable.ic_small_clear_day,
swatch.getRgb(), swatch.getBodyTextColor(), swatch.getRgb());
return true;
}
@Override
protected void applyColorToView(View view, int color) {
}
@Override
protected int getCurrentColorFromView(View view) {
return 0;
}
}
}
static abstract class AbsWeatherSunViewHolder extends AbsWeatherViewHolder
implements View.OnClickListener,
HomeItemConfigurationHelper.ConfigurationListener {
/**
* The string builder used by the formatter to format the time
*/
final StringBuilder mStringBuilder = new StringBuilder();
/**
* The formatter used to format the time
*/
final Formatter mFormatter = new Formatter(mStringBuilder);
/**
* The view containing the drawable that will draw the info
*/
final ImageView mSunView;
/**
* The main text view containing the location name
*/
final TextView mTextView;
/**
* The drawable doing all the work of displaying the arc and the
* dots
*/
final SunriseDrawable mSunriseDrawable;
/**
* The text-view that displays the sunrise time
*/
final TextView mSunriseView;
/**
* The text-view that displays the sunset time
*/
final TextView mSunsetView;
final Time sTempTime = new Time();
public AbsWeatherSunViewHolder(HomeViewWrapper view, boolean showsWallpaper) {
super(view, showsWallpaper);
Context context = view.getContext();
mSunView = (ImageView) view.findViewById(R.id.weather_sun);
mTextView = (TextView) view.findViewById(R.id.primary_text);
mSunriseView = (TextView) view.findViewById(R.id.sunrise);
mSunsetView = (TextView) view.findViewById(R.id.sunset);
mSunriseDrawable = new SunriseDrawable(context);
mSunView.setImageDrawable(mSunriseDrawable);
}
/**
* Uses the formatter to render the minuteOfDay into a string.
*/
String formatAsTime(Context context, int minuteOfDay) {
// calculate the sunrise time in millis
sTime.hour = minuteOfDay / 60;
sTime.minute = minuteOfDay % 60;
sTime.second = 0;
long millisSunrise = sTime.normalize(true);
// format it
mStringBuilder.setLength(0);
DateUtils.formatDateRange(context, mFormatter, millisSunrise, millisSunrise,
DateUtils.FORMAT_SHOW_TIME);
// return the resulting string
return mStringBuilder.toString();
}
@Override
void bind(HomeItem item, int heightPx) {
mHomeItem = item;
applySuggestedHeight(heightPx);
mChildView.setOnClickListener(this);
updateConfiguration();
}
@Override
void onWeatherDataReady(WeatherData weatherData) {
long cellId = mHomeItem.mId;
Context context = itemView.getContext();
String timezone = mConfigurationHelper.getProperty(
cellId, WeatherFragment.PREFERENCE_WEATHER_TIMEZONE, null);
// get the minutes now, sunrise and sunset minutes.
if (timezone == null) {
sTempTime.timezone = TimeZone.getDefault().getID();
} else {
sTempTime.timezone = timezone;
}
sTempTime.setToNow();
int minuteNow = sTempTime.minute + 60 * sTempTime.hour;
int sunriseMinuteOfDay = weatherData.getSunriseMinuteOfDay();
int sunsetMinuteOfDay = weatherData.getSunsetMinuteOfDay();
applyWeatherData(
context, cellId, minuteNow, sunriseMinuteOfDay, sunsetMinuteOfDay, weatherData);
}
abstract void applyWeatherData(Context context, long cellId, int minuteNow,
int sunriseMinuteOfDay, int sunsetMinuteOfDay, WeatherData weatherData);
@Override
void onNoWeatherDataAvailable() {
mTextView.setText(R.string.unknown);
}
@Override
public void onClick(View v) {
super.onClick(v);
}
}
/**
* Created by nick on 24/01/15.
*/
static class HomeSpanSizeLookup extends GridLayoutManager.SpanSizeLookup {
final Context mContext;
int[] mHomeItemSpans;
HomeSpanSizeLookup(Context context) {
mContext = context;
}
void setup(List<HomeItem> homeItems) throws InconsistentRowsException {
int row = 0;
int totalItems = 0;
int rowStartIdx = 0;
long rowId = -1;
int N = homeItems.size();
mHomeItemSpans = new int[N];
int pos;
for (pos = 0; pos < N; pos++) {
HomeItem homeItem = homeItems.get(pos);
//if (homeItem.mPageId != pageId) continue;
if (row != homeItem.mRowPosition) {
multiplyPositions(totalItems, rowStartIdx, pos);
totalItems = 0;
rowStartIdx = pos;
row = homeItem.mRowPosition;
}
mHomeItemSpans[pos] = homeItem.mColspan;
totalItems += homeItem.mColspan;
}
multiplyPositions(totalItems, rowStartIdx, pos);
}
/**
* Multiplies the values at the given range (a single row) with the correct multiplier.
* This makes sure we have the proper row-spans set up.
*/
private void multiplyPositions(int itemsInRow, int rangeStart, int rangeEnd)
throws InconsistentRowsException {
int multiplier;
if (itemsInRow == 1) {
multiplier = 12;
} else if (itemsInRow == 2) {
multiplier = 6;
} else if (itemsInRow == 3) {
multiplier = 4;
} else if (itemsInRow == 4) {
multiplier = 3;
} else {
throw new InconsistentRowsException("Invalid count in row: " + itemsInRow);
}
for (int i = rangeStart; i < rangeEnd; i++) {
mHomeItemSpans[i] = mHomeItemSpans[i] * multiplier;
}
}
@Override
public int getSpanSize(int position) {
// Something in the JB accessibility compat causes these
// conditions so we need to check for them
if (mHomeItemSpans == null) return 1;
if (position < 0) return 1;
if (position >= mHomeItemSpans.length) return 1;
return mHomeItemSpans[position];
}
}
static class OperationWrapper implements Operation {
final ContentProviderOperation.Builder mBuilder;
public OperationWrapper(ContentProviderOperation.Builder builder) {
mBuilder = builder;
}
static Operation newInsert(Uri uri) {
return new OperationWrapper(ContentProviderOperation.newInsert(uri));
}
static Operation newDelete(Uri uri) {
return new OperationWrapper(ContentProviderOperation.newDelete(uri));
}
static Operation newUpdate(Uri uri) {
return new OperationWrapper(ContentProviderOperation.newUpdate(uri));
}
@Override
public Operation withValue(String position, int value) {
mBuilder.withValue(position, value);
return this;
}
@Override
public Operation withValue(String position, long value) {
mBuilder.withValue(position, value);
return this;
}
@Override
public Operation withValueBackReference(String rowId, int resultIdx) {
mBuilder.withValueBackReference(rowId, resultIdx);
return this;
}
@Override
public Operation withSelection(String selection, String[] selectionArgs) {
mBuilder.withSelection(selection, selectionArgs);
return this;
}
@Override
public ContentProviderOperation build() {
return mBuilder.build();
}
}
static class DefaultOpsBuilder implements OpsBuilder {
@Override
public Operation newInsert(Uri uri) {
return OperationWrapper.newInsert(uri);
}
@Override
public Operation newDelete(Uri uri) {
return OperationWrapper.newDelete(uri);
}
@Override
public Operation newUpdate(Uri uri) {
return OperationWrapper.newUpdate(uri);
}
}
static class InconsistentRowsException extends Exception {
public InconsistentRowsException() {
}
public InconsistentRowsException(String detailMessage) {
super(detailMessage);
}
public InconsistentRowsException(String detailMessage, Throwable throwable) {
super(detailMessage, throwable);
}
public InconsistentRowsException(Throwable throwable) {
super(throwable);
}
}
class ProfileImageViewHolder extends BaseViewHolder implements View.OnClickListener,
PopupMenu.OnMenuItemClickListener {
final ImageView mImageView;
Bitmap mUserImage;
volatile int mViewWidth;
volatile int mViewHeight;
ContactBitmapLoader mImageLoader;
public ProfileImageViewHolder(HomeViewWrapper view) {
super(view);
mImageView = (ImageView) view.findViewById(R.id.image);
mOverflow.setOnClickListener(this);
}
Uri getProfileContactUri() {
Context context = itemView.getContext();
Cursor c = context.getContentResolver().query(ContactsContract.Contacts.CONTENT_URI,
new String[]{
ContactsContract.Contacts._ID,
ContactsContract.Contacts.LOOKUP_KEY,
},
ContactsContract.Contacts.IS_USER_PROFILE + "=?",
new String[]{
String.valueOf("1")
},
null);
if (c == null) return null;
Uri lookupUri;
if (c.moveToNext()) {
long id = c.getLong(0);
String lookupKey = c.getString(1);
Uri u = Uri.withAppendedPath(
ContactsContract.Contacts.CONTENT_LOOKUP_URI, lookupKey);
lookupUri = Uri.withAppendedPath(u, String.valueOf(id));
} else {
lookupUri = null;
}
c.close();
return lookupUri;
}
void applyUserImage(Bitmap bitmap) {
mUserImage = bitmap;
if (bitmap != null) {
Log.i("Home", "Loader userImage: " + bitmap.getWidth() + "x" + bitmap.getHeight());
}
if (mUserImage == null) {
mUserImage = BitmapFactory.decodeResource(mImageView.getResources(),
R.drawable.fallback_profile_image);
}
mImageView.setImageBitmap(mUserImage);
}
@Override
void updateConfiguration() {
loadUserImageIfNeeded();
}
@Override
void bind(HomeItem item, int heightPx) {
super.bind(item, heightPx);
mChildView.setOnClickListener(this);
if (!HomeViewWrapper.areLoadsDeferred()) {
loadUserImageIfNeeded();
}
}
private void loadUserImageIfNeeded() {
try {
loadUserImageIfNeededImpl();
} catch (PermissionDeniedException e) {
onPermissionDenied(e, Manifest.permission.READ_CONTACTS,
PERMISSION_ERROR_CONTACTS_PROFILE);
}
}
private void loadUserImageIfNeededImpl() throws PermissionDeniedException {
mPermissionUtils.throwIfNotPermitted(mContext, Manifest.permission.READ_CONTACTS);
if (mImageLoader != null) {
mImageLoader.cancel(true);
}
final String contactId =
mConfigurationHelper.getProperty(mHomeItem.mId, "contactId", null);
final String lookupKey =
mConfigurationHelper.getProperty(mHomeItem.mId, "lookupKey", null);
BitmapUtils bitmapUtils = AppInjector.provideBitmapUtils();
mImageLoader = new ContactBitmapLoader(lookupKey, contactId, bitmapUtils);
mImageLoader.execute(itemView.getContext());
}
@Override
public void onDisallowLoads() {
super.onDisallowLoads();
if (mImageLoader != null) {
mImageLoader.cancel(true);
}
}
@Override
public void onAllowLoads() {
super.onAllowLoads();
loadUserImageIfNeeded();
}
@Override
public void onAttached(boolean allowLoads) {
super.onAttached(allowLoads);
if (allowLoads) {
loadUserImageIfNeeded();
}
}
@Override
public void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mViewWidth = w;
mViewHeight = h;
// we wait with the load until we get the onAttached signal
// in case the view is locked
if (!HomeViewWrapper.areLoadsDeferred()) {
loadUserImageIfNeeded();
}
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.overflow) {
PopupMenuHelper.showPopupMenu(v, R.menu.home_item_profile_image, this);
} else {
// clicked on the image.
final String contactId =
mConfigurationHelper.getProperty(mHomeItem.mId, "contactId", null);
final String lookupKey =
mConfigurationHelper.getProperty(mHomeItem.mId, "lookupKey", null);
if (contactId != null && lookupKey != null) {
// TODO: track this event
Context context = v.getContext();
Long cid = Long.parseLong(contactId);
Uri contactLookupUri = ContactsContract.Contacts.getLookupUri(cid, lookupKey);
Intent intent = new Intent(Intent.ACTION_VIEW, contactLookupUri);
AnalyticsManager analyticsManager = mAnalyticsManager;
analyticsManager.trackAppsiEvent(AnalyticsManager.ACTION_OPEN_HOME_ITEM,
AnalyticsManager.CATEGORY_PEOPLE);
context.startActivity(intent);
}
}
}
@Override
public boolean onMenuItemClick(MenuItem item) {
int itemId = item.getItemId();
if (itemId == R.id.action_cell_image_prefs) {
Context context = itemView.getContext();
Intent intent = new Intent(context, CellProfileImageActivity.class);
intent.putExtra(CellProfileImageActivity.EXTRA_CELL_ID, mHomeItem.mId);
context.startActivity(intent);
return true;
}
return false;
}
class ContactBitmapLoader extends AsyncTask<Context, Void, Bitmap> {
final BitmapUtils mBitmapUtils;
private final String mLookupKey;
private final String mContactId;
public ContactBitmapLoader(String lookupKey, String contactId,
BitmapUtils bitmapUtils) {
mLookupKey = lookupKey;
mContactId = contactId;
mBitmapUtils = bitmapUtils;
}
@Override
protected Bitmap doInBackground(Context... params) {
Context context = params[0];
if (mLookupKey != null && mContactId != null) {
long id = Long.parseLong(mContactId);
Contact contact = RawContactsLoader.
loadContact(context, id, mLookupKey);
if (contact != null && contact.mBitmap != null) {
int w = contact.mBitmap.getWidth();
int h = contact.mBitmap.getWidth();
float wscale = mViewWidth / (float) w;
float hscale = mViewWidth / (float) h;
float scale = Math.max(wscale, hscale);
int destH = (int) (h * scale);
int destW = (int) (w * scale);
// can happen when the view was detached earlier
if (destH == 0 || destW == 0) return null;
try {
return Bitmap.createScaledBitmap(contact.mBitmap, destW, destH, true);
} catch (OutOfMemoryError e) {
Crashlytics.logException(e);
return contact.mBitmap;
}
}
}
Bitmap result;
try {
Uri contactUri = ContactsContract.Profile.CONTENT_URI;
result = mBitmapUtils
.decodeContactImage(contactUri, mViewWidth, mViewHeight);
if (result == null) {
Uri lookupUri = getProfileContactUri();
if (lookupUri != null) {
result = mBitmapUtils.decodeContactImage(
lookupUri, mViewWidth, mViewHeight);
}
}
} catch (PermissionDeniedException e) {
// this is already checked before we actually
// start and create the loader. So if it happens
// just return null.
return null;
}
return result;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
applyUserImage(bitmap);
}
}
}
class SimpleTextViewViewHolder extends AbsHomeViewHolder {
public SimpleTextViewViewHolder(HomeViewWrapper view) {
super(view);
}
@Override
void bind(HomeItem item, int heightPx) {
applySuggestedHeight(heightPx);
TextView myView = (TextView) mChildView;
myView.setText("r" + item.mRowPosition + ":p" + item.mPosition + ":s" +
item.mColspan + " - " + item.mDisplayType);
}
}
class EmptyViewViewHolder extends AbsHomeViewHolder {
public EmptyViewViewHolder(HomeViewWrapper view) {
super(view);
}
@Override
void bind(HomeItem item, int heightPx) {
applySuggestedHeight(heightPx);
}
}
class HomeAdapterEditor {
final ArrayList<HomeItem> mTemp = new ArrayList<>();
/**
* The builder used to create the operations. This allows tests to
* override the builder and add additional checks to the created
* operations.
*/
@VisibleForTesting
OpsBuilder mOpsBuilder = new DefaultOpsBuilder();
public void moveRowDown(int rowPosition, List<ContentProviderOperation> ops) {
if (isLastRow(rowPosition)) return;
boolean addedOpDown = false;
boolean addedOpUp = false;
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowPosition == rowPosition) {
homeItem.mRowPosition++;
if (!addedOpDown) {
addRowPositionOp(ops, homeItem);
addedOpDown = true;
}
} else if (homeItem.mRowPosition == rowPosition + 1) {
homeItem.mRowPosition--;
if (!addedOpUp) {
addRowPositionOp(ops, homeItem);
addedOpUp = true;
}
}
}
}
boolean isLastRow(int rowPosition) {
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowPosition > rowPosition) return false;
}
return true;
}
private void addRowPositionOp(List<ContentProviderOperation> ops, HomeItem homeItem) {
Uri uri = rowUri(homeItem);
ops.add(mOpsBuilder.newUpdate(uri).
withValue(HomeContract.Rows.POSITION, homeItem.mRowPosition).
build()
);
}
private Uri rowUri(HomeItem homeItem) {
return ContentUris.withAppendedId(HomeContract.Rows.CONTENT_URI, homeItem.mRowId);
}
public void moveRowUp(int rowPosition, List<ContentProviderOperation> ops) {
if (rowPosition == 0) return;
boolean addedOpDown = false;
boolean addedOpUp = false;
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowPosition == rowPosition) {
homeItem.mRowPosition--;
if (!addedOpUp) {
addRowPositionOp(ops, homeItem);
addedOpUp = true;
}
} else if (homeItem.mRowPosition == rowPosition - 1) {
homeItem.mRowPosition++;
if (!addedOpDown) {
addRowPositionOp(ops, homeItem);
addedOpDown = true;
}
}
}
}
public int getItemPosition(long id) {
int position = 0;
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mId == id) return position;
position++;
}
return -1;
}
public void moveItemRight(int position, List<ContentProviderOperation> ops) {
if (isInvalidPosition(position)) return;
boolean isLast = mHomeItems.size() - 1 == position;
if (isLast) return;
HomeItem item = mHomeItems.get(position);
HomeItem next = mHomeItems.get(position + 1);
if (next.mRowPosition != item.mRowPosition) return;
item.mPosition++;
next.mPosition--;
ops.add(mOpsBuilder.newUpdate(homeItemUri(item)).
withValue(HomeContract.Cells.POSITION, item.mPosition).
build()
);
ops.add(mOpsBuilder.newUpdate(homeItemUri(next)).
withValue(HomeContract.Cells.POSITION, next.mPosition).
build()
);
}
private boolean isInvalidPosition(int position) {
return (position < 0 || position >= mHomeItems.size());
}
Uri homeItemUri(HomeItem homeItem) {
return ContentUris.withAppendedId(HomeContract.Cells.CONTENT_URI, homeItem.mId);
}
boolean canMoveLeft(int position) {
if (position == 0) return false;
HomeItem item = mHomeItems.get(position);
HomeItem prev = mHomeItems.get(position - 1);
return prev.mRowPosition == item.mRowPosition;
}
boolean canMoveRight(int position) {
if (position >= mHomeItems.size() - 1) return false;
HomeItem item = mHomeItems.get(position);
HomeItem next = mHomeItems.get(position + 1);
return next.mRowPosition == item.mRowPosition;
}
public boolean canMoveUp(int position) {
HomeItem item = mHomeItems.get(position);
if (item.mRowPosition == 0) return false;
return true;
}
public boolean canMoveDown(int position) {
HomeItem item = mHomeItems.get(position);
HomeItem last = mHomeItems.get(mHomeItems.size() - 1);
if (item.mRowPosition == last.mRowPosition) return false;
return true;
}
public boolean canIncreaseHeight(int position) {
return true;
}
public boolean canDecreaseHeight(int position) {
HomeItem item = mHomeItems.get(position);
return item.mRowHeight > 1;
}
public boolean canIncreaseSpan(int position) {
HomeItem homeItem = mHomeItems.get(position);
int mRowPosition = homeItem.mRowPosition;
int totalSpan = getTotalSpanForRow(mRowPosition);
int itemCountInRow = getItemCountInRow(mRowPosition);
if (itemCountInRow == 1) return false;
return totalSpan < 4;
}
private int getTotalSpanForRow(int rowPosition) {
int spanCount = 0;
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowPosition == rowPosition) {
spanCount += homeItem.mColspan;
}
}
return spanCount;
}
private int getItemCountInRow(int rowPosition) {
int itemCount = 0;
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowPosition == rowPosition) {
itemCount++;
}
}
return itemCount;
}
public boolean canDecreaseSpan(int position) {
HomeItem item = mHomeItems.get(position);
if (item.mColspan == 1) return false;
return item.mColspan > 1;
}
// walter klep 076 501 0002
public void moveItemLeft(int position, List<ContentProviderOperation> ops) {
boolean isFirst = position == 0;
if (isFirst) return;
if (isInvalidPosition(position)) return;
HomeItem item = mHomeItems.get(position);
HomeItem prev = mHomeItems.get(position - 1);
if (prev.mRowPosition != item.mRowPosition) return;
prev.mPosition = item.mPosition;
item.mPosition--;
ops.add(mOpsBuilder.newUpdate(homeItemUri(item)).
withValue(HomeContract.Cells.POSITION, item.mPosition).
build()
);
ops.add(mOpsBuilder.newUpdate(homeItemUri(prev)).
withValue(HomeContract.Cells.POSITION, prev.mPosition).
build()
);
}
public void increaseSpan(int position, List<ContentProviderOperation> ops) {
HomeItem homeItem = mHomeItems.get(position);
int mRowPosition = homeItem.mRowPosition;
int totalSpan = getTotalSpanForRow(mRowPosition);
int itemCountInRow = getItemCountInRow(mRowPosition);
if (itemCountInRow == 1) return;
if (totalSpan >= 4) return;
if (ops != null) {
Uri uri = homeItemUri(homeItem);
ops.add(mOpsBuilder.newUpdate(uri).
withValue(HomeContract.Cells.COLSPAN, homeItem.mColspan + 1).
build());
}
}
public void decreaseSpan(int position, List<ContentProviderOperation> ops) {
HomeItem homeItem = mHomeItems.get(position);
if (homeItem.mColspan == 1) return;
int mRowPosition = homeItem.mRowPosition;
int itemCountInRow = getItemCountInRow(mRowPosition);
if (itemCountInRow == 1) return;
if (ops != null) {
Uri uri = homeItemUri(homeItem);
ops.add(mOpsBuilder.newUpdate(uri).
withValue(HomeContract.Cells.COLSPAN, homeItem.mColspan - 1).
build());
}
}
public void switchItemPositions(int positionFrom, int positionTo,
List<ContentProviderOperation> ops) {
// check for < 0
if (positionFrom < 0) return;
if (positionTo < 0) return;
if (positionTo >= mHomeItems.size() || positionFrom >= mHomeItems.size()) return;
HomeItem h1 = mHomeItems.get(positionTo);
HomeItem h2 = mHomeItems.get(positionFrom);
// first, before changing anything add the ops to update the cells.
// All we need to do is switch row-ids, col-span and position
ops.add(mOpsBuilder.newUpdate(homeItemUri(h1)).
withValue(HomeContract.Cells.COLSPAN, h2.mColspan).
withValue(HomeContract.Cells._ROW_ID, h2.mRowId).
withValue(HomeContract.Cells.POSITION, h2.mPosition).
build()
);
ops.add(mOpsBuilder.newUpdate(homeItemUri(h2)).
withValue(HomeContract.Cells.COLSPAN, h1.mColspan).
withValue(HomeContract.Cells._ROW_ID, h1.mRowId).
withValue(HomeContract.Cells.POSITION, h1.mPosition).
build()
);
}
public boolean canAddCellsInItemRow(int position) {
if (position >= mHomeItems.size()) return false;
HomeItem homeItem = mHomeItems.get(position);
int rowPosition = homeItem.mRowPosition;
if (getItemCountInRow(rowPosition) == 4) return false;
if (getTotalSpanForRow(rowPosition) == 4) return false;
return true;
}
public void removeCellAtPosition(int position, List<ContentProviderOperation> ops) {
if (position < 0) return;
if (position >= mHomeItems.size()) return;
HomeItem homeItem = mHomeItems.get(position);
int rowPosition = homeItem.mRowPosition;
int itemsInRow = getItemCountInRow(rowPosition);
if (itemsInRow == 1) {
removeRowAtItemPosition(position, ops);
return;
}
int size = mHomeItems.size();
// update the next items in the same row by decreasing their position nr.
for (int i = position + 1; i < size; i++) {
HomeItem next = mHomeItems.get(i);
if (next.mRowPosition == homeItem.mRowPosition &&
next.mPosition >= homeItem.mPosition) {
// add the db-op
ops.add(mOpsBuilder.newUpdate(homeItemUri(next)).
withValue(HomeContract.Cells.POSITION, next.mPosition - 1).
build()
);
} else {
break;
}
}
// add the delete operations
addDeleteCellOp(ops, homeItem);
}
public void removeRowAtItemPosition(int position, List<ContentProviderOperation> ops) {
if (position >= mHomeItems.size()) return;
HomeItem item = mHomeItems.get(position);
int rowPosition = item.mRowPosition;
int size = mHomeItems.size();
int rangeStart = -1;
int lastRowPosition = -1;
for (int i = size - 1; i >= 0; i--) {
HomeItem homeItem = mHomeItems.get(i);
if (homeItem.mRowPosition == rowPosition) {
addDeleteCellOp(ops, homeItem);
} else if (homeItem.mRowPosition > rowPosition) {
homeItem.mRowPosition--;
if (lastRowPosition != homeItem.mRowPosition) {
addRowPositionOp(ops, homeItem);
lastRowPosition = homeItem.mRowPosition;
}
} else if (rangeStart == -1) {
rangeStart = i;
}
}
addDeleteRowOp(ops, item);
}
public void insertCellLeftOfPosition(int position, List<ContentProviderOperation> ops) {
insertCellAtPosition(position, false, ops);
}
private void insertCellAtPosition(int position, boolean right,
List<ContentProviderOperation> ops) {
int rowPosition;
int rowHeight;
int cellPosition;
long rowId;
int size = mHomeItems.size();
{
// perform some checks that got a valid index
if (isInvalidPosition(position)) return;
HomeItem hi = mHomeItems.get(position);
int spanCount = getTotalSpanForRow(hi.mRowPosition);
if (spanCount > 3) return;
}
boolean append = size <= position;
// in case we add at the last position in the list
if (append) {
HomeItem item = mHomeItems.get(position - 1);
rowId = item.mRowId;
rowPosition = item.mRowPosition;
cellPosition = item.mPosition + 1;
rowHeight = item.mRowHeight;
} else {
HomeItem item = mHomeItems.get(position);
rowId = item.mRowId;
rowPosition = item.mRowPosition;
rowHeight = item.mRowHeight;
cellPosition = item.mPosition;
if (right) {
cellPosition++;
}
for (int i = position; i < size; i++) {
// if we are adding to the right, skip the item at position
if (right && i == position) continue;
// for all items in the same row, move them one to the right
HomeItem h = mHomeItems.get(i);
if (h.mRowPosition == item.mRowPosition) {
// add op
ops.add(mOpsBuilder.newUpdate(homeItemUri(h)).
withValue(HomeContract.Cells.POSITION, h.mPosition + 1).
build()
);
}
}
}
createEmptyCell(rowId, rowPosition, rowHeight, cellPosition, ops);
}
private HomeItem createEmptyCell(long rowId, int rowPosition, int rowHeight,
int cellPosition, List<ContentProviderOperation> ops) {
HomeItem toInsert = new HomeItem();
toInsert.mDisplayType = HomeContract.Cells.DISPLAY_TYPE_UNSET;
toInsert.mRowId = rowId;
toInsert.mRowPosition = rowPosition;
toInsert.mPosition = cellPosition;
toInsert.mColspan = 1;
toInsert.mRowHeight = rowHeight;
toInsert.mId = nextUnsetId();
ops.add(mOpsBuilder.newInsert(HomeContract.Cells.CONTENT_URI).
withValue(HomeContract.Cells.TYPE, toInsert.mDisplayType).
withValue(HomeContract.Cells._ROW_ID, toInsert.mRowId).
withValue(HomeContract.Cells.POSITION, toInsert.mPosition).
withValue(HomeContract.Cells.COLSPAN, toInsert.mColspan).
build()
);
return toInsert;
}
public void insertCellRightOfPosition(int position, List<ContentProviderOperation> ops) {
insertCellAtPosition(position, true, ops);
}
public void insertRowAbovePosition(int position, List<ContentProviderOperation> ops) {
if (position >= mHomeItems.size()) return;
if (position < 0) return;
position = getFirstItemPositionOfRowAtPosition(position);
int count = mHomeItems.size();
int lastRowPosition = -1;
for (int i = position; i < count; i++) {
HomeItem homeItem = mHomeItems.get(i);
homeItem.mRowPosition++;
// if we haven't seen this row before create an op to update it's position
if (lastRowPosition != homeItem.mRowPosition) {
addRowPositionOp(ops, homeItem);
lastRowPosition = homeItem.mRowPosition;
}
}
HomeItem homeItem = mHomeItems.get(position);
int rowPosition = homeItem.mRowPosition - 1;
// we need to know the location of the insert op to pass it as a backref
int insertOpPosition = ops.size();
ops.add(mOpsBuilder.newInsert(HomeContract.Rows.CONTENT_URI).
withValue(HomeContract.Rows.POSITION, rowPosition).
withValue(HomeContract.Rows._PAGE_ID, homeItem.mPageId).
withValue(HomeContract.Rows.HEIGHT, 1).
build()
);
// now add the op to create the cell in the column with a backref
createEmptyCellWithRowBackRef(rowPosition,
1 /* row-height */, 0 /* cellPosition */, ops, insertOpPosition);
}
private int getFirstItemPositionOfRowAtPosition(int position) {
HomeItem itemAtPosition = mHomeItems.get(position);
for (int i = position; i >= 0; i--) {
HomeItem homeItem = mHomeItems.get(i);
// the first item of a row with a different position is
// the one item before the position we are looking for.
// return position + 1;
if (homeItem.mRowPosition != itemAtPosition.mRowPosition) {
return i + 1;
}
}
// we are at the beginning, return 0 to indicate it should
// be inserted as the very first item
return 0;
}
private HomeItem createEmptyCellWithRowBackRef(int rowPosition, int rowHeight,
int cellPosition, List<ContentProviderOperation> ops, int resultIdx) {
HomeItem toInsert = new HomeItem();
toInsert.mDisplayType = HomeContract.Cells.DISPLAY_TYPE_UNSET;
toInsert.mRowPosition = rowPosition;
toInsert.mPosition = cellPosition;
toInsert.mColspan = 1;
toInsert.mRowHeight = rowHeight;
toInsert.mId = nextUnsetId();
ops.add(mOpsBuilder.newInsert(HomeContract.Cells.CONTENT_URI).
withValue(HomeContract.Cells.TYPE, toInsert.mDisplayType).
withValueBackReference(HomeContract.Cells._ROW_ID, resultIdx).
withValue(HomeContract.Cells.POSITION, toInsert.mPosition).
withValue(HomeContract.Cells.COLSPAN, toInsert.mColspan).
build()
);
return toInsert;
}
public void insertRowBelowPosition(int position, List<ContentProviderOperation> ops) {
if (isInvalidPosition(position)) return;
position = getLastItemPositionOfRowAtPosition(position);
if (position >= mHomeItems.size()) {
position = mHomeItems.size() - 1;
}
int lastRowPosition = -1;
int count = mHomeItems.size();
for (int i = position + 1; i < count; i++) {
HomeItem homeItem = mHomeItems.get(i);
homeItem.mRowPosition++;
// if we haven't seen this row before create an op to update it's position
if (lastRowPosition != homeItem.mRowPosition) {
addRowPositionOp(ops, homeItem);
lastRowPosition = homeItem.mRowPosition;
}
}
HomeItem homeItem = mHomeItems.get(position);
int rowPosition = homeItem.mRowPosition + 1;
// we need to know the location of the insert op to pass it as a backref
int insertOpPosition = ops.size();
ops.add(mOpsBuilder.newInsert(HomeContract.Rows.CONTENT_URI).
withValue(HomeContract.Rows.POSITION, rowPosition).
withValue(HomeContract.Rows._PAGE_ID, homeItem.mPageId).
withValue(HomeContract.Rows.HEIGHT, 1).
build()
);
// now add the op to create the cell in the column with a backref
createEmptyCellWithRowBackRef(rowPosition,
1 /* row-height */, 0 /* cellPosition */, ops, insertOpPosition);
}
private int getLastItemPositionOfRowAtPosition(int position) {
int count = mHomeItems.size();
HomeItem itemAtPosition = mHomeItems.get(position);
for (int i = position; i < count; i++) {
HomeItem homeItem = mHomeItems.get(i);
// the first item of a row with a different position is
// the one item after the position we are looking for.
// return position + 1;
if (homeItem.mRowPosition != itemAtPosition.mRowPosition) {
return i - 1;
}
}
// return the last valid position in the list
return count - 1;
}
public void changeCellType(int position, int displayType,
List<ContentProviderOperation> ops) {
HomeItem homeItem = mHomeItems.get(position);
homeItem.mDisplayType = displayType;
// Note that the deletion of the configuration is done elsewhere to
// prevent problems with the ConfigurationHelper's internal state.
ops.add(mOpsBuilder.newUpdate(homeItemUri(homeItem)).
withValue(HomeContract.Cells.TYPE, displayType).
build()
);
}
public void increaseHeight(int position, ArrayList<ContentProviderOperation> ops) {
if (position < 0) return;
if (position >= mHomeItems.size()) return;
HomeItem homeItem = mHomeItems.get(position);
mTemp.clear();
getItemsInRow(homeItem.mRowId, mTemp);
if (ops != null) {
Uri uri = ContentUris.
withAppendedId(HomeContract.Rows.CONTENT_URI, homeItem.mRowId);
ops.add(mOpsBuilder.newUpdate(uri).
withValue(HomeContract.Rows.HEIGHT, homeItem.mRowHeight + 1).
build()
);
}
}
public void decreaseHeight(int position, ArrayList<ContentProviderOperation> ops) {
if (position < 0) return;
if (position >= mHomeItems.size()) return;
HomeItem homeItem = getItemAt(position);
if (homeItem.mRowHeight <= 1) return;
mTemp.clear();
getItemsInRow(homeItem.mRowId, mTemp);
if (ops != null) {
Uri uri = ContentUris.
withAppendedId(HomeContract.Rows.CONTENT_URI, homeItem.mRowId);
ops.add(mOpsBuilder.newUpdate(uri).
withValue(HomeContract.Rows.HEIGHT, homeItem.mRowHeight - 1).
build()
);
}
}
public HomeItem getItemAt(int position) {
return HomeAdapter.this.getItemAt(position);
}
private void addDeleteCellOp(List<ContentProviderOperation> ops, HomeItem homeItem) {
ops.add(mOpsBuilder.newDelete(homeItemUri(homeItem)).build());
}
private void addDeleteRowOp(List<ContentProviderOperation> ops, HomeItem homeItem) {
ops.add(mOpsBuilder.newDelete(rowUri(homeItem)).build());
}
/**
* Returns true when there are at least two rows in the adapter. Needed to see if
* we can remove a row without removing the last one.
*/
public boolean hasAtLeastTwoRows() {
long rowId = -1;
int N = mHomeItems.size();
for (int i = 0; i < N; i++) {
HomeItem homeItem = mHomeItems.get(i);
if (rowId == -1) {
rowId = homeItem.mRowId;
} else if (rowId != homeItem.mRowId) {
return true;
}
}
return false;
}
}
}