diff --git a/briar-android/build.gradle b/briar-android/build.gradle index f982a86bec0ad01b5297ae2201d0324449c40e7c..4ead68e29f986ba6a82f592616ace455699b2f7b 100644 --- a/briar-android/build.gradle +++ b/briar-android/build.gradle @@ -17,6 +17,7 @@ dependencies { compile "org.roboguice:roboguice:2.0" compile "info.guardianproject.panic:panic:0.5" compile "info.guardianproject.trustedintents:trustedintents:0.2" + compile "de.hdodenhof:circleimageview:2.0.0" } dependencyVerification { diff --git a/briar-android/res/layout/author_view.xml b/briar-android/res/layout/author_view.xml new file mode 100644 index 0000000000000000000000000000000000000000..92c863c374784ace09c8c75ef5bb64c18d05e197 --- /dev/null +++ b/briar-android/res/layout/author_view.xml @@ -0,0 +1,42 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarView" + android:layout_width="@dimen/listitem_picture_size" + android:layout_height="@dimen/listitem_picture_size" + android:layout_centerVertical="true" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin"/> + + <ImageView + android:id="@+id/statusView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:src="@drawable/identity_anonymous"/> + + <TextView + android:id="@+id/nameView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginEnd="@dimen/margin_small" + android:layout_marginLeft="@dimen/listitem_text_left_margin" + android:layout_marginRight="@dimen/margin_small" + android:layout_marginStart="@dimen/listitem_text_left_margin" + android:layout_toLeftOf="@id/statusView" + android:layout_toStartOf="@id/statusView" + android:ellipsize="end" + android:singleLine="true" + android:text="@string/anonymous" + android:textSize="@dimen/text_size_medium"/> + +</RelativeLayout> \ No newline at end of file diff --git a/briar-android/res/layout/dropdown_author.xml b/briar-android/res/layout/dropdown_author.xml new file mode 100644 index 0000000000000000000000000000000000000000..01a9b3f5330f363b672becbc15ce8b1cd3e90d5c --- /dev/null +++ b/briar-android/res/layout/dropdown_author.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_vertical"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarView" + android:layout_width="@dimen/dropdown_picture_size" + android:layout_height="@dimen/dropdown_picture_size"/> + + <TextView + android:id="@+id/nameView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginLeft="@dimen/margin_medium" + android:layout_marginStart="@dimen/margin_medium" + android:ellipsize="end" + android:singleLine="true" + android:textSize="@dimen/text_size_medium" + tools:text="This is a name of an author. It can be quite long."/> + +</LinearLayout> \ No newline at end of file diff --git a/briar-android/res/layout/list_item_contact.xml b/briar-android/res/layout/list_item_contact.xml index 69ec0383e31bda3ec65b01e7fea2faab620448b2..8259e1b3b4011a16bc656ce89fa30de922c0a415 100644 --- a/briar-android/res/layout/list_item_contact.xml +++ b/briar-android/res/layout/list_item_contact.xml @@ -2,49 +2,71 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - android:orientation="vertical" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:orientation="vertical"> - <LinearLayout - android:orientation="horizontal" + <RelativeLayout android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="?attr/selectableItemBackground" - android:padding="12dp"> - - <ImageView - android:id="@+id/bulbView" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginRight="@dimen/margin_medium" - android:layout_marginEnd="@dimen/margin_medium" - android:layout_gravity="center_vertical" - tools:src="@drawable/contact_disconnected"/> - - <TextView - android:id="@+id/nameView" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_weight="1" - android:layout_gravity="center_vertical" - android:layout_marginRight="@dimen/margin_small" - android:layout_marginEnd="@dimen/margin_small" - android:textSize="@dimen/text_size_medium" - android:gravity="center_vertical" - tools:text="This is a name of a contact. It can be quite long."/> - - <TextView - android:id="@+id/dateView" - android:layout_width="wrap_content" - android:layout_height="match_parent" - android:layout_marginRight="@dimen/margin_small" - android:layout_marginEnd="@dimen/margin_small" - android:gravity="center_vertical" - android:textColor="@color/no_private_messages" - tools:text="Dec 24"/> - - </LinearLayout> + android:layout_height="@dimen/listitem_height_one_line_avatar" + android:background="?attr/selectableItemBackground"> + + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/avatarView" + android:layout_width="@dimen/listitem_picture_size" + android:layout_height="@dimen/listitem_picture_size" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin"/> + + <LinearLayout + android:id="@+id/bulbHolder" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:layout_centerVertical="true" + android:layout_marginEnd="@dimen/listitem_horizontal_margin" + android:layout_marginRight="@dimen/listitem_horizontal_margin" + android:gravity="right" + android:orientation="vertical"> + + <ImageView + android:id="@+id/bulbView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + tools:src="@drawable/contact_disconnected"/> + + <TextView + android:id="@+id/dateView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:textColor="@color/no_private_messages" + tools:text="Dec 24"/> + + </LinearLayout> + + <TextView + android:id="@+id/nameView" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_centerVertical="true" + android:layout_marginEnd="@dimen/margin_small" + android:layout_marginLeft="@dimen/listitem_text_left_margin" + android:layout_marginRight="@dimen/margin_small" + android:layout_marginStart="@dimen/listitem_text_left_margin" + android:layout_toLeftOf="@id/bulbHolder" + android:layout_toStartOf="@id/bulbHolder" + android:gravity="center_vertical" + android:maxLines="2" + android:textSize="@dimen/text_size_medium" + tools:text="This is a name of a contact. It can be quite long."/> + + </RelativeLayout> <View style="@style/Divider.Horizontal"/> diff --git a/briar-android/res/layout/list_item_msg_in.xml b/briar-android/res/layout/list_item_msg_in.xml index d93d269e7796bbbc767bc020dd647888b9b1927c..50ccd007720f43d5f23d90dd96c22f112f90fe3b 100644 --- a/briar-android/res/layout/list_item_msg_in.xml +++ b/briar-android/res/layout/list_item_msg_in.xml @@ -4,12 +4,20 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="vertical" + android:orientation="horizontal" android:paddingRight="@dimen/margin_medium" android:paddingEnd="@dimen/margin_medium" android:paddingTop="@dimen/margin_small" android:paddingBottom="@dimen/margin_small"> + <de.hdodenhof.circleimageview.CircleImageView + android:id="@+id/msgAvatar" + android:layout_width="@dimen/listitem_picture_size" + android:layout_height="@dimen/listitem_picture_size" + android:layout_marginLeft="@dimen/listitem_horizontal_margin" + android:layout_marginStart="@dimen/listitem_horizontal_margin" + /> + <RelativeLayout android:id="@+id/msgLayout" android:layout_width="wrap_content" diff --git a/briar-android/res/values/dimens.xml b/briar-android/res/values/dimens.xml index e417b0897aa47a6ad3824c2cfdd7e7c9bd74d19a..8c11314aba15cb52a0573d074eafbb1b55ab4d63 100644 --- a/briar-android/res/values/dimens.xml +++ b/briar-android/res/values/dimens.xml @@ -20,4 +20,12 @@ <dimen name="nav_drawer_width">300dp</dimen> <dimen name="nav_seperator_height">1dp</dimen> + <dimen name="listitem_horizontal_margin">16dp</dimen> + <dimen name="listitem_picture_size">40dp</dimen> + <dimen name="listitem_text_left_margin">72dp</dimen> + + <dimen name="listitem_height_one_line_avatar">56dp</dimen> + + <dimen name="dropdown_picture_size">32dp</dimen> + </resources> diff --git a/briar-android/src/im/delight/android/identicons/AsymmetricIdenticon.java b/briar-android/src/im/delight/android/identicons/AsymmetricIdenticon.java new file mode 100644 index 0000000000000000000000000000000000000000..b1d98d9b35aad0f9294afcb912a07b174510fff8 --- /dev/null +++ b/briar-android/src/im/delight/android/identicons/AsymmetricIdenticon.java @@ -0,0 +1,84 @@ +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 android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; + +import org.briarproject.api.crypto.CryptoComponent; + +import javax.inject.Inject; + +import roboguice.RoboGuice; + +public class AsymmetricIdenticon extends IdenticonView { + + @Inject private CryptoComponent mCrypto; + private IdenticonBase mDelegate; + + public AsymmetricIdenticon(Context context) { + super(context); + initDelegate(); + } + + public AsymmetricIdenticon(Context context, AttributeSet attrs) { + super(context, attrs); + initDelegate(); + } + + public AsymmetricIdenticon(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initDelegate(); + } + + @Override + protected IdenticonBase getDelegate() { + return mDelegate; + } + + private void initDelegate() { + RoboGuice.injectMembers(getContext(), this); + mDelegate = new IdenticonBase() { + @Override + protected CryptoComponent getCrypto() { + return mCrypto; + } + + @Override + protected int getRowCount() { + return 4; + } + + @Override + protected int getColumnCount() { + return 4; + } + + @Override + protected boolean isCellVisible(int row, int column) { + return getByte(3 + row * getColumnCount() + column) >= 0; + } + + @Override + protected int getIconColor() { + return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128); + } + }; + } + +} diff --git a/briar-android/src/im/delight/android/identicons/IdenticonBase.java b/briar-android/src/im/delight/android/identicons/IdenticonBase.java new file mode 100644 index 0000000000000000000000000000000000000000..f5ccc90e4456f458ef07edbb2f4319efb8f16374 --- /dev/null +++ b/briar-android/src/im/delight/android/identicons/IdenticonBase.java @@ -0,0 +1,127 @@ +package im.delight.android.identicons; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; + +import org.briarproject.api.crypto.CryptoComponent; + +/** + * Created by saiimons on 05/10/14. + */ +public abstract class IdenticonBase { + private final CryptoComponent mCrypto; + 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 IdenticonBase() { + mCrypto = getCrypto(); + mRowCount = getRowCount(); + mColumnCount = getColumnCount(); + mPaint = new Paint(); + + mPaint.setStyle(Paint.Style.FILL); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + } + + public byte[] getHash(byte[] input) { + byte[] mHash; + // 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; + } else { + // generate a hash from the input to get unique but deterministic byte values + try { + mHash = mCrypto.hash(input); + } catch (Exception e) { + mHash = null; + } + } + return mHash; + } + + protected void setupColors() { + mColors = new int[mRowCount][mColumnCount]; + int colorVisible = getIconColor(); + int colorInvisible = getBackgroundColor(); + + 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] = colorInvisible; + } + } + } + } + + public void show(byte[] input) { + if(input != null) { + mHash = getHash(input); + } else { + 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; + } + + public byte getByte(int index) { + if (mHash == null) { + return -128; + } else { + return mHash[index % mHash.length]; + } + } + + abstract protected CryptoComponent getCrypto(); + + abstract protected int getRowCount(); + + abstract protected int getColumnCount(); + + abstract protected boolean isCellVisible(int row, int column); + + abstract protected int getIconColor(); + + protected int getBackgroundColor() { + float[] hsv = new float[3]; + Color.colorToHSV(getIconColor(), hsv); + if (hsv[2] < 0.5) + return Color.parseColor("#ffeeeeee"); // @color/background_material_light + else + return Color.parseColor("#ff303030"); // @color/background_material_dark + } + + public void updateSize(int w, int h) { + mCellWidth = w / mColumnCount; + mCellHeight = h / mRowCount; + } + + protected void draw(Canvas 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); + } + } + } + } +} diff --git a/briar-android/src/im/delight/android/identicons/IdenticonDrawable.java b/briar-android/src/im/delight/android/identicons/IdenticonDrawable.java new file mode 100644 index 0000000000000000000000000000000000000000..6c78367f9e8fa45f0bbe9f7b4d2fd6dc2b67fa21 --- /dev/null +++ b/briar-android/src/im/delight/android/identicons/IdenticonDrawable.java @@ -0,0 +1,102 @@ +package im.delight.android.identicons; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ColorFilter; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.util.Log; + +import org.briarproject.api.crypto.CryptoComponent; + +/** + * Created by saiimons on 05/10/14. + */ +public class IdenticonDrawable extends Drawable { + private IdenticonBase mDelegate; + + private static final int CENTER_COLUMN_INDEX = 5; + + public IdenticonDrawable(final CryptoComponent crypto, byte[] toShow) { + super(); + mDelegate = new IdenticonBase() { + @Override + protected CryptoComponent getCrypto() { + return crypto; + } + + @Override + protected int getRowCount() { + return 9; + } + + @Override + protected int getColumnCount() { + return 9; + } + + @Override + protected boolean isCellVisible(int row, int column) { + return getByte(3 + row * CENTER_COLUMN_INDEX + getSymmetricColumnIndex(column)) >= 0; + } + + @Override + protected int getIconColor() { + return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128); + } + }; + mDelegate.show(toShow); + } + + @Override + public int getIntrinsicHeight() { + return 200; + } + + @Override + public int getIntrinsicWidth() { + return 200; + } + + @Override + public void setBounds(Rect bounds) { + super.setBounds(bounds); + Log.d("IDENTICON", "SIZE : " + (bounds.right - bounds.left) + " " + (bounds.bottom - bounds.top)); + mDelegate.updateSize(bounds.right - bounds.left, bounds.bottom - bounds.top); + } + + @Override + public void setBounds(int left, int top, int right, int bottom) { + super.setBounds(left, top, right, bottom); + mDelegate.updateSize(right - left, bottom - top); + } + + @Override + public void draw(Canvas canvas) { + Log.d("IDENTICON", "DRAW IN PROGRESS"); + mDelegate.draw(canvas); + } + + @Override + public void setAlpha(int alpha) { + + } + + @Override + public void setColorFilter(ColorFilter cf) { + + } + + @Override + public int getOpacity() { + return 0; + } + + protected int getSymmetricColumnIndex(int row) { + if (row < CENTER_COLUMN_INDEX) { + return row; + } else { + return mDelegate.getColumnCount() - row - 1; + } + } +} diff --git a/briar-android/src/im/delight/android/identicons/IdenticonView.java b/briar-android/src/im/delight/android/identicons/IdenticonView.java new file mode 100644 index 0000000000000000000000000000000000000000..32a4f863c05b4fabcfbea60041342aa9c6da9195 --- /dev/null +++ b/briar-android/src/im/delight/android/identicons/IdenticonView.java @@ -0,0 +1,122 @@ +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 android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Canvas; +import android.os.Build; +import android.util.AttributeSet; +import android.view.View; + +abstract public class IdenticonView extends View { + + + public IdenticonView(Context context) { + super(context); + init(); + } + + public IdenticonView(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public IdenticonView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @SuppressLint("NewApi") + protected void init() { + setWillNotDraw(false); + if (Build.VERSION.SDK_INT >= 11) { + setLayerType(View.LAYER_TYPE_SOFTWARE, null); + } + } + + public void show(byte[] input) { + getDelegate().show(input); + invalidate(); + } + + public void show(String input) { + show(input.getBytes()); + } + + 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(new byte[] { 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) { + getDelegate().show(null); + } else { + show(String.valueOf(input)); + } + } + + protected byte getByte(int index) { + return getDelegate().getByte(index); + } + + abstract protected IdenticonBase getDelegate(); + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + getDelegate().updateSize(w, h); + } + + @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); + getDelegate().draw(canvas); + } + +} diff --git a/briar-android/src/im/delight/android/identicons/SymmetricIdenticon.java b/briar-android/src/im/delight/android/identicons/SymmetricIdenticon.java new file mode 100644 index 0000000000000000000000000000000000000000..f102e45f4bdef4f385c5bb12b23c27d6415c1e06 --- /dev/null +++ b/briar-android/src/im/delight/android/identicons/SymmetricIdenticon.java @@ -0,0 +1,94 @@ +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 android.content.Context; +import android.graphics.Color; +import android.util.AttributeSet; + +import org.briarproject.api.crypto.CryptoComponent; + +import javax.inject.Inject; + +import roboguice.RoboGuice; + +public class SymmetricIdenticon extends IdenticonView { + + private static final int CENTER_COLUMN_INDEX = 5; + + @Inject private CryptoComponent mCrypto; + private IdenticonBase mDelegate; + + public SymmetricIdenticon(Context context) { + super(context); + initDelegate(); + } + + public SymmetricIdenticon(Context context, AttributeSet attrs) { + super(context, attrs); + initDelegate(); + } + + public SymmetricIdenticon(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initDelegate(); + } + + private void initDelegate() { + RoboGuice.injectMembers(getContext(), this); + mDelegate = new IdenticonBase() { + @Override + protected CryptoComponent getCrypto() { + return mCrypto; + } + + @Override + protected int getRowCount() { + return 9; + } + + @Override + protected int getColumnCount() { + return 9; + } + + @Override + protected boolean isCellVisible(int row, int column) { + return getByte(3 + row * CENTER_COLUMN_INDEX + getSymmetricColumnIndex(column)) >= 0; + } + + @Override + protected int getIconColor() { + return Color.rgb(getByte(0) + 128, getByte(1) + 128, getByte(2) + 128); + } + }; + } + + @Override + protected IdenticonBase getDelegate() { + return mDelegate; + } + + protected int getSymmetricColumnIndex(int row) { + if (row < CENTER_COLUMN_INDEX) { + return row; + } else { + return getDelegate().getColumnCount() - row - 1; + } + } + +} diff --git a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java index 9d040e87d55380621a75ef665676ca92a0809c57..739912d95343ed2c32afd572df539286a772bed6 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListAdapter.java @@ -14,10 +14,14 @@ import android.widget.TextView; import org.briarproject.R; import org.briarproject.api.contact.ContactId; +import org.briarproject.api.crypto.CryptoComponent; +import org.briarproject.api.identity.Author; import org.briarproject.api.sync.GroupId; import java.util.List; +import im.delight.android.identicons.IdenticonDrawable; + import static android.support.v7.util.SortedList.INVALID_POSITION; public class ContactListAdapter @@ -83,9 +87,11 @@ public class ContactListAdapter } }); private Context ctx; + private CryptoComponent crypto; - public ContactListAdapter(Context context) { + public ContactListAdapter(Context context, CryptoComponent cryptoComponent) { ctx = context; + crypto = cryptoComponent; } @Override @@ -113,7 +119,10 @@ public class ContactListAdapter ui.bulb.setImageResource(R.drawable.contact_disconnected); } - String contactName = item.getContact().getAuthor().getName(); + Author author = item.getContact().getAuthor(); + ui.avatar.setImageDrawable( + new IdenticonDrawable(crypto, author.getId().getBytes())); + String contactName = author.getName(); if (unread > 0) { ui.name.setText(contactName + " (" + unread + ")"); } else { @@ -193,6 +202,7 @@ public class ContactListAdapter public static class ContactHolder extends RecyclerView.ViewHolder { public ViewGroup layout; public ImageView bulb; + public ImageView avatar; public TextView name; public TextView date; @@ -201,6 +211,7 @@ public class ContactListAdapter layout = (ViewGroup) v; bulb = (ImageView) v.findViewById(R.id.bulbView); + avatar = (ImageView) v.findViewById(R.id.avatarView); name = (TextView) v.findViewById(R.id.nameView); date = (TextView) v.findViewById(R.id.dateView); } diff --git a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java index d2bff01e582b45a40cb0c2bee5adbc1c897c9db8..bd52792c5b7d0b427b073d69d9ddf9b7dfd621fe 100644 --- a/briar-android/src/org/briarproject/android/contact/ContactListFragment.java +++ b/briar-android/src/org/briarproject/android/contact/ContactListFragment.java @@ -16,6 +16,7 @@ import org.briarproject.android.util.BriarRecyclerView; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.event.ContactAddedEvent; @@ -62,6 +63,8 @@ public class ContactListFragment extends BaseEventFragment { return TAG; } + @Inject + private CryptoComponent crypto; @Inject private ConnectionRegistry connectionRegistry; private ContactListAdapter adapter = null; @@ -83,7 +86,7 @@ public class ContactListFragment extends BaseEventFragment { inflater.inflate(R.layout.activity_contact_list, container, false); - adapter = new ContactListAdapter(getContext()); + adapter = new ContactListAdapter(getContext(), crypto); list = (BriarRecyclerView) contentView.findViewById(R.id.contactList); list.setLayoutManager(new LinearLayoutManager(getContext())); list.setAdapter(adapter); diff --git a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java index 6b96389c8c8fbac2f62353b5c6c219fa5d618a4e..dc96bd47fb7e6bb51d1dfb480ff2b25a53df0e21 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationActivity.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationActivity.java @@ -23,6 +23,7 @@ import org.briarproject.api.android.AndroidNotificationManager; import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactManager; +import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.crypto.CryptoExecutor; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchContactException; @@ -71,6 +72,7 @@ public class ConversationActivity extends BriarActivity private static final Logger LOG = Logger.getLogger(ConversationActivity.class.getName()); + @Inject private CryptoComponent crypto; @Inject private AndroidNotificationManager notificationManager; @Inject private ConnectionRegistry connectionRegistry; @Inject @CryptoExecutor private Executor cryptoExecutor; @@ -88,6 +90,7 @@ public class ConversationActivity extends BriarActivity private volatile GroupId groupId = null; private volatile ContactId contactId = null; private volatile String contactName = null; + private volatile byte[] contactIdenticonKey = null; private volatile boolean connected = false; @Override @@ -101,7 +104,7 @@ public class ConversationActivity extends BriarActivity setContentView(R.layout.activity_conversation); - adapter = new ConversationAdapter(this); + adapter = new ConversationAdapter(this, crypto); list = (BriarRecyclerView) findViewById(R.id.conversationView); list.setLayoutManager(new LinearLayoutManager(this)); list.setAdapter(adapter); @@ -165,6 +168,7 @@ public class ConversationActivity extends BriarActivity contactId = messagingManager.getContactId(groupId); Contact contact = contactManager.getContact(contactId); contactName = contact.getAuthor().getName(); + contactIdenticonKey = contact.getAuthor().getId().getBytes(); connected = connectionRegistry.isConnected(contactId); long duration = System.currentTimeMillis() - now; if (LOG.isLoggable(INFO)) @@ -192,6 +196,7 @@ public class ConversationActivity extends BriarActivity actionBar.setSubtitle(getString(R.string.offline)); } } + adapter.setIdenticonKey(contactIdenticonKey); } }); } diff --git a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java index 09509c0ee4cfd411672bea00da246de2a3598d4c..a35db98bdd96863a16a752b4d87fdd7dd804c329 100644 --- a/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java +++ b/briar-android/src/org/briarproject/android/contact/ConversationAdapter.java @@ -11,9 +11,12 @@ import android.widget.ImageView; import android.widget.TextView; import org.briarproject.R; +import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.util.StringUtils; +import im.delight.android.identicons.IdenticonDrawable; + import static android.support.v7.util.SortedList.INVALID_POSITION; class ConversationAdapter extends @@ -70,9 +73,17 @@ class ConversationAdapter extends } }); private Context ctx; + private CryptoComponent crypto; + private byte[] identiconKey; - public ConversationAdapter(Context context) { + public ConversationAdapter(Context context, CryptoComponent cryptoComponent) { ctx = context; + crypto = cryptoComponent; + } + + public void setIdenticonKey(byte[] key) { + this.identiconKey = key; + notifyDataSetChanged(); } @Override @@ -119,18 +130,23 @@ class ConversationAdapter extends } else { ui.status.setImageResource(R.drawable.message_stored); } - } else if (!header.isRead()) { - int left = ui.layout.getPaddingLeft(); - int top = ui.layout.getPaddingTop(); - int right = ui.layout.getPaddingRight(); - int bottom = ui.layout.getPaddingBottom(); - - // show unread messages in different color to not miss them - ui.layout.setBackgroundResource(R.drawable.msg_in_unread); - - // re-apply the previous padding due to bug in some Android versions - // see: https://code.google.com/p/android/issues/detail?id=17885 - ui.layout.setPadding(left, top, right, bottom); + } else { + if (identiconKey != null) + ui.avatar.setImageDrawable( + new IdenticonDrawable(crypto, identiconKey)); + if (!header.isRead()) { + int left = ui.layout.getPaddingLeft(); + int top = ui.layout.getPaddingTop(); + int right = ui.layout.getPaddingRight(); + int bottom = ui.layout.getPaddingBottom(); + + // show unread messages in different color to not miss them + ui.layout.setBackgroundResource(R.drawable.msg_in_unread); + + // re-apply the previous padding due to bug in some Android versions + // see: https://code.google.com/p/android/issues/detail?id=17885 + ui.layout.setPadding(left, top, right, bottom); + } } if (item.getBody() == null) { @@ -186,6 +202,7 @@ class ConversationAdapter extends public TextView body; public TextView date; public ImageView status; + public ImageView avatar; public MessageHolder(View v, int type) { super(v); @@ -197,6 +214,8 @@ class ConversationAdapter extends // outgoing message (local) if (type == MSG_OUT) { status = (ImageView) v.findViewById(R.id.msgStatus); + } else { + avatar = (ImageView) v.findViewById(R.id.msgAvatar); } } } diff --git a/briar-android/src/org/briarproject/android/forum/ForumActivity.java b/briar-android/src/org/briarproject/android/forum/ForumActivity.java index 7f725d40d2db8a62bcd2653572a3348ce689bdc9..a67ebd12e702a697fcf8bde22b874ce0f0217136 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ForumActivity.java @@ -18,6 +18,7 @@ import org.briarproject.android.util.ElasticHorizontalSpace; import org.briarproject.android.util.HorizontalBorder; import org.briarproject.android.util.ListLoadingProgressBar; import org.briarproject.api.android.AndroidNotificationManager; +import org.briarproject.api.android.ReferenceManager; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.NoSuchSubscriptionException; @@ -70,6 +71,8 @@ public class ForumActivity extends BriarActivity implements EventListener, private ListLoadingProgressBar loading = null; private ImageButton composeButton = null, shareButton = null; + @Inject private ReferenceManager referenceManager; + // Fields that are accessed from background threads must be volatile @Inject private volatile ForumManager forumManager; @Inject private volatile EventBus eventBus; @@ -366,7 +369,8 @@ public class ForumActivity extends BriarActivity implements EventListener, i.putExtra("briar.FORUM_NAME", forum.getName()); i.putExtra("briar.MESSAGE_ID", header.getId().getBytes()); Author author = header.getAuthor(); - if (author != null) i.putExtra("briar.AUTHOR_NAME", author.getName()); + if (author != null) i.putExtra("briar.AUTHOR_HANDLE", + referenceManager.putReference(author, Author.class)); i.putExtra("briar.AUTHOR_STATUS", header.getAuthorStatus().name()); i.putExtra("briar.CONTENT_TYPE", header.getContentType()); i.putExtra("briar.TIMESTAMP", header.getTimestamp()); diff --git a/briar-android/src/org/briarproject/android/forum/ForumAdapter.java b/briar-android/src/org/briarproject/android/forum/ForumAdapter.java index ac121144740797b6f4a21c45f8b865e0f766ff54..30ff7d0bf8367158b88af5c8178dc9c13d7bdb15 100644 --- a/briar-android/src/org/briarproject/android/forum/ForumAdapter.java +++ b/briar-android/src/org/briarproject/android/forum/ForumAdapter.java @@ -55,8 +55,7 @@ class ForumAdapter extends ArrayAdapter<ForumItem> { AuthorView authorView = new AuthorView(ctx); authorView.setLayoutParams(WRAP_WRAP_1); Author author = header.getAuthor(); - if (author == null) authorView.init(null, header.getAuthorStatus()); - else authorView.init(author.getName(), header.getAuthorStatus()); + authorView.init(author, header.getAuthorStatus()); headerLayout.addView(authorView); TextView date = new TextView(ctx); diff --git a/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java b/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java index 706cb31fe00a0414ffa3faac22ecb1c5ea8a78bf..5ffc8aaa89eccabf84427ff583f4112ef41c951e 100644 --- a/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java +++ b/briar-android/src/org/briarproject/android/forum/ReadForumPostActivity.java @@ -17,6 +17,7 @@ import org.briarproject.android.util.AuthorView; import org.briarproject.android.util.ElasticHorizontalSpace; import org.briarproject.android.util.HorizontalBorder; import org.briarproject.android.util.LayoutUtils; +import org.briarproject.api.android.ReferenceManager; import org.briarproject.api.db.DbException; import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.forum.ForumManager; @@ -56,6 +57,8 @@ implements OnClickListener { private TextView content = null; private int position = -1; + @Inject private ReferenceManager referenceManager; + // Fields that are accessed from background threads must be volatile @Inject private volatile ForumManager forumManager; private volatile MessageId messageId = null; @@ -82,7 +85,9 @@ implements OnClickListener { if (minTimestamp == -1) throw new IllegalStateException(); position = i.getIntExtra("briar.POSITION", -1); if (position == -1) throw new IllegalStateException(); - String authorName = i.getStringExtra("briar.AUTHOR_NAME"); + long authorHandle = i.getLongExtra("briar.AUTHOR_HANDLE", -1); + if (authorHandle == -1) throw new IllegalStateException(); + Author author = referenceManager.removeReference(authorHandle, Author.class); String s = i.getStringExtra("briar.AUTHOR_STATUS"); if (s == null) throw new IllegalStateException(); Author.Status authorStatus = Author.Status.valueOf(s); @@ -102,10 +107,10 @@ implements OnClickListener { header.setOrientation(HORIZONTAL); header.setGravity(CENTER_VERTICAL); - AuthorView author = new AuthorView(this); - author.setLayoutParams(WRAP_WRAP_1); - author.init(authorName, authorStatus); - header.addView(author); + AuthorView authorView = new AuthorView(this); + authorView.setLayoutParams(WRAP_WRAP_1); + authorView.init(author, authorStatus); + header.addView(authorView); int pad = LayoutUtils.getPadding(this); diff --git a/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java b/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java index 40e72a14877abad5ce9c4f3d74b3dc372a3449f3..a38433f311e324066248a455671078095a0fec57 100644 --- a/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java +++ b/briar-android/src/org/briarproject/android/forum/WriteForumPostActivity.java @@ -123,7 +123,7 @@ implements OnItemSelectedListener, OnClickListener { left.addRule(CENTER_VERTICAL); header.addView(from, left); - adapter = new LocalAuthorSpinnerAdapter(this, true); + adapter = new LocalAuthorSpinnerAdapter(this, crypto, true); spinner = new Spinner(this); spinner.setId(2); spinner.setAdapter(adapter); diff --git a/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java b/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java index 2aa2358f45e1c62c248e7ba6109cd4ed0cb4aca3..a0e2d88de2ebfff7c8dd3e5d3bdcb01f4ad2ffce 100644 --- a/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java +++ b/briar-android/src/org/briarproject/android/identity/LocalAuthorSpinnerAdapter.java @@ -1,21 +1,24 @@ package org.briarproject.android.identity; import android.content.Context; +import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; +import android.widget.ImageView; import android.widget.SpinnerAdapter; import android.widget.TextView; import org.briarproject.R; -import org.briarproject.android.util.LayoutUtils; +import org.briarproject.api.crypto.CryptoComponent; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; -import static android.text.TextUtils.TruncateAt.END; +import im.delight.android.identicons.IdenticonDrawable; + import static org.briarproject.android.identity.LocalAuthorItem.ANONYMOUS; import static org.briarproject.android.identity.LocalAuthorItem.NEW; @@ -23,11 +26,14 @@ public class LocalAuthorSpinnerAdapter extends BaseAdapter implements SpinnerAdapter { private final Context ctx; + private final CryptoComponent crypto; private final boolean includeAnonymous; private final List<LocalAuthorItem> list = new ArrayList<LocalAuthorItem>(); - public LocalAuthorSpinnerAdapter(Context ctx, boolean includeAnonymous) { + public LocalAuthorSpinnerAdapter(Context ctx, + CryptoComponent crypto, boolean includeAnonymous) { this.ctx = ctx; + this.crypto = crypto; this.includeAnonymous = includeAnonymous; } @@ -49,17 +55,33 @@ implements SpinnerAdapter { @Override public View getDropDownView(int position, View convertView, ViewGroup parent) { - TextView name = new TextView(ctx); - name.setTextSize(18); - name.setSingleLine(); - name.setEllipsize(END); - int pad = LayoutUtils.getPadding(ctx); - name.setPadding(pad, pad, pad, pad); + View view; + if (convertView == null) { + LayoutInflater inflater = + (LayoutInflater) ctx + .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + view = inflater.inflate(R.layout.dropdown_author, parent, false); + } else + view = convertView; + + TextView name = (TextView) view.findViewById(R.id.nameView); + ImageView avatar = + (ImageView) view.findViewById(R.id.avatarView); + LocalAuthorItem item = getItem(position); - if (item == ANONYMOUS) name.setText(R.string.anonymous); - else if (item == NEW) name.setText(R.string.new_identity_item); - else name.setText(item.getLocalAuthor().getName()); - return name; + if (item == ANONYMOUS) { + name.setText(R.string.anonymous); + avatar.setVisibility(View.INVISIBLE); + } else if (item == NEW) { + name.setText(R.string.new_identity_item); + avatar.setVisibility(View.INVISIBLE); + } else { + name.setText(item.getLocalAuthor().getName()); + avatar.setVisibility(View.VISIBLE); + avatar.setImageDrawable(new IdenticonDrawable(crypto, + item.getLocalAuthor().getId().getBytes())); + } + return view; } public LocalAuthorItem getItem(int position) { @@ -78,15 +100,7 @@ implements SpinnerAdapter { } public View getView(int position, View convertView, ViewGroup parent) { - TextView name = new TextView(ctx); - name.setTextSize(18); - name.setSingleLine(); - name.setEllipsize(END); - LocalAuthorItem item = getItem(position); - if (item == ANONYMOUS) name.setText(R.string.anonymous); - else if (item == NEW) name.setText(R.string.new_identity_item); - else name.setText(item.getLocalAuthor().getName()); - return name; + return getDropDownView(position, convertView, parent); } @Override diff --git a/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java b/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java index 940de7e7594f83fff6a48fd6d1d0062ce8e8f048..912d28ccebaf78525a7115852776583b1e3a3000 100644 --- a/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java +++ b/briar-android/src/org/briarproject/android/invitation/ChooseIdentityView.java @@ -16,11 +16,16 @@ import org.briarproject.android.identity.CreateIdentityActivity; import org.briarproject.android.identity.LocalAuthorItem; import org.briarproject.android.identity.LocalAuthorItemComparator; import org.briarproject.android.identity.LocalAuthorSpinnerAdapter; +import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.LocalAuthor; import java.util.Collection; +import javax.inject.Inject; + +import roboguice.RoboGuice; + import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE; import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION; import static org.briarproject.android.identity.LocalAuthorItem.NEW; @@ -30,6 +35,7 @@ import static org.briarproject.android.invitation.AddContactActivity.REQUEST_CRE class ChooseIdentityView extends AddContactView implements OnItemSelectedListener, OnClickListener { + @Inject private CryptoComponent crypto; private LocalAuthorSpinnerAdapter adapter = null; private Spinner spinner = null; @@ -40,6 +46,7 @@ implements OnItemSelectedListener, OnClickListener { void populate() { removeAllViews(); Context ctx = getContext(); + RoboGuice.injectMembers(ctx, this); LayoutInflater inflater = (LayoutInflater) ctx.getSystemService (Context.LAYOUT_INFLATER_SERVICE); @@ -50,7 +57,7 @@ implements OnItemSelectedListener, OnClickListener { TextView step = (TextView) view.findViewById(R.id.stepView); step.setText(String.format(ctx.getString(R.string.step), 1, 3)); - adapter = new LocalAuthorSpinnerAdapter(ctx, false); + adapter = new LocalAuthorSpinnerAdapter(ctx, crypto, false); spinner = (Spinner) view.findViewById(R.id.spinner); spinner.setAdapter(adapter); spinner.setOnItemSelectedListener(this); diff --git a/briar-android/src/org/briarproject/android/util/AuthorView.java b/briar-android/src/org/briarproject/android/util/AuthorView.java index 152a801b91495218d4b843b7bb4ff71daba326ab..dd20a28e7717cd0e54a2a9f745bf5aef0d414595 100644 --- a/briar-android/src/org/briarproject/android/util/AuthorView.java +++ b/briar-android/src/org/briarproject/android/util/AuthorView.java @@ -1,42 +1,69 @@ package org.briarproject.android.util; import android.content.Context; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; import android.widget.ImageView; -import android.widget.RelativeLayout; import android.widget.TextView; import org.briarproject.R; +import org.briarproject.api.crypto.CryptoComponent; import org.briarproject.api.identity.Author; -import static android.text.TextUtils.TruncateAt.END; +import javax.inject.Inject; -public class AuthorView extends RelativeLayout { +import im.delight.android.identicons.IdenticonDrawable; +import roboguice.RoboGuice; + +public class AuthorView extends FrameLayout { + + @Inject private CryptoComponent crypto; + private ImageView avatarView; + private TextView nameView; + private ImageView statusView; public AuthorView(Context ctx) { super(ctx); + + initViews(); + } + + public AuthorView(Context context, AttributeSet attrs) { + super(context, attrs); + + initViews(); } - public void init(String name, Author.Status status) { - Context ctx = getContext(); - int pad = LayoutUtils.getPadding(ctx); - - TextView nameView = new TextView(ctx); - nameView.setId(1); - nameView.setTextSize(18); - nameView.setSingleLine(); - nameView.setEllipsize(END); - nameView.setPadding(pad, pad, pad, pad); - if (name == null) nameView.setText(R.string.anonymous); - else nameView.setText(name); - LayoutParams leftOf = CommonLayoutParams.relative(); - leftOf.addRule(ALIGN_PARENT_LEFT); - leftOf.addRule(CENTER_VERTICAL); - leftOf.addRule(LEFT_OF, 2); - addView(nameView, leftOf); - - ImageView statusView = new ImageView(ctx); - statusView.setId(2); - statusView.setPadding(0, pad, pad, pad); + public AuthorView(Context context, AttributeSet attrs, + int defStyle) { + super(context, attrs, defStyle); + + initViews(); + } + + private void initViews() { + RoboGuice.injectMembers(getContext(), this); + if (isInEditMode()) + return; + + View v = LayoutInflater.from(getContext()).inflate( + R.layout.author_view, this, true); + + avatarView = (ImageView) v.findViewById(R.id.avatarView); + nameView = (TextView) v.findViewById(R.id.nameView); + statusView = (ImageView) v.findViewById(R.id.statusView); + } + + public void init(Author author, Author.Status status) { + if (author == null) nameView.setText(R.string.anonymous); + else { + avatarView.setImageDrawable( + new IdenticonDrawable(crypto, author.getId().getBytes())); + nameView.setText(author.getName()); + } + switch(status) { case ANONYMOUS: statusView.setImageResource(R.drawable.identity_anonymous); @@ -51,9 +78,5 @@ public class AuthorView extends RelativeLayout { statusView.setImageResource(R.drawable.identity_verified); break; } - LayoutParams right = CommonLayoutParams.relative(); - right.addRule(ALIGN_PARENT_RIGHT); - right.addRule(CENTER_VERTICAL); - addView(statusView, right); } }