/*
* 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.config;
import android.content.AsyncQueryHandler;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Looper;
import android.support.v4.util.LongSparseArray;
import android.text.TextUtils;
import com.appsimobile.appsii.annotation.VisibleForTesting;
import com.appsimobile.appsii.module.home.provider.HomeContract;
import net.jcip.annotations.GuardedBy;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* A helper facility to ease the process of loading and saving configurations for multiple
* small views.
* Created by nick on 21/01/15.
*/
@Singleton
public class HomeItemConfigurationHelper extends AbstractHomeItemConfiguration {
@GuardedBy("this")
private final LongSparseArray<ConfigurationProperty> mConfigurationProperties;
// Confined to main thread
QueryHandlerImpl mQueryHandler;
HomeItemConfigurationLoader mHomeItemConfigurationLoader;
@Inject
public HomeItemConfigurationHelper(Context context, HomeItemConfigurationLoader loader) {
super(context);
mHomeItemConfigurationLoader = loader;
mConfigurationProperties = mHomeItemConfigurationLoader.loadConfigurations(context);
}
void ensureQueryHandler() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException("Method can only be called on the main thread");
}
mQueryHandler = new QueryHandlerImpl(mContext.getContentResolver());
}
@Override
public void updateProperty(long cellId, String key, String value) {
ensureQueryHandler();
mQueryHandler.updateProperty(cellId, key, value);
}
@Override
public synchronized String getProperty(long cellId, String key, String nullValue) {
ConfigurationProperty property = mConfigurationProperties.get(cellId);
if (property == null) return nullValue;
return property.getProperty(key, nullValue);
}
@Override
public void removeProperty(long cellId, String key) {
ensureQueryHandler();
mQueryHandler.deleteProperty(cellId, key);
}
@Override
public void removeAllProperties(long cellId) {
ensureQueryHandler();
synchronized (this) {
ConfigurationProperty props = mConfigurationProperties.get(cellId);
if (props != null) {
int N = props.size();
for (int i = 0; i < N; i++) {
String key = props.keyAt(i);
mQueryHandler.deleteProperty(cellId, key);
}
}
}
}
@Override
public String[] getWeatherWidgetWoeids(String key) {
Set<String> result = new HashSet<>();
synchronized (this) {
int len = mConfigurationProperties.size();
for (int i = 0; i < len; i++) {
ConfigurationProperty property = mConfigurationProperties.valueAt(i);
String value = property.getProperty(key, null);
if (value != null) {
result.add(value);
}
}
}
return result.toArray(new String[result.size()]);
}
@Override
public long findCellWithPropertyValue(String propertyName, String value) {
synchronized (this) {
int len = mConfigurationProperties.size();
for (int i = 0; i < len; i++) {
ConfigurationProperty property = mConfigurationProperties.valueAt(i);
String propValue = property.getProperty(propertyName, null);
if (propValue != null && TextUtils.equals(value, propValue)) {
return property.mCellId;
}
}
}
return -1;
}
void onPropertyUpdated(long cellId, String key, String value) {
synchronized (this) {
ConfigurationProperty property = mConfigurationProperties.get(cellId);
if (property == null) {
property = createProperty(mConfigurationProperties, cellId);
}
property.put(key, value);
}
notifyPropertyChanged(cellId, key, value);
}
void onPropertyDeleted(long cellId, String key) {
synchronized (this) {
ConfigurationProperty property = mConfigurationProperties.get(cellId);
if (property != null) {
property.remove(key);
}
}
notifyPropertyDeleted(cellId, key);
}
@Singleton
public static class HomeItemConfigurationLoader {
ContentResolver mContentResolver;
@Inject
HomeItemConfigurationLoader(ContentResolver contentResolver) {
mContentResolver = contentResolver;
}
@VisibleForTesting
public LongSparseArray<ConfigurationProperty> loadConfigurations(Context context) {
String[] projection = new String[]{
HomeContract.Configuration._CELL_ID,
HomeContract.Configuration.KEY,
HomeContract.Configuration.VALUE,
};
Cursor cursor = mContentResolver.query(HomeContract.Configuration.CONTENT_URI,
projection,
null,
null,
null);
if (cursor == null) return null;
LongSparseArray<ConfigurationProperty> result = new LongSparseArray<>();
try {
while (cursor.moveToNext()) {
long cellId = cursor.getLong(0);
ConfigurationProperty info = result.get(cellId);
if (info == null) {
info = createProperty(result, cellId);
}
String key = cursor.getString(1);
String value = cursor.getString(2);
info.put(key, value);
}
} finally {
cursor.close();
}
return result;
}
}
class QueryHandlerImpl extends AsyncQueryHandler {
final String mSelection = HomeContract.Configuration._CELL_ID + "=? AND " +
HomeContract.Configuration.KEY + "=?";
/**
* This is a cookie pool, this prevents that we create lots of objects that need
* to be garbage collected. Once an object is acquired from the pool, it is used
* as a cookie in a query.
* When done with this cookie it must be released, which will add it to the pool
* again.
* SimpleCookie instances must not be leaked outside this class.
*/
final LinkedList<SimpleCookie> mCookiePool = new LinkedList<>();
public QueryHandlerImpl(ContentResolver cr) {
super(cr);
}
void deleteProperty(long cellId, String key) {
SimpleCookie cookie = acquireCookie(cellId, key);
startDelete(0, cookie, HomeContract.Configuration.CONTENT_URI, mSelection, new String[]{
String.valueOf(cellId),
key
});
}
SimpleCookie acquireCookie(long cellId, String key) {
if (mCookiePool.isEmpty()) return new SimpleCookie(cellId, key);
return mCookiePool.remove(0).set(cellId, key);
}
void updateProperty(long cellId, String key, String value) {
SimpleCookie cookie = acquireCookie(cellId, key);
cookie.mValue = value;
ContentValues values = new ContentValues(3);
values.put(HomeContract.Configuration._CELL_ID, cellId);
values.put(HomeContract.Configuration.KEY, key);
values.put(HomeContract.Configuration.VALUE, value);
startInsert(0, cookie, HomeContract.Configuration.CONTENT_URI, values);
}
@Override
protected void onInsertComplete(int token, Object cookie, Uri uri) {
SimpleCookie simpleCookie = (SimpleCookie) cookie;
onPropertyUpdated(simpleCookie.mId, simpleCookie.mKey, simpleCookie.mValue);
simpleCookie.release();
}
@Override
protected void onDeleteComplete(int token, Object cookie, int result) {
SimpleCookie simpleCookie = (SimpleCookie) cookie;
onPropertyDeleted(simpleCookie.mId, simpleCookie.mKey);
simpleCookie.release();
}
class SimpleCookie {
long mId;
String mKey;
String mValue;
public SimpleCookie(long cellId, String key) {
mId = cellId;
mKey = key;
}
void release() {
mValue = null;
mCookiePool.add(this);
}
public SimpleCookie set(long cellId, String key) {
mId = cellId;
mKey = key;
return this;
}
}
}
}