From 6c431996c77be700aec6276ae832c5d1b15e9644 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Tue, 22 Jan 2019 12:21:06 -0300
Subject: [PATCH 1/6] Allow to set a display name and choose an avatar

---
 android/app/src/main/cpp/native.cpp           |  28 ++++
 .../hifiinterface/MainActivity.java           |  31 ++++-
 .../fragment/ProfileFragment.java             | 126 ++++++++++++++++++
 .../provider/AvatarProvider.java              |  70 ++++++++++
 .../hifiinterface/view/AvatarAdapter.java     | 111 +++++++++++++++
 .../app/src/main/res/layout/avatar_item.xml   |  22 +++
 .../src/main/res/layout/fragment_profile.xml  | 107 +++++++++++++++
 .../app/src/main/res/menu/menu_navigation.xml |   4 +
 android/app/src/main/res/values/dimens.xml    |   5 +-
 android/app/src/main/res/values/strings.xml   |   4 +
 interface/src/AndroidHelper.cpp               |  14 ++
 interface/src/AndroidHelper.h                 |   3 +
 12 files changed, 523 insertions(+), 2 deletions(-)
 create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java
 create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java
 create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java
 create mode 100644 android/app/src/main/res/layout/avatar_item.xml
 create mode 100644 android/app/src/main/res/layout/fragment_profile.xml

diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp
index f9c7751a3e..01d805b077 100644
--- a/android/app/src/main/cpp/native.cpp
+++ b/android/app/src/main/cpp/native.cpp
@@ -493,6 +493,34 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
 
 }
 
+JNIEXPORT jstring JNICALL
+Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_getDisplayName(JNIEnv *env,
+                                                                           jobject instance) {
+
+    QString displayName = AndroidHelper::instance().getDisplayName();
+    return env->NewStringUTF(displayName.toLatin1().data());
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setDisplayName(JNIEnv *env,
+                                                                           jobject instance,
+                                                                           jstring name_) {
+    const char *c_name = env->GetStringUTFChars(name_, 0);
+    const QString name = QString::fromUtf8(c_name);
+    env->ReleaseStringUTFChars(name_, c_name);
+    AndroidHelper::instance().setDisplayName(name);
+}
+
+JNIEXPORT void JNICALL
+Java_io_highfidelity_hifiinterface_fragment_ProfileFragment_setAvatarUrl(JNIEnv *env,
+                                                                         jobject instance,
+                                                                         jstring url_) {
+    const char *url = env->GetStringUTFChars(url_, 0);
+    QString avatarUrl = QString::fromUtf8(url);
+    AndroidHelper::instance().setMyAvatarUrl(avatarUrl);
+    env->ReleaseStringUTFChars(url_, url);
+}
+
 JNIEXPORT void JNICALL
 Java_io_highfidelity_hifiinterface_MainActivity_logout(JNIEnv *env, jobject instance) {
     DependencyManager::get<AccountManager>()->logout();
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
index e17b530f1c..2e3ca8a5e5 100644
--- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
@@ -33,13 +33,15 @@ import com.squareup.picasso.Picasso;
 import io.highfidelity.hifiinterface.fragment.FriendsFragment;
 import io.highfidelity.hifiinterface.fragment.HomeFragment;
 import io.highfidelity.hifiinterface.fragment.PolicyFragment;
+import io.highfidelity.hifiinterface.fragment.ProfileFragment;
 import io.highfidelity.hifiinterface.fragment.SettingsFragment;
 import io.highfidelity.hifiinterface.fragment.SignupFragment;
 import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
 
 public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
                                                                 HomeFragment.OnHomeInteractionListener,
-                                                                FriendsFragment.OnHomeInteractionListener {
+                                                                FriendsFragment.OnHomeInteractionListener,
+                                                                ProfileFragment.OnProfileInteractionListener {
 
     private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
     public static final String DEFAULT_FRAGMENT = "Home";
@@ -61,6 +63,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
     private View mProfilePanel;
     private TextView mLogoutOption;
     private MenuItem mPeopleMenuItem;
+    private MenuItem mProfileMenuItem;
 
     private boolean backToScene;
     private String backToUrl;
@@ -83,6 +86,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
 
         mPeopleMenuItem = mNavigationView.getMenu().findItem(R.id.action_people);
 
+        mProfileMenuItem = mNavigationView.getMenu().findItem(R.id.action_profile);
+
         updateDebugMenu(mNavigationView.getMenu());
 
         Toolbar toolbar = findViewById(R.id.toolbar);
@@ -162,6 +167,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
         loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
     }
 
+    private void loadProfileFragment() {
+        Fragment fragment = ProfileFragment.newInstance();
+
+        loadFragment(fragment, getString(R.string.profile), getString(R.string.tagFragmentProfile), true, true);
+    }
+
     private void loadSettingsFragment() {
         SettingsFragment fragment = SettingsFragment.newInstance();
 
@@ -261,6 +272,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
             case R.id.action_people:
                 loadPeopleFragment();
                 return true;
+            case R.id.action_profile:
+                loadProfileFragment();
+                break;
             case R.id.action_debug_settings:
                 loadSettingsFragment();
                 return true;
@@ -351,6 +365,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
         goToUser(username);
     }
 
+    @Override
+    public void onCancelProfileEdit() {
+        getFragmentManager().popBackStack();
+    }
+
+    @Override
+    public void onCompleteProfileEdit() {
+        getFragmentManager().popBackStack();
+    }
+
+    @Override
+    public void onAvatarChosen() {
+        getFragmentManager().popBackStack();
+    }
+
     private class RoundProfilePictureCallback implements Callback {
         @Override
         public void onSuccess() {
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java
new file mode 100644
index 0000000000..e5aa793341
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java
@@ -0,0 +1,126 @@
+package io.highfidelity.hifiinterface.fragment;
+
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.TextView;
+
+import io.highfidelity.hifiinterface.R;
+import io.highfidelity.hifiinterface.provider.AvatarProvider;
+import io.highfidelity.hifiinterface.view.AvatarAdapter;
+
+public class ProfileFragment extends Fragment {
+
+    private TextView mDisplayName;
+
+    private Button mOkButton;
+    private OnProfileInteractionListener mListener;
+    private AvatarProvider mAvatarsProvider;
+
+    private native String getDisplayName();
+    private native void setDisplayName(String name);
+    private native void setAvatarUrl(String url);
+
+    public ProfileFragment() {
+        // Required empty public constructor
+    }
+
+    public static ProfileFragment newInstance() {
+        ProfileFragment fragment = new ProfileFragment();
+        return fragment;
+    }
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+                             Bundle savedInstanceState) {
+        View rootView = inflater.inflate(R.layout.fragment_profile, container, false);
+
+        mDisplayName = rootView.findViewById(R.id.displayName);
+        mDisplayName.setText(getDisplayName());
+        mDisplayName.setOnEditorActionListener((textView, actionId, keyEvent) -> onDisplayNameEditorAction(textView, actionId, keyEvent));
+
+        mOkButton = rootView.findViewById(R.id.okButton);
+        mOkButton.setOnClickListener(view -> onOkButtonClicked());
+
+        rootView.findViewById(R.id.cancel).setOnClickListener(view -> onCancelProfileEdit());
+
+        RecyclerView avatarsView = rootView.findViewById(R.id.gridview);
+        int numberOfColumns = 1;
+        mAvatarsProvider = new AvatarProvider(getContext());
+        GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
+        avatarsView.setLayoutManager(gridLayoutMgr);
+        AvatarAdapter avatarAdapter = new AvatarAdapter(getContext(), mAvatarsProvider);
+        avatarsView.setAdapter(avatarAdapter);
+        avatarAdapter.loadAvatars();
+
+        avatarAdapter.setClickListener((view, position, avatar) -> {
+            setAvatarUrl(avatar.avatarUrl);
+            if (mListener != null) {
+                mListener.onAvatarChosen();
+            }
+        });
+        return rootView;
+    }
+
+    private void onOkButtonClicked() {
+        setDisplayName(mDisplayName.getText().toString());
+        View view = getActivity().getCurrentFocus();
+        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+        if (mListener != null) {
+            mListener.onCompleteProfileEdit();
+        }
+    }
+
+    private boolean onDisplayNameEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            mOkButton.performClick();
+            return true;
+        }
+        return false;
+    }
+
+    private void onCancelProfileEdit() {
+        View view = getActivity().getCurrentFocus();
+        InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+        imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
+        if (mListener != null) {
+            mListener.onCancelProfileEdit();
+        }
+    }
+
+    /**
+     * Processes the back pressed event and returns true if it was managed by this Fragment
+     * @return
+     */
+    public boolean onBackPressed() {
+        return false;
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        if (context instanceof OnProfileInteractionListener) {
+            mListener = (OnProfileInteractionListener) context;
+        } else {
+            throw new RuntimeException(context.toString()
+                    + " must implement OnProfileInteractionListener");
+        }
+    }
+
+    public interface OnProfileInteractionListener {
+        void onCancelProfileEdit();
+        void onCompleteProfileEdit();
+        void onAvatarChosen();
+    }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java
new file mode 100644
index 0000000000..5bbb8ee666
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java
@@ -0,0 +1,70 @@
+package io.highfidelity.hifiinterface.provider;
+
+import android.content.Context;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import io.highfidelity.hifiinterface.view.AvatarAdapter;
+
+/**
+ * Created by gcalero on 1/21/19
+ */
+public class AvatarProvider {
+
+    private static final String AVATARS_JSON = "avatars.json";
+    private static final String JSON_FIELD_NAME = "name";
+    private static final String JSON_FIELD_URL = "url";
+    private static final String JSON_FIELD_IMAGE = "preview_image";
+    private static final String JSON_FIELD_AVATARS_ARRAY = "avatars";
+    private final Context mContext;
+
+    public interface AvatarsCallback {
+        void retrieveOk(List<AvatarAdapter.Avatar> avatars);
+        void retrieveError(Exception e, String message);
+    }
+
+    public AvatarProvider(Context context) {
+        mContext = context;
+    }
+
+    public void retrieve(AvatarsCallback avatarsCallback)
+    {
+        try {
+            JSONObject obj = new JSONObject(loadJSONFromAssets());
+            JSONArray m_jArry = obj.getJSONArray(JSON_FIELD_AVATARS_ARRAY);
+            ArrayList<AvatarAdapter.Avatar> avatars = new ArrayList<>();
+
+            for (int i = 0; i < m_jArry.length(); i++) {
+                JSONObject jo_inside = m_jArry.getJSONObject(i);
+                AvatarAdapter.Avatar anAvatar = new AvatarAdapter.Avatar();
+                anAvatar.avatarName = jo_inside.getString(JSON_FIELD_NAME);
+                anAvatar.avatarPreviewUrl = jo_inside.getString(JSON_FIELD_IMAGE);
+                anAvatar.avatarUrl = jo_inside.getString(JSON_FIELD_URL);
+                avatars.add(anAvatar);
+            }
+            avatarsCallback.retrieveOk(avatars);
+        } catch (IOException e) {
+            avatarsCallback.retrieveError(e, "Failed retrieving avatar JSON");
+        } catch (JSONException e) {
+            avatarsCallback.retrieveError(e, "Failed parsing avatar JSON");
+        }
+    }
+
+    private String loadJSONFromAssets() throws IOException {
+        String json = null;
+        InputStream is = mContext.getAssets().open(AVATARS_JSON);
+        int size = is.available();
+        byte[] buffer = new byte[size];
+        is.read(buffer);
+        is.close();
+        json = new String(buffer, "UTF-8");
+        return json;
+    }
+}
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java
new file mode 100644
index 0000000000..d88083ff2a
--- /dev/null
+++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java
@@ -0,0 +1,111 @@
+package io.highfidelity.hifiinterface.view;
+
+import android.content.Context;
+import android.net.Uri;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.squareup.picasso.Picasso;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import io.highfidelity.hifiinterface.R;
+import io.highfidelity.hifiinterface.provider.AvatarProvider;
+
+/**
+ * Created by gcalero on 1/21/19
+ */
+public class AvatarAdapter extends RecyclerView.Adapter<AvatarAdapter.ViewHolder> {
+
+    private static final String TAG = "Interface";
+    private final Context mContext;
+    private final LayoutInflater mInflater;
+    private final AvatarProvider mProvider;
+    private List<Avatar> mAvatars = new ArrayList<>();
+    private ItemClickListener mClickListener;
+
+    public AvatarAdapter(Context context, AvatarProvider provider) {
+        mContext = context;
+        mInflater = LayoutInflater.from(mContext);
+        mProvider = provider;
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+        View view = mInflater.inflate(R.layout.avatar_item, parent, false);
+        return new AvatarAdapter.ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder holder, int position) {
+        AvatarAdapter.Avatar anAvatar = mAvatars.get(position);
+        assert(holder.mName != null);
+        holder.mName.setText(anAvatar.avatarName);
+        Uri uri = Uri.parse(anAvatar.avatarPreviewUrl);
+        Picasso.get().load(uri).into(holder.mPreviewImage);
+    }
+
+    @Override
+    public int getItemCount() {
+        return mAvatars.size();
+    }
+
+    public void loadAvatars() {
+        mProvider.retrieve(new AvatarProvider.AvatarsCallback() {
+            @Override
+            public void retrieveOk(List<AvatarAdapter.Avatar> avatars) {
+                mAvatars = new ArrayList<>(avatars);
+                notifyDataSetChanged();
+            }
+
+            @Override
+            public void retrieveError(Exception e, String message) {
+                Log.e(TAG, message, e);
+            }
+        });
+    }
+
+    public void setClickListener(ItemClickListener clickListener) {
+        mClickListener = clickListener;
+    }
+
+    public interface ItemClickListener {
+        void onItemClick(View view, int position, Avatar avatar);
+    }
+
+    public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
+
+        TextView mName;
+        ImageView mPreviewImage;
+
+        public ViewHolder(View itemView) {
+            super(itemView);
+            mName = itemView.findViewById(R.id.avatarName);
+            assert (mName != null);
+            mPreviewImage = itemView.findViewById(R.id.avatarPreview);
+            itemView.setOnClickListener(this);
+        }
+
+        @Override
+        public void onClick(View view) {
+            int position= getAdapterPosition();
+            if (mClickListener != null) {
+                mClickListener.onItemClick(view, position, mAvatars.get(position));
+            }
+        }
+    }
+
+    public static class Avatar {
+        public String avatarName;
+        public String avatarUrl;
+        public String avatarPreviewUrl;
+
+        public Avatar() { }
+    }
+}
diff --git a/android/app/src/main/res/layout/avatar_item.xml b/android/app/src/main/res/layout/avatar_item.xml
new file mode 100644
index 0000000000..4da6ee1170
--- /dev/null
+++ b/android/app/src/main/res/layout/avatar_item.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:paddingTop="5dp"
+    android:paddingBottom="5dp">
+    <TextView
+        android:id="@+id/avatarName"
+        android:fontFamily="@font/raleway_bold"
+        android:textSize="20sp"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="center"
+        android:textColor="@color/colorButton1" />
+    <ImageView
+        android:id="@+id/avatarPreview"
+        android:layout_width="match_parent"
+        android:layout_height="190dp"
+        android:scaleType="fitCenter"
+        app:layout_constraintTop_toBottomOf="@id/avatarName"/>
+</android.support.constraint.ConstraintLayout>
\ No newline at end of file
diff --git a/android/app/src/main/res/layout/fragment_profile.xml b/android/app/src/main/res/layout/fragment_profile.xml
new file mode 100644
index 0000000000..8a5f925ad2
--- /dev/null
+++ b/android/app/src/main/res/layout/fragment_profile.xml
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="utf-8"?>
+<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@color/backgroundLight">
+
+    <ImageView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:src="@drawable/encourage_login_background"
+        android:scaleType="fitXY" />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:background="#B2000000" />
+
+    <ImageView
+        android:id="@+id/header"
+        android:layout_width="@dimen/header_hifi_width"
+        android:layout_height="@dimen/header_hifi_height"
+        android:layout_marginTop="@dimen/header_hifi_margin_top"
+        android:contentDescription="HighFidelity"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:srcCompat="@drawable/hifi_header" />
+
+        <android.support.constraint.ConstraintLayout
+        android:id="@+id/profileForm"
+        android:layout_width="0dp"
+        android:layout_height="0dp"
+        android:layout_marginTop="@dimen/profile_margin_top"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@id/header"
+        app:layout_constraintBottom_toBottomOf="parent"
+        android:visibility="visible">
+        <EditText
+            android:id="@+id/displayName"
+            android:layout_width="match_parent"
+            android:layout_height="27dp"
+            android:layout_marginLeft="@dimen/profile_margin"
+            android:layout_marginRight="@dimen/profile_margin"
+            android:layout_marginBottom="@dimen/profile_margin"
+            android:background="@color/white_opaque"
+            android:paddingLeft="@dimen/edit_text_padding"
+            android:ems="10"
+            android:fontFamily="sans-serif"
+            android:textSize="@dimen/login_edit_text_size"
+            android:inputType="textCapWords"
+            android:textColor="@color/editTextColor"
+            android:textColorHint="@color/editTextColor"
+            android:gravity="left|center_vertical"
+            android:hint="@string/displayName"
+            app:layout_constraintTop_toTopOf="parent" />
+            <FrameLayout
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                app:layout_constraintTop_toBottomOf="@id/displayName"
+                app:layout_constraintBottom_toTopOf="@id/okButton"
+                android:layout_marginTop="@dimen/profile_margin_avatars_top"
+                android:layout_marginBottom="@dimen/profile_margin_avatars_bottom"
+                android:layout_marginLeft="@dimen/profile_margin"
+                android:layout_marginRight="@dimen/profile_margin">
+                <android.support.v7.widget.RecyclerView
+                    android:id="@+id/gridview"
+                    android:paddingTop="@dimen/list_vertical_padding"
+                    android:paddingBottom="@dimen/list_vertical_padding"
+                    android:clipToPadding="false"
+                    android:layout_width="match_parent"
+                    android:layout_height="match_parent"
+                    />
+            </FrameLayout>
+            <TextView
+                android:id="@+id/cancel"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginRight="8dp"
+                app:layout_constraintLeft_toLeftOf="@id/displayName"
+                app:layout_constraintTop_toTopOf="@id/okButton"
+                app:layout_constraintBottom_toBottomOf="@id/okButton"
+                app:layout_constraintRight_toLeftOf="@id/okButton"
+                android:textColor="@color/white_opaque"
+                android:fontFamily="@font/raleway_bold"
+                android:textSize="@dimen/button_medium_text_size"
+                android:text="@string/cancel_uppercase" />
+
+            <Button
+                android:id="@+id/okButton"
+                android:layout_width="@dimen/button_medium_width"
+                android:layout_height="@dimen/button_medium_height"
+                android:background="@drawable/rounded_button_color3"
+                android:fontFamily="@font/raleway_bold"
+                android:layout_marginTop="@dimen/button_medium_margin"
+                android:layout_marginBottom="@dimen/button_medium_margin"
+                android:text="@string/ok"
+                android:textColor="@color/white_opaque"
+                android:textAllCaps="false"
+                android:textSize="@dimen/button_medium_text_size"
+                app:layout_constraintRight_toRightOf="@id/displayName"
+                app:layout_constraintBottom_toBottomOf="parent" />
+        </android.support.constraint.ConstraintLayout>
+
+</android.support.constraint.ConstraintLayout>
diff --git a/android/app/src/main/res/menu/menu_navigation.xml b/android/app/src/main/res/menu/menu_navigation.xml
index 142af5d146..2bd8a58b4b 100644
--- a/android/app/src/main/res/menu/menu_navigation.xml
+++ b/android/app/src/main/res/menu/menu_navigation.xml
@@ -9,6 +9,10 @@
         android:id="@+id/action_people"
         android:title="@string/people"
         />
+    <item
+        android:id="@+id/action_profile"
+        android:title="@string/profile"
+        />
     <item
         android:id="@+id/action_debug_settings"
         android:title="@string/settings"
diff --git a/android/app/src/main/res/values/dimens.xml b/android/app/src/main/res/values/dimens.xml
index 85d79509ba..81217c0425 100644
--- a/android/app/src/main/res/values/dimens.xml
+++ b/android/app/src/main/res/values/dimens.xml
@@ -54,5 +54,8 @@
     <dimen name="login_menu_button_margin_top">77dp</dimen>
     <dimen name="login_menu_text_size">20sp</dimen>
     <dimen name="login_edit_text_padding">14dp</dimen>
-
+    <dimen name="profile_margin_top">20dp</dimen>
+    <dimen name="profile_margin">35dp</dimen>
+    <dimen name="profile_margin_avatars_top">40dp</dimen>
+    <dimen name="profile_margin_avatars_bottom">70dp</dimen>
 </resources>
diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml
index 671f171c3e..b60caf1f2c 100644
--- a/android/app/src/main/res/values/strings.xml
+++ b/android/app/src/main/res/values/strings.xml
@@ -43,6 +43,7 @@
     <string name="tagFragmentSignup">tagFragmentSignup</string>
     <string name="tagFragmentPolicy">tagFragmentPolicy</string>
     <string name="tagFragmentPeople">tagFragmentPeople</string>
+    <string name="tagFragmentProfile">tagFragmentProfile</string>
     <string name="tagSettings">tagSettings</string>
     <string name="tagFragmentSignedIn">tagFragmentSignedIn</string>
     <string name="settings">Settings</string>
@@ -54,4 +55,7 @@
     <string name="take_me_in_world">No thanks, take me in-world!</string>
     <string name="be_anywere">BE ANYWHERE, WITH ANYONE \nRIGHT NOW</string>
     <string name="steam_log_in">STEAM LOG IN</string>
+    <string name="displayName">Name to display</string>
+    <string name="ok">OK</string>
+    <string name="profile">Avatar</string>
 </resources>
diff --git a/interface/src/AndroidHelper.cpp b/interface/src/AndroidHelper.cpp
index 708859c00a..4f75d5bdb2 100644
--- a/interface/src/AndroidHelper.cpp
+++ b/interface/src/AndroidHelper.cpp
@@ -15,6 +15,7 @@
 #include <src/ui/LoginDialog.h>
 #include "Application.h"
 #include "Constants.h"
+#include "avatar/AvatarManager.h"
 
 #if defined(qApp)
 #undef qApp
@@ -152,3 +153,16 @@ void AndroidHelper::signupFailed(QNetworkReply* reply) {
         emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
     }
 }
+
+QString AndroidHelper::getDisplayName() {
+    return DependencyManager::get<AvatarManager>()->getMyAvatar()->getDisplayName();
+}
+
+void AndroidHelper::setDisplayName(const QString &displayName) {
+    DependencyManager::get<AvatarManager>()->getMyAvatar()->setDisplayName(displayName);
+}
+
+void AndroidHelper::setMyAvatarUrl(const QString &avatarUrl) {
+    QUrl url = QUrl(avatarUrl);
+    DependencyManager::get<AvatarManager>()->getMyAvatar()->useFullAvatarURL(url);
+}
diff --git a/interface/src/AndroidHelper.h b/interface/src/AndroidHelper.h
index f0aaa84214..f1cec6a43b 100644
--- a/interface/src/AndroidHelper.h
+++ b/interface/src/AndroidHelper.h
@@ -41,6 +41,9 @@ public:
     void operator=(AndroidHelper const&) = delete;
 
     void signup(QString email, QString username, QString password);
+    QString getDisplayName();
+    void setDisplayName(const QString &displayName);
+    void setMyAvatarUrl(const QString &avatarUrl);
 
 public slots:
     void showLoginDialog(QUrl url);

From 455d27aeb3f6c111d072b7ef381e160729184fbc Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Tue, 22 Jan 2019 16:18:00 -0300
Subject: [PATCH 2/6] Fix merge

---
 .../io/highfidelity/hifiinterface/fragment/ProfileFragment.java   | 0
 .../io/highfidelity/hifiinterface/provider/AvatarProvider.java    | 0
 .../highfidelity/hifiinterface/provider}/view/AvatarAdapter.java  | 0
 .../{app => apps/interface}/src/main/res/layout/avatar_item.xml   | 0
 .../interface}/src/main/res/layout/fragment_profile.xml           | 0
 5 files changed, 0 insertions(+), 0 deletions(-)
 rename android/{app => apps/interface}/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java (100%)
 rename android/{app => apps/interface}/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java (100%)
 rename android/{app/src/main/java/io/highfidelity/hifiinterface => apps/interface/src/main/java/io/highfidelity/hifiinterface/provider}/view/AvatarAdapter.java (100%)
 rename android/{app => apps/interface}/src/main/res/layout/avatar_item.xml (100%)
 rename android/{app => apps/interface}/src/main/res/layout/fragment_profile.xml (100%)

diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java
similarity index 100%
rename from android/app/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java
rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/fragment/ProfileFragment.java
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java
similarity index 100%
rename from android/app/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java
rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/AvatarProvider.java
diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/view/AvatarAdapter.java
similarity index 100%
rename from android/app/src/main/java/io/highfidelity/hifiinterface/view/AvatarAdapter.java
rename to android/apps/interface/src/main/java/io/highfidelity/hifiinterface/provider/view/AvatarAdapter.java
diff --git a/android/app/src/main/res/layout/avatar_item.xml b/android/apps/interface/src/main/res/layout/avatar_item.xml
similarity index 100%
rename from android/app/src/main/res/layout/avatar_item.xml
rename to android/apps/interface/src/main/res/layout/avatar_item.xml
diff --git a/android/app/src/main/res/layout/fragment_profile.xml b/android/apps/interface/src/main/res/layout/fragment_profile.xml
similarity index 100%
rename from android/app/src/main/res/layout/fragment_profile.xml
rename to android/apps/interface/src/main/res/layout/fragment_profile.xml

From 618b27c9525be5ffa9fecefacf13a90a5dccf158 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Tue, 22 Jan 2019 18:04:54 -0300
Subject: [PATCH 3/6] Add missing json

---
 .../interface/src/main/assets/avatars.json    | 39 +++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 android/apps/interface/src/main/assets/avatars.json

diff --git a/android/apps/interface/src/main/assets/avatars.json b/android/apps/interface/src/main/assets/avatars.json
new file mode 100644
index 0000000000..fd5651bf42
--- /dev/null
+++ b/android/apps/interface/src/main/assets/avatars.json
@@ -0,0 +1,39 @@
+{
+  "avatars": [
+    {
+      "name": "Anime-Styled Boy",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg",
+      "url": "http://mpassets.highfidelity.com/46e0fd52-3cff-462f-ba97-927338d88295-v1/AnimeBoy2.fst"
+    },
+    {
+      "name": "Anime-Styled Girl",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/1e7e43f6-1757-44d3-baa4-756827d96311/large/hifi-mp-1e7e43f6-1757-44d3-baa4-756827d96311.jpg",
+      "url": "http://mpassets.highfidelity.com/0dce3426-55c8-4641-8dd5-d76eb575b64a-v1/Anime_F_Outfit.fst"
+    },
+    {
+      "name": "Last Legends: Male Avatar",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/28569047-6f1a-4100-af67-8054ec397cc3/thumbnail/hifi-mp-28569047-6f1a-4100-af67-8054ec397cc3.jpg",
+      "url": "http://mpassets.highfidelity.com/28569047-6f1a-4100-af67-8054ec397cc3-v1/LLMale2.fst"
+    },
+    {
+      "name": "Last Legends: Female Avatar",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/8d823be5-6197-4418-b984-eb94160ed956/thumbnail/hifi-mp-8d823be5-6197-4418-b984-eb94160ed956.jpg",
+      "url": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg"
+    },
+    {
+      "name": "Matthew: Photo-real avatar",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/b652081b-a199-425e-ae5c-7815721bdc09/thumbnail/hifi-mp-b652081b-a199-425e-ae5c-7815721bdc09.jpg",
+      "url": "http://mpassets.highfidelity.com/b652081b-a199-425e-ae5c-7815721bdc09-v1/matthew.fst"
+    },
+    {
+      "name": "Priscilla: Photo real avatar",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339/thumbnail/hifi-mp-e7565f93-8bc5-47c2-b6eb-b3b31d4a1339.jpg",
+      "url": "http://mpassets.highfidelity.com/e7565f93-8bc5-47c2-b6eb-b3b31d4a1339-v1/priscilla.fst"
+    },
+    {
+      "name": "H1-F1 Optical Interpreter bot",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/469c8b66-e3c2-47fb-9820-e306b1dd15c4/large/hifi-mp-469c8b66-e3c2-47fb-9820-e306b1dd15c4.jpg",
+      "url": "http://mpassets.highfidelity.com/469c8b66-e3c2-47fb-9820-e306b1dd15c4-v1/optical_interpreter[1].fst"
+    }
+  ]
+}
\ No newline at end of file

From 68feea23cb39ae8ffa35a8edde7dfff6c0cfbac0 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 24 Jan 2019 16:21:38 -0300
Subject: [PATCH 4/6] Add the wooden mannequin as an eligible avatar

---
 android/apps/interface/src/main/assets/avatars.json        | 5 +++++
 android/apps/interface/src/main/res/layout/avatar_item.xml | 1 +
 2 files changed, 6 insertions(+)

diff --git a/android/apps/interface/src/main/assets/avatars.json b/android/apps/interface/src/main/assets/avatars.json
index fd5651bf42..c736b8b63a 100644
--- a/android/apps/interface/src/main/assets/avatars.json
+++ b/android/apps/interface/src/main/assets/avatars.json
@@ -1,5 +1,10 @@
 {
   "avatars": [
+    {
+      "name": "Default",
+      "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/large/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg",
+      "url": "qrc:////meshes/defaultAvatar_full.fst"
+    },
     {
       "name": "Anime-Styled Boy",
       "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/46e0fd52-3cff-462f-ba97-927338d88295/thumbnail/hifi-mp-46e0fd52-3cff-462f-ba97-927338d88295.jpg",
diff --git a/android/apps/interface/src/main/res/layout/avatar_item.xml b/android/apps/interface/src/main/res/layout/avatar_item.xml
index 4da6ee1170..6fba708030 100644
--- a/android/apps/interface/src/main/res/layout/avatar_item.xml
+++ b/android/apps/interface/src/main/res/layout/avatar_item.xml
@@ -15,6 +15,7 @@
         android:textColor="@color/colorButton1" />
     <ImageView
         android:id="@+id/avatarPreview"
+        android:background="@color/backgroundDark"
         android:layout_width="match_parent"
         android:layout_height="190dp"
         android:scaleType="fitCenter"

From 4a345a55595d3350dde0dc76327174ef7335af39 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Thu, 24 Jan 2019 18:53:15 -0300
Subject: [PATCH 5/6] Change default avatar name to Wooden Mannequin

---
 android/apps/interface/src/main/assets/avatars.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/android/apps/interface/src/main/assets/avatars.json b/android/apps/interface/src/main/assets/avatars.json
index c736b8b63a..b84d904587 100644
--- a/android/apps/interface/src/main/assets/avatars.json
+++ b/android/apps/interface/src/main/assets/avatars.json
@@ -1,7 +1,7 @@
 {
   "avatars": [
     {
-      "name": "Default",
+      "name": "Wooden Mannequin",
       "preview_image": "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/previews/7fe80a1e-f445-4800-9e89-40e677b03bee/large/hifi-mp-7fe80a1e-f445-4800-9e89-40e677b03bee.jpg",
       "url": "qrc:////meshes/defaultAvatar_full.fst"
     },

From 3a3bd5922a4e71d148ebe6719fa6b1159d5232f4 Mon Sep 17 00:00:00 2001
From: Gabriel Calero <gcalero1984@gmail.com>
Date: Fri, 25 Jan 2019 16:01:21 -0300
Subject: [PATCH 6/6] Bugfix: fragment title remains as Avatar after closing it

---
 .../java/io/highfidelity/hifiinterface/MainActivity.java    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
index 2e3ca8a5e5..e5ea0f998d 100644
--- a/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
+++ b/android/apps/interface/src/main/java/io/highfidelity/hifiinterface/MainActivity.java
@@ -367,17 +367,17 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
 
     @Override
     public void onCancelProfileEdit() {
-        getFragmentManager().popBackStack();
+        loadHomeFragment(false);
     }
 
     @Override
     public void onCompleteProfileEdit() {
-        getFragmentManager().popBackStack();
+        loadHomeFragment(false);
     }
 
     @Override
     public void onAvatarChosen() {
-        getFragmentManager().popBackStack();
+        loadHomeFragment(false);
     }
 
     private class RoundProfilePictureCallback implements Callback {