Initial commit

A.k.a. "I should have done this some time ago"
This commit is contained in:
2015-08-05 19:48:41 +02:00
commit 89a5ada48a
60 changed files with 1918 additions and 0 deletions

View File

@ -0,0 +1,68 @@
package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v4.app.NavUtils;
import android.view.MenuItem;
/**
* An activity representing a single Message detail screen. This
* activity is only used on handset devices. On tablet-size devices,
* item details are presented side-by-side with a list of items
* in a {@link MessageListActivity}.
* <p/>
* This activity is mostly just a 'shell' activity containing nothing
* more than a {@link MessageDetailFragment}.
*/
public class MessageDetailActivity extends ActionBarActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message_detail);
// Show the Up button in the action bar.
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// savedInstanceState is non-null when there is fragment state
// saved from previous configurations of this activity
// (e.g. when rotating the screen from portrait to landscape).
// In this case, the fragment will automatically be re-added
// to its container so we don't need to manually add it.
// For more information, see the Fragments API guide at:
//
// http://developer.android.com/guide/components/fragments.html
//
if (savedInstanceState == null) {
// Create the detail fragment and add it to the activity
// using a fragment transaction.
Bundle arguments = new Bundle();
arguments.putString(MessageDetailFragment.ARG_ITEM,
getIntent().getStringExtra(MessageDetailFragment.ARG_ITEM));
MessageDetailFragment fragment = new MessageDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.add(R.id.message_detail_container, fragment)
.commit();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if (id == android.R.id.home) {
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpTo(this, new Intent(this, MessageListActivity.class));
return true;
}
return super.onOptionsItemSelected(item);
}
}

View File

@ -0,0 +1,61 @@
package ch.dissem.apps.abit;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import ch.dissem.bitmessage.entity.Plaintext;
/**
* A fragment representing a single Message detail screen.
* This fragment is either contained in a {@link MessageListActivity}
* in two-pane mode (on tablets) or a {@link MessageDetailActivity}
* on handsets.
*/
public class MessageDetailFragment extends Fragment {
/**
* The fragment argument representing the item ID that this fragment
* represents.
*/
public static final String ARG_ITEM = "item";
/**
* The dummy content this fragment is presenting.
*/
private Plaintext item;
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public MessageDetailFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments().containsKey(ARG_ITEM)) {
// Load the dummy content specified by the fragment
// arguments. In a real-world scenario, use a Loader
// to load content from a content provider.
item = (Plaintext) getArguments().getSerializable(ARG_ITEM);
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_message_detail, container, false);
// Show the dummy content as text in a TextView.
if (item != null) {
((TextView) rootView.findViewById(R.id.message_detail)).setText(item.getText());
}
return rootView;
}
}

View File

@ -0,0 +1,215 @@
package ch.dissem.apps.abit;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.View;
import android.widget.AdapterView;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.IconicsDrawable;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.DrawerBuilder;
import com.mikepenz.materialdrawer.accountswitcher.AccountHeader;
import com.mikepenz.materialdrawer.accountswitcher.AccountHeaderBuilder;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileDrawerItem;
import com.mikepenz.materialdrawer.model.ProfileSettingDrawerItem;
import com.mikepenz.materialdrawer.model.SecondaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IProfile;
import com.mikepenz.materialdrawer.model.interfaces.Nameable;
import java.util.ArrayList;
/**
* An activity representing a list of Messages. This activity
* has different presentations for handset and tablet-size devices. On
* handsets, the activity presents a list of items, which when touched,
* lead to a {@link MessageDetailActivity} representing
* item details. On tablets, the activity presents the list of items and
* item details side-by-side using two vertical panes.
* <p>
* The activity makes heavy use of fragments. The list of items is a
* {@link MessageListFragment} and the item details
* (if present) is a {@link MessageDetailFragment}.
* </p><p>
* This activity also implements the required
* {@link MessageListFragment.Callbacks} interface
* to listen for item selections.
* </p>
*/
public class MessageListActivity extends AppCompatActivity
implements MessageListFragment.Callbacks {
private static final int ADD_IDENTITY = 1;
/**
* Whether or not the activity is in two-pane mode, i.e. running on a tablet
* device.
*/
private boolean twoPane;
private AccountHeader accountHeader;
private BitmessageContext bmc;
private Label selectedLabel;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bmc = Singleton.getBitmessageContext(this);
selectedLabel = bmc.messages().getLabels().get(0);
setContentView(R.layout.activity_message_list);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
if (findViewById(R.id.message_detail_container) != null) {
// The detail container view will be present only in the
// large-screen layouts (res/values-large and
// res/values-sw600dp). If this view is present, then the
// activity should be in two-pane mode.
twoPane = true;
// In two-pane mode, list items should be given the
// 'activated' state when touched.
((MessageListFragment) getSupportFragmentManager()
.findFragmentById(R.id.message_list))
.setActivateOnItemClick(true);
}
createDrawer(toolbar);
// TODO: If exposing deep links into your app, handle intents here.
}
private void createDrawer(Toolbar toolbar) {
final ArrayList<IProfile> profiles = new ArrayList<>();
for (BitmessageAddress identity : bmc.addresses().getIdentities()) {
profiles.add(new ProfileDrawerItem()
.withName(identity.toString())
.withEmail(identity.getAddress())
.withTag(identity)
);
}
profiles.add(new ProfileSettingDrawerItem()
.withName("Add Identity")
.withDescription("Create new identity")
.withIcon(new IconicsDrawable(this, GoogleMaterial.Icon.gmd_add)
.actionBar()
.paddingDp(5)
.colorRes(R.color.icons))
.withIdentifier(ADD_IDENTITY)
);
profiles.add(new ProfileSettingDrawerItem()
.withName("Manage Account")
.withIcon(GoogleMaterial.Icon.gmd_settings)
);
// Create the AccountHeader
accountHeader = new AccountHeaderBuilder()
.withActivity(this)
.withHeaderBackground(R.drawable.header)
.withProfiles(profiles)
.withOnAccountHeaderListener(new AccountHeader.OnAccountHeaderListener() {
@Override
public boolean onProfileChanged(View view, IProfile profile, boolean currentProfile) {
if (profile.getIdentifier() == ADD_IDENTITY) {
BitmessageAddress identity = bmc.createIdentity(false);
IProfile newProfile = new ProfileDrawerItem()
.withName(identity.toString())
.withEmail(identity.getAddress())
.withTag(identity);
if (accountHeader.getProfiles() != null) {
//we know that there are 2 setting elements. set the new profile above them ;)
accountHeader.addProfile(newProfile, accountHeader.getProfiles().size() - 2);
} else {
accountHeader.addProfiles(newProfile);
}
}
// false if it should close the drawer
return false;
}
})
.build();
ArrayList<IDrawerItem> drawerItems = new ArrayList<>();
for (Label label : bmc.messages().getLabels()) {
drawerItems.add(new PrimaryDrawerItem().withName(label.toString()).withTag(label));
}
new DrawerBuilder()
.withActivity(this)
.withToolbar(toolbar)
.withAccountHeader(accountHeader)
.withDrawerItems(drawerItems)
.addStickyDrawerItems(
new SecondaryDrawerItem()
.withName(R.string.settings)
.withIcon(GoogleMaterial.Icon.gmd_settings)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public boolean onItemClick(AdapterView<?> adapterView, View view, int i, long l, IDrawerItem item) {
if (item.getTag() instanceof Label) {
selectedLabel = (Label) item.getTag();
} else if (item instanceof Nameable<?>) {
Nameable<?> ni = (Nameable<?>) item;
switch (ni.getNameRes()) {
case R.string.settings:
startActivity(new Intent(MessageListActivity.this, SettingsActivity.class));
break;
}
}
return false;
}
})
.build();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
boolean running = bmc.isRunning();
menu.findItem(R.id.sync_enabled).setVisible(running);
menu.findItem(R.id.sync_disabled).setVisible(!running);
return true;
}
/**
* Callback method from {@link MessageListFragment.Callbacks}
* indicating that the item with the given ID was selected.
*/
@Override
public void onItemSelected(Plaintext plaintext) {
if (twoPane) {
// In two-pane mode, show the detail view in this activity by
// adding or replacing the detail fragment using a
// fragment transaction.
Bundle arguments = new Bundle();
arguments.putSerializable(MessageDetailFragment.ARG_ITEM, plaintext);
MessageDetailFragment fragment = new MessageDetailFragment();
fragment.setArguments(arguments);
getSupportFragmentManager().beginTransaction()
.replace(R.id.message_detail_container, fragment)
.commit();
} else {
// In single-pane mode, simply start the detail activity
// for the selected item ID.
Intent detailIntent = new Intent(this, MessageDetailActivity.class);
detailIntent.putExtra(MessageDetailFragment.ARG_ITEM, plaintext);
startActivity(detailIntent);
}
}
public Label getSelectedLabel() {
return selectedLabel;
}
}

View File

@ -0,0 +1,177 @@
package ch.dissem.apps.abit;
import android.app.Activity;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.ListFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import ch.dissem.apps.abit.service.Singleton;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.Plaintext;
/**
* A list fragment representing a list of Messages. This fragment
* also supports tablet devices by allowing list items to be given an
* 'activated' state upon selection. This helps indicate which item is
* currently being viewed in a {@link MessageDetailFragment}.
* <p/>
* Activities containing this fragment MUST implement the {@link Callbacks}
* interface.
*/
public class MessageListFragment extends ListFragment {
/**
* The serialization (saved instance state) Bundle key representing the
* activated item position. Only used on tablets.
*/
private static final String STATE_ACTIVATED_POSITION = "activated_position";
/**
* The fragment's current callback object, which is notified of list item
* clicks.
*/
private Callbacks mCallbacks = sDummyCallbacks;
/**
* The current activated item position. Only used on tablets.
*/
private int mActivatedPosition = ListView.INVALID_POSITION;
private BitmessageContext bmc;
/**
* A callback interface that all activities containing this fragment must
* implement. This mechanism allows activities to be notified of item
* selections.
*/
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
void onItemSelected(Plaintext plaintext);
}
/**
* A dummy implementation of the {@link Callbacks} interface that does
* nothing. Used only when this fragment is not attached to an activity.
*/
private static Callbacks sDummyCallbacks = new Callbacks() {
@Override
public void onItemSelected(Plaintext plaintext) {
}
};
/**
* Mandatory empty constructor for the fragment manager to instantiate the
* fragment (e.g. upon screen orientation changes).
*/
public MessageListFragment() {
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
bmc = Singleton.getBitmessageContext(getActivity());
// TODO: replace with a real list adapter.
setListAdapter(new ArrayAdapter<>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
bmc.messages().findMessages(((MessageListActivity) getActivity()).getSelectedLabel())));
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_message_list, container, false);
// Show the dummy content as text in a TextView.
FloatingActionButton fab = (FloatingActionButton) rootView.findViewById(R.id.fab_compose_message);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MessageListFragment.this.getActivity(), "TODO", Toast.LENGTH_SHORT).show();
}
});
return rootView;
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// Restore the previously serialized activated item position.
if (savedInstanceState != null
&& savedInstanceState.containsKey(STATE_ACTIVATED_POSITION)) {
setActivatedPosition(savedInstanceState.getInt(STATE_ACTIVATED_POSITION));
}
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Activities containing this fragment must implement its callbacks.
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException("Activity must implement fragment's callbacks.");
}
mCallbacks = (Callbacks) activity;
}
@Override
public void onDetach() {
super.onDetach();
// Reset the active callbacks interface to the dummy implementation.
mCallbacks = sDummyCallbacks;
}
@Override
public void onListItemClick(ListView listView, View view, int position, long id) {
super.onListItemClick(listView, view, position, id);
// Notify the active callbacks interface (the activity, if the
// fragment is attached to one) that an item has been selected.
mCallbacks.onItemSelected((Plaintext) listView.getItemAtPosition(position));
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mActivatedPosition != ListView.INVALID_POSITION) {
// Serialize and persist the activated item position.
outState.putInt(STATE_ACTIVATED_POSITION, mActivatedPosition);
}
}
/**
* Turns on activate-on-click mode. When this mode is on, list items will be
* given the 'activated' state when touched.
*/
public void setActivateOnItemClick(boolean activateOnItemClick) {
// When setting CHOICE_MODE_SINGLE, ListView will automatically
// give items the 'activated' state when touched.
getListView().setChoiceMode(activateOnItemClick
? ListView.CHOICE_MODE_SINGLE
: ListView.CHOICE_MODE_NONE);
}
private void setActivatedPosition(int position) {
if (position == ListView.INVALID_POSITION) {
getListView().setItemChecked(mActivatedPosition, false);
} else {
getListView().setItemChecked(position, true);
}
mActivatedPosition = position;
}
}

View File

@ -0,0 +1,37 @@
package ch.dissem.apps.abit;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.os.Environment;
import ch.dissem.bitmessage.repository.JdbcConfig;
import org.flywaydb.core.api.android.ContextHolder;
import org.sqldroid.SQLDroidDriver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;
/**
* Created by chris on 14.07.15.
*/
public class SQLiteConfig extends JdbcConfig {
public SQLiteConfig(Context ctx) {
super(getDbUrl(ctx), "", "");
}
private static String getDbUrl(Context ctx) {
SQLiteDatabase db = ctx.openOrCreateDatabase(Environment.getExternalStorageDirectory()
+ "/jabit.db", Context.MODE_PRIVATE, null);
ContextHolder.setContext(ctx);
return "jdbc:sqlite:" + db.getPath() + "?timeout=5";
}
@Override
public Connection getConnection() throws SQLException {
Properties removeLocale = new Properties();
removeLocale.put(SQLDroidDriver.ADDITONAL_DATABASE_FLAGS, android.database.sqlite.SQLiteDatabase.NO_LOCALIZED_COLLATORS);
return DriverManager.getConnection(dbUrl, removeLocale);
}
}

View File

@ -0,0 +1,27 @@
package ch.dissem.apps.abit;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
/**
* Created by chris on 14.07.15.
*/
public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.toolbar_layout);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(false);
// Display the fragment as the main content.
getFragmentManager().beginTransaction()
.replace(R.id.content, new SettingsFragment())
.commit();
}
}

View File

@ -0,0 +1,18 @@
package ch.dissem.apps.abit;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceFragment;
/**
* Created by chris on 14.07.15.
*/
public class SettingsFragment extends PreferenceFragment {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
}
}

View File

@ -0,0 +1,104 @@
package ch.dissem.apps.abit.service;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.support.v7.app.NotificationCompat;
import ch.dissem.apps.abit.R;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.entity.BitmessageAddress;
import ch.dissem.bitmessage.entity.Plaintext;
import ch.dissem.bitmessage.entity.valueobject.Label;
import java.util.List;
public class BitmessageService extends Service {
private static BitmessageContext ctx;
private ServiceBinder binder = new ServiceBinder();
private NotificationCompat.Builder ongoingNotificationBuilder = new NotificationCompat.Builder(this);
private NotificationManager notifyManager;
public BitmessageService() {
if (ctx == null) {
ctx = Singleton.getBitmessageContext(this);
}
}
@Override
public void onCreate() {
super.onCreate();
ongoingNotificationBuilder.setOngoing(true);
ongoingNotificationBuilder.setContentTitle(getString(R.string.bitmessage_active));
ongoingNotificationBuilder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
// ongoingNotificationBuilder.setSmallIcon(R.drawable.ic_bitmessage);
notifyManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
}
public void startService() {
if (!ctx.isRunning()) {
notifyManager.notify(0, ongoingNotificationBuilder.build());
ctx.startup(new BitmessageContext.Listener() {
@Override
public void receive(Plaintext plaintext) {
Notification notification = new NotificationCompat.Builder(BitmessageService.this)
.setContentTitle(plaintext.getSubject())
.setContentText(plaintext.getText())
.setVisibility(NotificationCompat.VISIBILITY_PRIVATE)
.setPriority(plaintext.getType() == Plaintext.Type.BROADCAST
? NotificationCompat.PRIORITY_DEFAULT
: NotificationCompat.PRIORITY_HIGH)
.build();
notifyManager.notify(plaintext.getInventoryVector().hashCode(), notification);
}
});
}
}
public void stopService() {
ctx.shutdown();
notifyManager.cancel(0);
}
public List<BitmessageAddress> getIdentities() {
return ctx.addresses().getIdentities();
}
public List<BitmessageAddress> getContacts() {
return ctx.addresses().getContacts();
}
public List<Plaintext> getMessages(Label label) {
return ctx.messages().findMessages(label);
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
public class ServiceBinder extends Binder {
public BitmessageService getService() {
return BitmessageService.this;
}
}
public enum NetworkChoice {
/**
* A full node, receiving and relaying objects all the time.
*/
FULL,
/**
* Connect to a trusted node from time to time to get all new objects and disconnect afterwards
* (see {@link android.content.AbstractThreadedSyncAdapter})
*/
TRUSTED,
/**
* Offline
*/
NONE
}
}

View File

@ -0,0 +1,34 @@
package ch.dissem.apps.abit.service;
import android.content.Context;
import ch.dissem.apps.abit.SQLiteConfig;
import ch.dissem.bitmessage.BitmessageContext;
import ch.dissem.bitmessage.networking.DefaultNetworkHandler;
import ch.dissem.bitmessage.repository.*;
import ch.dissem.bitmessage.security.sc.SpongySecurity;
/**
* Created by chris on 16.07.15.
*/
public class Singleton {
private static BitmessageContext bitmessageContext;
public static BitmessageContext getBitmessageContext(Context ctx) {
if (bitmessageContext == null) {
synchronized (Singleton.class) {
if (bitmessageContext == null) {
JdbcConfig config = new SQLiteConfig(ctx);
bitmessageContext = new BitmessageContext.Builder()
.security(new SpongySecurity())
.nodeRegistry(new MemoryNodeRegistry())
.inventory(new JdbcInventory(config))
.addressRepo(new JdbcAddressRepository(config))
.messageRepo(new JdbcMessageRepository(config))
.networkHandler(new DefaultNetworkHandler())
.build();
}
}
}
return bitmessageContext;
}
}

View File

@ -0,0 +1,213 @@
package im.delight.android.identicons;
/**
* Copyright 2014 www.delight.im <info@delight.im>
*
* 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.
*/
import java.security.MessageDigest;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
abstract public class Identicon extends View {
private static final String HASH_ALGORITHM = "SHA-256";
private final int mRowCount;
private final int mColumnCount;
private final Paint mPaint;
private volatile int mCellWidth;
private volatile int mCellHeight;
private volatile byte[] mHash;
private volatile int[][] mColors;
private volatile boolean mReady;
public Identicon(Context context) {
super(context);
mRowCount = getRowCount();
mColumnCount = getColumnCount();
mPaint = new Paint();
init();
}
public Identicon(Context context, AttributeSet attrs) {
super(context, attrs);
mRowCount = getRowCount();
mColumnCount = getColumnCount();
mPaint = new Paint();
init();
}
public Identicon(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mRowCount = getRowCount();
mColumnCount = getColumnCount();
mPaint = new Paint();
init();
}
@SuppressLint("NewApi")
protected void init() {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setAntiAlias(true);
mPaint.setDither(true);
setWillNotDraw(false);
if (Build.VERSION.SDK_INT >= 11) {
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
}
public void show(String input) {
// if the input was null
if (input == null) {
// we can't create a hash value and have nothing to show (draw to the view)
mHash = null;
}
// if the input was a proper string (non-null)
else {
// generate a hash from the string to get unique but deterministic byte values
try {
final MessageDigest digest = java.security.MessageDigest.getInstance(HASH_ALGORITHM);
digest.update(input == null ? new byte[0] : input.getBytes());
mHash = digest.digest();
}
catch (Exception e) {
mHash = null;
}
}
// set up the cell colors according to the input that was provided via show(...)
setupColors();
// this view may now be drawn (and thus must be re-drawn)
mReady = true;
invalidate();
}
public void show(int input) {
show(String.valueOf(input));
}
public void show(long input) {
show(String.valueOf(input));
}
public void show(float input) {
show(String.valueOf(input));
}
public void show(double input) {
show(String.valueOf(input));
}
public void show(byte input) {
show(String.valueOf(input));
}
public void show(char input) {
show(String.valueOf(input));
}
public void show(boolean input) {
show(String.valueOf(input));
}
public void show(Object input) {
if (input == null) {
mHash = null;
}
else {
show(String.valueOf(input));
}
}
protected void setupColors() {
mColors = new int[mRowCount][mColumnCount];
int colorVisible = getIconColor();
for (int r = 0; r < mRowCount; r++) {
for (int c = 0; c < mColumnCount; c++) {
if (isCellVisible(r, c)) {
mColors[r][c] = colorVisible;
}
else {
mColors[r][c] = Color.TRANSPARENT;
}
}
}
}
protected byte getByte(int index) {
if (mHash == null) {
return -128;
}
else {
return mHash[index % mHash.length];
}
}
abstract protected int getRowCount();
abstract protected int getColumnCount();
abstract protected boolean isCellVisible(int row, int column);
abstract protected int getIconColor();
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mCellWidth = w / mColumnCount;
mCellHeight = h / mRowCount;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int size = Math.min(getMeasuredWidth(), getMeasuredHeight());
setMeasuredDimension(size, size);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mReady) {
int x, y;
for (int r = 0; r < mRowCount; r++) {
for (int c = 0; c < mColumnCount; c++) {
x = mCellWidth * c;
y = mCellHeight * r;
mPaint.setColor(mColors[r][c]);
canvas.drawRect(x, y + mCellHeight, x + mCellWidth, y, mPaint);
}
}
}
}
}