From 2041ab9ff50390c939a41af1c2b3703f105b2e98 Mon Sep 17 00:00:00 2001
From: Aline Bonnet <aline.bonnet@savoirfairelinux.com>
Date: Wed, 5 Oct 2016 11:51:58 -0400
Subject: [PATCH] profile : edit photo and name

This commit allows the user to edit his name and photo.
An AlertDialog pops up when you click on the name or photo in the left menu.
- VCardUtils implements the methods to load and save the vcard for the local profile
- MenuHeaderView implements the methods to change the name or photo

If a VCard does not exist, loadFromDisk saves a default profile.

Change-Id: I810607d02fbec115329a9e159fe44ebec0736618
Tuleap: #1074
---
 .../cx/ring/adapters/ContactDetailsTask.java  |  85 ++++++-
 .../java/cx/ring/client/HomeActivity.java     |  33 +++
 .../java/cx/ring/fragments/CallFragment.java  |   3 +-
 .../main/java/cx/ring/model/CallContact.java  |  40 +---
 .../cx/ring/service/CallManagerCallBack.java  |   2 +-
 .../main/java/cx/ring/utils/VCardUtils.java   |  92 ++++++--
 .../java/cx/ring/views/MenuHeaderView.java    | 207 ++++++++++++++----
 .../res/drawable-hdpi/ic_insert_photo.png     | Bin 0 -> 425 bytes
 .../res/drawable-hdpi/ic_photo_camera.png     | Bin 0 -> 750 bytes
 .../res/drawable-mdpi/ic_insert_photo.png     | Bin 0 -> 298 bytes
 .../res/drawable-mdpi/ic_photo_camera.png     | Bin 0 -> 479 bytes
 .../res/drawable-xhdpi/ic_insert_photo.png    | Bin 0 -> 613 bytes
 .../res/drawable-xhdpi/ic_photo_camera.png    | Bin 0 -> 1013 bytes
 .../res/drawable-xxhdpi/ic_insert_photo.png   | Bin 0 -> 797 bytes
 .../res/drawable-xxhdpi/ic_photo_camera.png   | Bin 0 -> 1699 bytes
 .../src/main/res/layout/dialog_profile.xml    |  76 +++++++
 .../src/main/res/layout/frag_menu_header.xml  |  72 ++++--
 .../app/src/main/res/values/strings.xml       |   8 +
 18 files changed, 500 insertions(+), 118 deletions(-)
 create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_insert_photo.png
 create mode 100644 ring-android/app/src/main/res/drawable-hdpi/ic_photo_camera.png
 create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_insert_photo.png
 create mode 100644 ring-android/app/src/main/res/drawable-mdpi/ic_photo_camera.png
 create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_insert_photo.png
 create mode 100644 ring-android/app/src/main/res/drawable-xhdpi/ic_photo_camera.png
 create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_insert_photo.png
 create mode 100644 ring-android/app/src/main/res/drawable-xxhdpi/ic_photo_camera.png
 create mode 100644 ring-android/app/src/main/res/layout/dialog_profile.xml

diff --git a/ring-android/app/src/main/java/cx/ring/adapters/ContactDetailsTask.java b/ring-android/app/src/main/java/cx/ring/adapters/ContactDetailsTask.java
index 8246a98d0..392ec8707 100644
--- a/ring-android/app/src/main/java/cx/ring/adapters/ContactDetailsTask.java
+++ b/ring-android/app/src/main/java/cx/ring/adapters/ContactDetailsTask.java
@@ -25,15 +25,19 @@ import android.content.ContentResolver;
 import android.content.ContentUris;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
 import android.net.Uri;
 import android.provider.ContactsContract;
+import android.provider.MediaStore;
 import android.text.TextUtils;
 import android.util.Log;
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -58,6 +62,13 @@ public class ContactDetailsTask implements Runnable {
 
     private final int mViewWidth, mViewHeight;
 
+    private final static String MIME_TYPE_JPG = "image/jpg";
+    private final static String MIME_TYPE_JPEG = "image/jpeg";
+    private final static String MIME_TYPE_PNG = "image/png";
+    private final static int ORIENTATION_LEFT = 270;
+    private final static int ORIENTATION_RIGHT = 90;
+    private final static int MAX_IMAGE_DIMENSION = 70;
+
     public void addCallback(DetailsLoadedCallback cb) {
         synchronized (mCallbacks) {
             if (cb == null) {
@@ -127,7 +138,8 @@ public class ContactDetailsTask implements Runnable {
 
         if (!mContact.getPhones().isEmpty()) {
             String username = mContact.getPhones().get(0).getNumber().host;
-            vcard = VCardUtils.loadFromDisk(username + ".vcf", mContext);
+            Log.d(TAG, "getPhones not empty. Username : " + username);
+            vcard = VCardUtils.loadPeerProfileFromDisk(username + ".vcf", mContext);
 
             if (vcard != null && vcard.getFormattedName() != null) {
                 if (!TextUtils.isEmpty(vcard.getFormattedName().getValue())) {
@@ -216,4 +228,75 @@ public class ContactDetailsTask implements Runnable {
 
         return inSampleSize;
     }
+
+    public static Bitmap loadProfilePhotoFromUri(Context context, Uri uriImage) {
+        try {
+            InputStream is = context.getContentResolver().openInputStream(uriImage);
+            BitmapFactory.Options dbo = new BitmapFactory.Options();
+            dbo.inJustDecodeBounds = true;
+            BitmapFactory.decodeStream(is, null, dbo);
+            is.close();
+
+            int rotatedWidth, rotatedHeight;
+            int orientation = getOrientation(context, uriImage);
+
+            if (orientation == ORIENTATION_LEFT || orientation == ORIENTATION_RIGHT) {
+                rotatedWidth = dbo.outHeight;
+                rotatedHeight = dbo.outWidth;
+            } else {
+                rotatedWidth = dbo.outWidth;
+                rotatedHeight = dbo.outHeight;
+            }
+
+            Bitmap srcBitmap;
+            is = context.getContentResolver().openInputStream(uriImage);
+            if (rotatedWidth > MAX_IMAGE_DIMENSION || rotatedHeight > MAX_IMAGE_DIMENSION) {
+                float widthRatio = ((float) rotatedWidth) / ((float) MAX_IMAGE_DIMENSION);
+                float heightRatio = ((float) rotatedHeight) / ((float) MAX_IMAGE_DIMENSION);
+                float maxRatio = Math.max(widthRatio, heightRatio);
+
+                // Create the bitmap from file
+                BitmapFactory.Options options = new BitmapFactory.Options();
+                options.inSampleSize = (int) maxRatio;
+                srcBitmap = BitmapFactory.decodeStream(is, null, options);
+            } else {
+                srcBitmap = BitmapFactory.decodeStream(is);
+            }
+            is.close();
+
+            if (orientation > 0) {
+                Matrix matrix = new Matrix();
+                matrix.postRotate(orientation);
+
+                srcBitmap = Bitmap.createBitmap(srcBitmap, 0, 0, srcBitmap.getWidth(),
+                        srcBitmap.getHeight(), matrix, true);
+            }
+
+            String type = context.getContentResolver().getType(uriImage);
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            if (type.equals(MIME_TYPE_PNG)) {
+                srcBitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
+            } else if (type.equals(MIME_TYPE_JPG) || type.equals(MIME_TYPE_JPEG)) {
+                srcBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
+            }
+            byte[] bMapArray = baos.toByteArray();
+            baos.close();
+            return BitmapFactory.decodeByteArray(bMapArray, 0, bMapArray.length);
+        } catch (Exception e) {
+            Log.e(TAG, "Error while loading photo from URI", e);
+            return null;
+        }
+    }
+
+    public static int getOrientation(Context context, Uri photoUri) {
+        Cursor cursor = context.getContentResolver().query(photoUri,
+                new String[]{MediaStore.Images.ImageColumns.ORIENTATION}, null, null, null);
+
+        if (cursor.getCount() != 1) {
+            return -1;
+        }
+
+        cursor.moveToFirst();
+        return cursor.getInt(0);
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
index c24b15e02..904fafaa3 100644
--- a/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
+++ b/ring-android/app/src/main/java/cx/ring/client/HomeActivity.java
@@ -20,6 +20,7 @@
 package cx.ring.client;
 
 import android.Manifest;
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
@@ -40,6 +41,7 @@ import android.os.Build;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.preference.PreferenceManager;
+import android.provider.MediaStore;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.widget.FloatingActionButton;
@@ -96,6 +98,10 @@ public class HomeActivity extends AppCompatActivity implements LocalService.Call
     public static final int REQUEST_CODE_PREFERENCES = 1;
     public static final int REQUEST_CODE_CALL = 3;
     public static final int REQUEST_CODE_CONVERSATION = 4;
+    public static final int REQUEST_CODE_PHOTO = 5;
+    public static final int REQUEST_CODE_GALLERY = 6;
+    public static final int REQUEST_PERMISSION_CAMERA = 113;
+    public static final int REQUEST_PERMISSION_READ_STORAGE = 114;
 
     private static final String HOME_TAG = "Home";
     private static final String ACCOUNTS_TAG = "Accounts";
@@ -371,6 +377,22 @@ public class HomeActivity extends AppCompatActivity implements LocalService.Call
 
                 break;
             }
+            case REQUEST_PERMISSION_READ_STORAGE:
+                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+                    Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+                    startActivityForResult(intent, REQUEST_CODE_GALLERY);
+                } else {
+                    return;
+                }
+                break;
+            case REQUEST_PERMISSION_CAMERA:
+                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
+                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+                    startActivityForResult(intent, REQUEST_CODE_PHOTO);
+                } else {
+                    return;
+                }
+                break;
         }
     }
 
@@ -555,6 +577,17 @@ public class HomeActivity extends AppCompatActivity implements LocalService.Call
                     Log.w(TAG, "Call Failed");
                 }
                 break;
+            case REQUEST_CODE_PHOTO:
+                if(resultCode == RESULT_OK && data != null){
+                    fMenuHead.updatePhoto((Bitmap) data.getExtras().get("data"));
+
+                }
+                break;
+            case REQUEST_CODE_GALLERY:
+                if(resultCode == RESULT_OK && data != null) {
+                    fMenuHead.updatePhoto(data.getData());
+                }
+                break;
         }
 
     }
diff --git a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
index ed152fd44..f7de4e6e6 100644
--- a/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
+++ b/ring-android/app/src/main/java/cx/ring/fragments/CallFragment.java
@@ -937,7 +937,8 @@ public class CallFragment extends Fragment implements CallInterface, ContactDeta
 
         VCard vcard;
         String username = participant.getNumberUri().username;
-        vcard = VCardUtils.loadFromDisk(username + ".vcf", context);
+        Log.d(TAG, "username " + username);
+        vcard = VCardUtils.loadPeerProfileFromDisk(username + ".vcf", context);
         if (vcard == null) {
             Log.d(TAG, "No vcard.");
             setDefaultPhoto();
diff --git a/ring-android/app/src/main/java/cx/ring/model/CallContact.java b/ring-android/app/src/main/java/cx/ring/model/CallContact.java
index 004bc09ea..a3de0fad5 100644
--- a/ring-android/app/src/main/java/cx/ring/model/CallContact.java
+++ b/ring-android/app/src/main/java/cx/ring/model/CallContact.java
@@ -44,6 +44,8 @@ import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 import cx.ring.R;
+import cx.ring.utils.VCardUtils;
+import ezvcard.VCard;
 
 public class CallContact implements Parcelable {
     static final String TAG = CallContact.class.getSimpleName();
@@ -100,44 +102,6 @@ public class CallContact implements Parcelable {
         return new CallContact(UNKNOWN_ID, null, to, 0, phones, "", false);
     }
 
-    public static CallContact buildUserContact(Context c) {
-        CallContact result = null;
-        try {
-            if (null != c) {
-                //~ Checking the state of the READ_CONTACTS permission
-                boolean hasReadContactsPermission = ContextCompat.checkSelfPermission(c,
-                        Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
-                if (hasReadContactsPermission) {
-                    Cursor mProfileCursor = c.getContentResolver().query(Profile.CONTENT_URI, PROFILE_PROJECTION, null, null, null);
-                    if (mProfileCursor != null) {
-                        if (mProfileCursor.moveToFirst()) {
-                            String key = mProfileCursor.getString(mProfileCursor.getColumnIndex(Profile.LOOKUP_KEY));
-                            String displayName = mProfileCursor.getString(mProfileCursor.getColumnIndex(Profile.DISPLAY_NAME_PRIMARY));
-
-                            if (displayName == null || displayName.isEmpty())
-                                displayName = c.getResources().getString(R.string.me);
-                            result = new CallContact(mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile._ID)), key, displayName,
-                                    mProfileCursor.getLong(mProfileCursor.getColumnIndex(Profile.PHOTO_ID)), new ArrayList<Phone>(), "", true);
-                        }
-                        mProfileCursor.close();
-                    }
-                } else {
-                    Log.d(TAG, "READ_CONTACTS permission is not granted.");
-                }
-            }
-        } catch (Exception e) {
-            Log.w(TAG, e);
-        }
-
-        //~ Returns the contact if not null
-        if (null != result) {
-            return result;
-        }
-        //~ Or returning a default one
-        String displayName = (null != c) ? c.getResources().getString(R.string.me) : "Me";
-        return new CallContact(UNKNOWN_ID, null, displayName, 0, new ArrayList<Phone>(), "", true);
-    }
-
     public void setContactInfos(String k, String displayName, long photo_id) {
         key = k;
         mDisplayName = displayName;
diff --git a/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java b/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java
index 2845ee815..53761489c 100644
--- a/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java
+++ b/ring-android/app/src/main/java/cx/ring/service/CallManagerCallBack.java
@@ -99,7 +99,7 @@ public class CallManagerCallBack extends Callback {
                     String splitFrom[] = from.split("@");
                     if (splitFrom.length == 2) {
                         String filename = splitFrom[0] + ".vcf";
-                        VCardUtils.saveToDisk(mProfileChunk.getCompleteProfile(),
+                        VCardUtils.savePeerProfileToDisk(mProfileChunk.getCompleteProfile(),
                                 filename,
                                 this.mService.getApplicationContext());
 
diff --git a/ring-android/app/src/main/java/cx/ring/utils/VCardUtils.java b/ring-android/app/src/main/java/cx/ring/utils/VCardUtils.java
index 8182b2805..066670abe 100644
--- a/ring-android/app/src/main/java/cx/ring/utils/VCardUtils.java
+++ b/ring-android/app/src/main/java/cx/ring/utils/VCardUtils.java
@@ -23,15 +23,19 @@ package cx.ring.utils;
 import android.content.Context;
 import android.support.annotation.Nullable;
 import android.text.TextUtils;
+import android.util.Log;
 
 import java.io.File;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.HashMap;
 
+import cx.ring.R;
 import cx.ring.service.StringMap;
+import ezvcard.Ezvcard;
 import ezvcard.VCard;
-import ezvcard.io.text.VCardReader;
+import ezvcard.VCardVersion;
+import ezvcard.property.FormattedName;
 
 public class VCardUtils {
     public static final String TAG = VCardUtils.class.getSimpleName();
@@ -70,56 +74,101 @@ public class VCardUtils {
         return messageKeyValue;
     }
 
+    public static void savePeerProfileToDisk(String vcard, String filename, Context context) {
+        String path = peerProfilePath(context);
+        saveToDisk(vcard, filename, path, context);
+    }
+
+    public static void saveLocalProfileToDisk(String vcard, Context context) {
+        String path = localProfilePath(context);
+        File vcardPath = new File(path);
+        String filename;
+        if (vcardPath.exists() && vcardPath.listFiles().length > 0) {
+            filename = vcardPath.listFiles()[0].getName();
+        } else {
+            filename = String.valueOf(System.currentTimeMillis()) + ".vcf";
+        }
+        saveToDisk(vcard, filename, path, context);
+    }
+
     /**
      * Saves a vcard string to an internal new vcf file.
      *
      * @param vcard    the string to save
      * @param filename the filename of the vcf
+     * @param path     the path of the vcf
      * @param context  the context used to open streams.
      */
-    public static void saveToDisk(String vcard, String filename, Context context) {
+    private static void saveToDisk(String vcard, String filename, String path, Context context) {
         if (TextUtils.isEmpty(vcard) || TextUtils.isEmpty(filename) || context == null) {
             return;
         }
-        String path = peerProfilePath(context);
         File peerProfilesFile = new File(path);
-        if (!peerProfilesFile.exists())
+        if (!peerProfilesFile.exists()) {
             peerProfilesFile.mkdirs();
+        }
         FileOutputStream outputStream;
         try {
-            outputStream = new FileOutputStream(path + "/" + filename);
+            File outputFile = new File(path + File.separator + filename);
+            outputStream = new FileOutputStream(outputFile);
             outputStream.write(vcard.getBytes());
             outputStream.close();
+            Log.d(TAG, "vcard in saveToDisk " + vcard);
         } catch (Exception e) {
-            e.printStackTrace();
+            Log.e(TAG, "Error while saving VCard to disk", e);
+        }
+    }
+
+    public static VCard loadPeerProfileFromDisk(@Nullable String filename, @Nullable Context context) {
+        String path = peerProfilePath(context) + File.separator + filename;
+        return loadFromDisk(path, context);
+    }
+
+    public static VCard loadLocalProfileFromDisk(@Nullable Context context) {
+        VCard vcard = null;
+        String path = localProfilePath(context);
+        File vcardPath = new File(path);
+        if (vcardPath.exists()) {
+            File[] listvCard = vcardPath.listFiles();
+            if (listvCard.length > 0) {
+                vcard = loadFromDisk(listvCard[0].toString(), context);
+            }
         }
+
+        if (vcard == null) {
+            Log.d(TAG, "load default profile");
+            vcard = setupDefaultProfile(context);
+        }
+
+        return vcard;
     }
 
     /**
      * Loads the vcard file from the disk
      *
-     * @param filename the filename of the vcard
-     * @param context  the contact used to open a fileinputstream
+     * @param path    the filename of the vcard
+     * @param context the contact used to open a fileinputstream
      * @return the VCard or null
      */
     @Nullable
-    public static VCard loadFromDisk(@Nullable String filename,@Nullable Context context) {
+    private static VCard loadFromDisk(@Nullable String path, @Nullable Context context) {
         try {
-            if (TextUtils.isEmpty(filename) || context == null) {
+            if (TextUtils.isEmpty(path) || context == null) {
+                Log.d(TAG, "Empty file or error with the context");
                 return null;
             }
 
-            String path = peerProfilePath(context);
-            File vcardPath = new File(path + "/" + filename);
+            File vcardPath = new File(path);
             if (!vcardPath.exists()) {
+                Log.d(TAG, "vcardPath not exist " + vcardPath);
                 return null;
             }
-            VCardReader reader = new VCardReader(new File(path + "/" + filename));
-            VCard vcard = reader.readNext();
-            reader.close();
+            VCard vcard = Ezvcard.parse(vcardPath).first();
+
+            Log.d(TAG, "vcard in loadFromDisk " + Ezvcard.write(vcard).go());
             return vcard;
         } catch (IOException e) {
-            e.printStackTrace();
+            Log.e(TAG, "Error while loading VCard from disk", e);
             return null;
         }
     }
@@ -127,4 +176,15 @@ public class VCardUtils {
     private static String peerProfilePath(Context context) {
         return context.getFilesDir().getAbsolutePath() + File.separator + "peer_profiles";
     }
+
+    private static String localProfilePath(Context context) {
+        return context.getFilesDir().getAbsolutePath() + File.separator + "profiles";
+    }
+
+    private static VCard setupDefaultProfile(Context context) {
+        VCard vcard = new VCard();
+        vcard.setFormattedName(new FormattedName(context.getString(R.string.unknown)));
+        saveLocalProfileToDisk(Ezvcard.write(vcard).version(VCardVersion.V2_1).go(), context);
+        return vcard;
+    }
 }
diff --git a/ring-android/app/src/main/java/cx/ring/views/MenuHeaderView.java b/ring-android/app/src/main/java/cx/ring/views/MenuHeaderView.java
index 67c0ad0ac..9bb916c42 100644
--- a/ring-android/app/src/main/java/cx/ring/views/MenuHeaderView.java
+++ b/ring-android/app/src/main/java/cx/ring/views/MenuHeaderView.java
@@ -19,45 +19,83 @@
  */
 package cx.ring.views;
 
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.net.Uri;
+import android.provider.MediaStore;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.AdapterView.OnItemSelectedListener;
 import android.widget.Button;
+import android.widget.EditText;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.Spinner;
 import android.widget.TextView;
 
+import java.io.ByteArrayOutputStream;
 import java.util.ArrayList;
 import java.util.List;
 
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
 import cx.ring.R;
 import cx.ring.adapters.AccountSelectionAdapter;
 import cx.ring.adapters.ContactDetailsTask;
 import cx.ring.client.AccountWizard;
 import cx.ring.client.HomeActivity;
-import cx.ring.model.CallContact;
 import cx.ring.model.account.Account;
 import cx.ring.service.LocalService;
+import cx.ring.utils.CropImageUtils;
+import cx.ring.utils.VCardUtils;
+import ezvcard.Ezvcard;
+import ezvcard.VCard;
+import ezvcard.VCardVersion;
+import ezvcard.parameter.ImageType;
+import ezvcard.property.FormattedName;
+import ezvcard.property.Photo;
+import ezvcard.property.RawProperty;
 
 public class MenuHeaderView extends FrameLayout {
     private static final String TAG = MenuHeaderView.class.getSimpleName();
 
     private AccountSelectionAdapter mAccountAdapter;
-    private Spinner mSpinnerAccounts;
-    private ImageButton mShareBtn;
-    private Button mNewAccountBtn;
-    private ImageButton mQrImage;
-    private ImageView mUserImage;
-    private TextView mUserName;
-    private CallContact mCurrentlyDisplayedUser;
+    @BindView(R.id.account_selection)
+    Spinner mSpinnerAccounts;
+
+    @BindView(R.id.share_btn)
+    ImageButton mShareBtn;
+
+    @BindView(R.id.addaccount_btn)
+    Button mNewAccountBtn;
+
+    @BindView(R.id.qr_image)
+    ImageButton mQrImage;
+
+    @BindView(R.id.user_photo)
+    ImageView mUserImage;
+
+    @BindView(R.id.user_name)
+    TextView mUserName;
+    private ImageView mProfilePhoto;
+    private VCard mVCardProfile;
 
     public MenuHeaderView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
@@ -87,8 +125,9 @@ public class MenuHeaderView extends FrameLayout {
                     if (!shareUri.isEmpty()) {
                         Bitmap qrBitmap = HomeActivity.QRCodeFragment.encodeStringAsQrBitmap(shareUri, mQrImage.getWidth());
                         mQrImage.setImageBitmap(qrBitmap);
-                    } else
+                    } else {
                         mQrImage.setImageBitmap(null);
+                    }
                 }
 
                 @Override
@@ -102,27 +141,36 @@ public class MenuHeaderView extends FrameLayout {
 
     public void updateUserView() {
         LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        Log.d(TAG, "updateUserView");
         if (null != inflater) {
-            boolean shouldUpdate = true;
-            CallContact user = CallContact.buildUserContact(inflater.getContext());
-            if (null != this.mCurrentlyDisplayedUser && this.mCurrentlyDisplayedUser.equals(user)) {
-                shouldUpdate = false;
-                Log.d(TAG, "User did not change, not updating user view.");
-            }
-            if (shouldUpdate) {
-                this.mCurrentlyDisplayedUser = user;
-                new ContactDetailsTask(inflater.getContext(), mUserImage, user).run();
-                mUserName.setText(user.getDisplayName());
-                Log.d(TAG, "User did change, updating user view.");
+            mVCardProfile = VCardUtils.loadLocalProfileFromDisk(getContext());
+            if (!mVCardProfile.getPhotos().isEmpty()) {
+                Photo tmp = mVCardProfile.getPhotos().get(0);
+                mUserImage.setImageBitmap(CropImageUtils.cropImageToCircle(tmp.getData()));
+            } else {
+                mUserImage.setImageDrawable(ResourcesCompat.getDrawable(getResources(), R.drawable.ic_contact_picture, null));
             }
+            mUserName.setText(mVCardProfile.getFormattedName().getValue());
+            Log.d(TAG, "User did change, updating user view.");
         }
     }
 
+    public void updatePhoto(Uri uriImage) {
+        Bitmap imageProfile = ContactDetailsTask.loadProfilePhotoFromUri(getContext(), uriImage);
+        updatePhoto(imageProfile);
+    }
+
+    public void updatePhoto(Bitmap image) {
+        image = CropImageUtils.cropImageToCircle(image);
+        mProfilePhoto.setImageBitmap(image);
+    }
+
     private void initViews() {
-        LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+        final LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
         View inflatedView = inflater.inflate(R.layout.frag_menu_header, this);
 
-        mNewAccountBtn = (Button) inflatedView.findViewById(R.id.addaccount_btn);
+        ButterKnife.bind(this, inflatedView);
+
         mNewAccountBtn.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -132,27 +180,94 @@ public class MenuHeaderView extends FrameLayout {
 
         mAccountAdapter = new AccountSelectionAdapter(inflater.getContext(), new ArrayList<Account>());
 
-        mShareBtn = (ImageButton) inflatedView.findViewById(R.id.share_btn);
-        mShareBtn.setOnClickListener(new View.OnClickListener() {
+        mSpinnerAccounts.setAdapter(mAccountAdapter);
+
+        mVCardProfile = VCardUtils.loadLocalProfileFromDisk(getContext());
+
+        updateUserView();
+    }
+
+    @OnClick(R.id.profile_container)
+    public void profileContainerClicked() {
+        Log.d(TAG, "Click on the edit profile");
+        AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+        builder.setTitle(R.string.profile);
+
+        LayoutInflater inflater = ((Activity) getContext()).getLayoutInflater();
+        ViewGroup view = (ViewGroup) inflater.inflate(R.layout.dialog_profile, null);
+
+        final EditText editText = (EditText) view.findViewById(R.id.user_name);
+        editText.setText(mUserName.getText());
+        mProfilePhoto = (ImageView) view.findViewById(R.id.profile_photo);
+        mProfilePhoto.setImageDrawable(mUserImage.getDrawable());
+
+        ImageButton cameraView = (ImageButton) view.findViewById(R.id.camera);
+        cameraView.setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
-                Account account = mAccountAdapter.getSelectedAccount();
-                String shareUri = account.getShareURI();
-                Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
-                sharingIntent.setType("text/plain");
-                String shareBody = getContext().getString(R.string.account_share_body, shareUri);
-                sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getContext().getString(R.string.account_contact_me));
-                sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
-                getContext().startActivity(Intent.createChooser(sharingIntent, getContext().getText(R.string.share_via)));
+                boolean hasPermission = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED &&
+                        ContextCompat.checkSelfPermission(getContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+                if (hasPermission) {
+                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+                    ((Activity) getContext()).startActivityForResult(intent, HomeActivity.REQUEST_CODE_PHOTO);
+                } else {
+                    ActivityCompat.requestPermissions((Activity) getContext(),
+                            new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE},
+                            HomeActivity.REQUEST_PERMISSION_CAMERA);
+                }
             }
         });
-        mQrImage = (ImageButton) inflatedView.findViewById(R.id.qr_image);
-        mSpinnerAccounts = (Spinner) inflatedView.findViewById(R.id.account_selection);
-        mSpinnerAccounts.setAdapter(mAccountAdapter);
 
-        mUserImage = (ImageView) inflatedView.findViewById(R.id.user_photo);
-        mUserName = (TextView) inflatedView.findViewById(R.id.user_name);
-        this.updateUserView();
+        ImageButton gallery = (ImageButton) view.findViewById(R.id.gallery);
+        gallery.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                boolean hasPermission = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+                if (hasPermission) {
+                    Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
+                    ((Activity) getContext()).startActivityForResult(intent, HomeActivity.REQUEST_CODE_GALLERY);
+                } else {
+                    ActivityCompat.requestPermissions((Activity) getContext(),
+                            new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
+                            HomeActivity.REQUEST_PERMISSION_READ_STORAGE);
+                }
+            }
+        });
+
+        builder.setView(view);
+
+        builder.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                dialog.cancel();
+            }
+        });
+
+        builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+            @Override
+            public void onClick(DialogInterface dialog, int which) {
+                String username = editText.getText().toString().trim();
+                if (username.isEmpty()) {
+                    username = getContext().getString(R.string.unknown);
+                }
+                mVCardProfile.setFormattedName(new FormattedName(username));
+
+                if (mProfilePhoto.getDrawable() != ResourcesCompat.getDrawable(getResources(), R.drawable.ic_contact_picture, null)) {
+                    Bitmap bmp = ((BitmapDrawable) mProfilePhoto.getDrawable()).getBitmap();
+                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
+                    bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
+                    Photo photo = new Photo(stream.toByteArray(), ImageType.PNG);
+                    mVCardProfile.removeProperties(Photo.class);
+                    mVCardProfile.removeProperties(RawProperty.class);
+                    mVCardProfile.addPhoto(photo);
+                }
+
+                VCardUtils.saveLocalProfileToDisk(Ezvcard.write(mVCardProfile).version(VCardVersion.V2_1).go(), getContext());
+                updateUserView();
+            }
+        });
+
+        builder.show();
     }
 
     public Account getSelectedAccount() {
@@ -179,4 +294,20 @@ public class MenuHeaderView extends FrameLayout {
         mQrImage.setOnClickListener(l);
     }
 
+    /**
+     * Click listeners
+     */
+
+    @OnClick(R.id.share_btn)
+    public void shareButtonClicked() {
+        Account account = mAccountAdapter.getSelectedAccount();
+        String shareUri = account.getShareURI();
+        Intent sharingIntent = new Intent(android.content.Intent.ACTION_SEND);
+        sharingIntent.setType("text/plain");
+        String shareBody = getContext().getString(R.string.account_share_body, shareUri);
+        sharingIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, getContext().getString(R.string.account_contact_me));
+        sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, shareBody);
+        getContext().startActivity(Intent.createChooser(sharingIntent, getContext().getText(R.string.share_via)));
+    }
+
 }
diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_insert_photo.png b/ring-android/app/src/main/res/drawable-hdpi/ic_insert_photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..7976c2cc5d0b9512be401f528532331dc25fbe1d
GIT binary patch
literal 425
zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F<Zz1)eUBAr-gY&h+*^>>$#*
zTA@i#v__|7a&LgsJ;tN0_6zI}_!iz4W8J=sQ!i%Kk*06=tMno`CV4J9Jj3UK%#J1f
z7pnK2+3TB@X0k}5t0=5cX1<E&rUM&~6kbjB@H=ATdpSAzb4UNW-s?wp3%0~mvY%P+
zo}6dIwA7hl=}C??F<eXn4Gb&_42&EO3`_zCynGgG?2^_xU_9?_xy||8M_(reFzk4h
zZ&1JE-(LeB|BpA9b29`V$?g7`FLS)Un8mT+rZz*5t*57m>&D*i{i{A3XwF~p>BYC^
zQU=XVozr|;#V>4+c%QBL#u%T!5+okV5bF7)vQFi+g6QY<``*9b{`AsI_SFo!s)dY?
z<1O+zlb`SUdF$6fDF&ar=2MU4Ti#`OQgwNb@81MghwUmpSFiA!F-&8y`2GHCM5z4@
zh8?f60(Y*H|6yQm(Qr`z-Kk02oEbLvGNuGCtO|cy-0b}BgWG>LCPn3okHs#ZxnbiP
d7^41*ahZO&sQFdLBnAcs22WQ%mvv4FO#r|3xK027

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-hdpi/ic_photo_camera.png b/ring-android/app/src/main/res/drawable-hdpi/ic_photo_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..b2666709880229a24596ca6fe58d8e91ce82eed6
GIT binary patch
literal 750
zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F<Y|TRmMILn>~)jfnP+b`-c4
z6`iy4);00lUhzrHa%cZuy5W%=y1`D%QJ!g$Nbb}s$M`)7%ubmbzg}V&{-3*_zbuhM
z>fF&C?l&40XK%LN{rT;gb8m8@XRCTo(}~<9GU3zDnsTeTc4tzI!t-V%YYKBc?LPXb
zSmVptwV#-dgrAZMmGiP`G&;R9WYs%ShSUSztxgM9r*gR|O*|fvBf(vJDsA)RULJ<*
z2eZPTwu)wLxmQ}OKKUf)I$v3)ooqi2o#$guU3YTpPS1TYyuxSLZ{EF~wDfS3{QrO1
zQI(0k!S-S++BqEV8aFhg{b&Ez+*&{R=Ckbz#rHTFW=RPy5$ZoKE4L?T<(CO(%=Rzb
zf5>nl^Ma&bTu%2_7wx=LczMnn<_Xs)M1Fm>NSB%6`i>N*q@c;4`X1YKxh+0_BF9|K
zLA6+tjX`>2#G94VR)?+T7wSBsq04kH{LHuNZ!Zg{G`?9D$Pn;=CGT_C=_f@yr~lYj
z7yB<DvYa=(UA)RBweo;3Qv^%-t8DgL@w0wzdlh|6&o%N~;-%I}N#7s){MB>#oFydu
zTru?$ldaQ4SN_1&i=Gz5RrNAXP!#^Z-In3TrJ^&!#~4n0;NY(LE^z(2^tv}`3<@Iq
z1Rtm0QBj}zW!Y94qwfud3_3OjTjoBVnV8s`EdKnN#GUoiGLLW9oZMc~J~K?ad9G}>
zSm%ZEzWe-ACaD`acdId8S#ZF8X6wgX7Kb{I@}R3%1-)Hn_I}*|WLrko)6hraMz1DJ
zd1}g78~CMn^37@2d7l>Xr8*Z%w<_5So@bwZ*Cq9a%cW}fnH#P$rx#3()VOwiiO<`^
zJ1qNnQymxo`Ps8xvS9W3J&luYhWQ&juk*fYt*aS*@cXV6%TxbGJWz01FZ8?aUhAhi
z?hn!TYWMj+wP8*;v`|6qhVe(I<yt#zRZgw(sRN~|l#`Q|{NMOz66YDmJKGr;7#KWV
L{an^LB{Ts5%XnV0

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_insert_photo.png b/ring-android/app/src/main/res/drawable-mdpi/ic_insert_photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..fb22f9f1cbf5448dbba0d61d9c0a6deb5494561f
GIT binary patch
literal 298
zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}A3a?hLn>}1CrGd^X5gJM
zd-i8ZY3b)oA5DaXC(pccAYgNmLz-|2!-=A-$R5_`0*@Oqw-hm)$Z6tRu<-rCiYttZ
z`6NmYtYFS~#*m<SJuM|Ah4F|n+abna9$wz33x73dK4O@_-#Bds!^@WuPu3>>a&~r}
z*|;#kEtlPgF-nJhYPrMYZfEnu$BwxPyy&pYvN|BO_E=9-S2**{H8;*MS~>7XS1BB4
zl(5Um$Vgz4n#Z;!rlleBpok?yhNq~Wyb$w-rR<Aj8M>T}jg1q%W>>s;nfO86rFo&}
zG-qvghQ&&=SEN~<5WJE%tyxEXRiqu4n+yZP<a?>dv%I-Lp~2wk>gTe~DWM4fAkA%N

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-mdpi/ic_photo_camera.png b/ring-android/app/src/main/res/drawable-mdpi/ic_photo_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..111db39b1f62a327000f7a3c762abb2ee61c8169
GIT binary patch
literal 479
zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANNf%RF5iLn>~)4L$93#6jRr
zs7OsrO*j7+Igc}W^JdImq;AV$soBA9tI@GRT~t-)NU)38lo$7-ScN52)C%WvI7x9$
zdO2_1ta<ZhhOORs?B8U`wvD&mmI<HUWcSHoe&#IgtrKo<+so7Rul&iRj(x9pc{>aL
z+-KjYQmtU~zQjsg(%@T&o1Wsd=S*j|p1-q{NuYvl&;N!sLNg}s^k!A~5o)?~i*0@=
zONj5Zg8i(MR(%fP=xBVaz>#*yKt}ZkuVCYctLh9Ywh5C814Vi#%y?ja{6(@)EK^d+
zN1bGw^S4Ekgc{|}{b#G~dwfPTG%15o;fCP%gH{~#KUX<j|7t3@d36i}$Ft4JQ3A!M
zBsyXP^c-$IKhDI+A6c&<-tft<lk1KM$Bwj~Wto>xu`bxj^RV1u%CiZ}HTJ4jC9Lj~
zOSs;@anGLXA8jYpFz=K9lKAzptNTZDrl2)%7!-4leLT9kHL+Uk=ohmj{?A%V-l!dU
z)n9pS|GCE!Tju;Z8tStrVd`{&VwVU1yOO=`HCfF|`sukYsPVTH>z%1w59aoLy0>Z#
mOR-`4w>Xn)5y_isnT$4Y)w9lfyoiB;fx*+&&t;ucLK6V;Bh*v?

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_insert_photo.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_insert_photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..0d166021d0469db1eab14a5f5002a18b31b30126
GIT binary patch
literal 613
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS$MbUa-gLn>~)o#pL)*g>Q<
zRKzh>qsu$ob&AJjwOq|8lShx`ITk5xlNMj3(4{S2s{Cj%t7Fdpwa=Zn7H%=jR5MII
z*SOG7_3hsKXJ*FDu{LuH6mbm{dAj~%(8?{17dPmvZoPQ0)ZEuOsVI5%>}8oAXS61-
zzp?G_ylqR`d5`=GYn$RQVdizsN`01cRTs7k7j!H8bze<n>{xHYb|Hvy!D7RPJ}&N(
zQaKg}2JC17GsEp|_J(D;r>joLFsS_NExeLrrv71<T75c~!^Q|3_e;im-yi?Fa7~8~
zgTtcpTcdIh{{GvRc;?|=@3jmkdJ@&P7uHIv{q$P8$x5TG#?HR``}{2o7d~&8bNXQ^
z>-SrZ^R%a*w(fiE^J|X(jm0t-Rrwa&KRhE;_<MY9L#^V@%MI;Y{BAVQa$)58Wbn?H
z@zlKYXYMm!N}3RH=lm`|RR(+h-SdleHsAezhWl91jEW8NI_)d@g_sp)&zs-Q-FnD|
zY5yl1<?6%NH2!iWzG}X~cw$Y`9F{q@H_yt)%FBCfG|q}~eBG?VaEJBlh80n3*B$<z
zzB4Az$_peK!PwD$SY>^b<Ev&1#w`zDpWXlN-mkTG9jpx%5~nNdyV^y|H}J0IPhw<v
zWFBa{CaCT!<9|gbhMY;K@@C6?YvUJXcqO&Iq5A~GlDi?d{ETmFY46{BsbY;V%hI!#
xY&o2Q-%NN`R{3#{PW9EdpFmOS`q%$2LyJIl`PI+SH4F?444$rjF6*2UngABf3<3ZE

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-xhdpi/ic_photo_camera.png b/ring-android/app/src/main/res/drawable-xhdpi/ic_photo_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..0e75cb7b513ae004534754afa8227f1286171332
GIT binary patch
literal 1013
zcmeAS@N?(olHy`uVBq!ia0y~yU~m9o4mJh`hE<t`_ZS$M_jtNEhE&{o8}&O|*ij(w
zidI<717X(rA&XfqtK?2{%j{gSqPAfh+v<NxTkN!svU_At%@X;$(C+2okpI#9*ZU+!
zp0C<_w`hAIdz<&Svgc|0=FXmbbMG`U|9Li*pPrnYtnNQ=O>la8x^%^w)vI5Ji#&@s
zRdDG?j@j<D0UCEYvi2|?Dm_(n>WA0TO`@m1H}0R!q+s+*<Fe@XeAak|`mG6ny2NV^
zZuPoZcysC_9*4%}r9m$%Hb&TdG;dop{bK&TX&<>bKL4!w_bBW9vb-ySkK9hp`dINn
zQKL{eru~Ebw%*MgmxO-E?r@vCwt*q!yTcEcNxb|FO}qR8%99xcPO>X(c-Zxki-Y%7
z9&-c#I?44P+@%BeKGb69;XKA*Ve|X+91Dgci^X=XX?))DeCgVYQy3Kb{!B?p6RP38
z_%lRr`t8uQVdfKmO25s#=X~Gu+tV3oj19)WSzZ^Z&h^XxfBo|f_J%o!XDpX_ZNgym
zStsq=_19l{6X&1YC)51VdD%Pxb%ySk_S?5_Z)ZuJe&%&$zmDZU`AF&aj>i^ssvluu
zm=gEk4Qoh&=E<w4Uzj(&PB!{=@6dmt_b(khQZ0RE`Y||6XSLU!ewzKu%_R>XOn1yQ
zy?r29BdcrI^#HM7HvONhjxZiDk2$~ky$x^D)Q;-SipS0~HG9wWN!u(XeBqw*rpdew
zX9{Mo+H>a5bJG()UHk(apR5x|@!8Gbuz#A@`=__|g;{R+-x_z;aK$bGmdmdyBqnuF
zdT6M2;K3TL)w_59?%|f(5G-w<vq^E)sYyMCHyI4JUcK{epQE1Ps#A+{#BCOYO50~m
z@Vs}~JVuzmL2ictGaIkX0)PG$dKCvEeP3P<42n#vTO+mgy5{RcS_X@jZ9eg+%;6(@
z_Q^*}I;1YIIxh0=ZkeKRjd%D}5&y++eI*2r{E&3{Gg)hb#`5ol(f3-z6ZqU>R$k4L
zzWSo~8`D8f!#cL>dv?tV5IYvPG)KKlF(y9oSl=se2_6fj^@p7<oaCSG!*cepR>dR+
zS?BrJ{Y0F*r0o~oW}7QpwAuMpbKOIu-)wXLdj77s(mU~nnM)Xx<g1@|)s_cMN!+9@
z@_U*@>^w%_^4C{8)^ksH=&612Y1)jcgrD;&YiDe>S}E6_awh2V9BvK)8*Yi`ZSL;w
z?rn|}&)m<MTwZ&y?O~^_#LxHsZF86pEVDlLac%=I!!lX6gi{g>DoT3;zduPfn>RDH
z-;g2b?A_RY{`uD~SA3XjkUnvt-6SQBh-FnTgE;?tWc@5?HP$~C{X6IkC__PV)^fGQ
a_tW0XY|-8zsKLO%z~JfX=d#Wzp$P!|CFMc@

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_insert_photo.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_insert_photo.png
new file mode 100644
index 0000000000000000000000000000000000000000..ad92e792149c367b90603dc485462df98a857a97
GIT binary patch
literal 797
zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^S>o_M-AhE&{oJJ&J)vVlY!
zbCS#rK^2D$g>J5yv#+f^;-F=nU%tvK^gjcKg!&E^wZasSoTNX|cHWG>k-|dig>Rlz
zF7;o%&2;1b<KME)ONCfC6k7zGAcW|Ype+5aMORadET`_6;kSJMZkb(uLh+?@4;$3n
ziF&kDz&dC_>2=|G;!F>XGHa}_P1gUo@%%~uuU*2c1%BQ8WL$Ol)U6r*hqm*(huJtz
zR(m*e;}*kz^C!LV(f^Uee1P@jxp|9j86-@aE6hM9$gsH<D3Z%?!LR1p?RK{2gSnNm
z%m?1BoBDq9%{T9+uD@nK<^ErmhNZgBpFT(K`m^}r3rhx_mY`P_>9eo0F`RuDQmO5_
zcVcL}+TR1=3{|&ZZ(8=Gp8IgY_5)$7-ToY~W|-9C-+lB^n9OnWr(buks@`(!@yE%F
zN;41f?_t`II<08=DLtRc_uS37+h@lyUD!P3VS$B*e7)WL`_uQ&pK3dA%b%^DwXeQ0
z&u5T%y5-dJOS9^y#^!|HsEVE~U%}`Q^?LT6NUup#!dd&v)&5tn&)WOy6Z3OMk-1A%
zY!g#A-7pH3t$SeAP|Ua?{!2_%n#%ji`OFV?oZEHvA^SRJ3yo^KyV2Xu_Wav^BkMGO
z4BG}X+vP#88daXPpEO9j%|D0bL;1$gr+cbStx9)G6_4Kb_kbnC%cEgOm&kbS;n6v9
zg?|pygsnF|xvJGYkZM?bpm^$6=5z*;$ef>xjOre+G%QQk+!lI_KZYS7YvWZ@`vS%T
zju!$&KQhO!@A!CB2OL=+7#Pg+a(^CM`|BaAinVc)^lGQPdvo{xs=sT&P}}|ONsxl)
z{(lD_D@V`oy}*5IN9kWt`3wC9mWnU`pZV^5mD|rvpv3>h=YlC$YBOzrX8K=fy}+;k
q;&k$wFFEeg>$V!oBT`|hQ$544%|`Q<dmh&SS?%fS=d#Wzp$Pzo$yrbU

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/drawable-xxhdpi/ic_photo_camera.png b/ring-android/app/src/main/res/drawable-xxhdpi/ic_photo_camera.png
new file mode 100644
index 0000000000000000000000000000000000000000..89b4678d36afa961aebf8d0eed0fc81bd78bfc8e
GIT binary patch
literal 1699
zcmeAS@N?(olHy`uVBq!ia0y~yU`POA4mJh`hDS5XEf^TsvOHZJLn>~)ozv|v6Do47
z`<nQ^#cm%RYZq*BO`Lw5sjGBte?+#hyrXdQ)iABCy<uB|0<tyjHe3j3+<N8zwfEEK
zXyv|tbI8c}b<ufGzcQoE*5%)7zR#<kw>i0eGIkJr?#lJ+-<SR9|M&0T{>v%vPVS98
zbh0U5cdD1|-o1OpOKL2c8T5+tk0t0#|6%p&)vH~nTmIMHYT{=ooV9V+?%nT8POhK*
z^Oubt!@3k+hU5<QBX=*ZXF2;Py8Gy(IL>FEb*}iCmM%HJm?^QpFTXSB$L7tO&0n71
z?#*#rrN=AoQ2g<!jg5`TL9YF;3!@e>I~f00<a|)l+)^JZyEaRxMqBiU!{&1j%s4-m
z{+&44YM$`<**muj#)<r&+J0~SHu(=cR&h6u6rX=xzW>!{?K4rnOWY^At~(?zC%12L
zfX1E^$2uQ;W!HOIymsQD9EO7Wu6n`xmBspslkcS29lE|?vQ&*FTjTW`7bYH5QFyXR
zXi1UWMV=qWTI+c^w9OQ68?ieu@9lMe*|gtL_u)bLGlvUj86JuKa6K~Q*B?cGhG{no
zVqYBWtyJY_ShTV16{BLm?Sa<VR#^sih1pAU&&kdER{1f~m~B~a`Wu<+&wl1DzVc)n
z(}S!P4C{lB$3OV>`p@SdN-dL4KmFu4Bl%q6*?q=|$DH0R?8{|lNRvIdD{147_4A%^
zddl5hp5r;Mi+RJzHKI-OGBPzYB)o0)_OWtDfAnK?vU$poqZ4haws3ENhR8c_<!we*
zR#nE|m6(?q@};kxG)ss<_>WLkT}H->lYM=Cw=dP@y^GYEI&V|Slb|pM6Kw|XUn<K#
z`1C%E-tzL^xgIxL!R~g;XEN8bQn#CgvH#?jzRYN#{XAP|MpTo$q~y+c?UeqOf3sMA
zEd0@7c>H(i{JS;F#ROJZeAM(l=EBcl_Q%8Y;p9Ej8}1ug@1J#HLs?qqji^m~m!9&f
znE4>`3PXh7!(J8R$uo1NavM+EzgDW<Xn*3_ebsHEiK`VemPLu)^E$&Ib3Hx%#4SVN
zB7x5ro%YW5oAiMB#B>RctN&-r+!bA;<i#*!<)pKAX6GjqsO+)xuE}@xUp&dLt6On`
zWc2wDUb7y4O^agOpIDT%oIS06_K#pE0nMIb&foIhnopDyW@XK}pUm=L#lIiT3w_1!
zt`RNJJ9@QD?6l?<&CfiKK1wiZl$Vv=lf6;FxvqBlrp}0k6BL%TRj~Qoc)>8?U9HuV
zkKew0DGAJ<+_=+Pz4UX;3Fl*sJ$wvi2PXVGu5)&S<KwFrx(-)%^D)f+t2o!j>t+%g
zgO1nLJ>r{od=Op4+%R)G!-3<Ex+NJ30vT$i2t2zXDa+6z?!+K){UirN*j08#hKSya
zj0(H|%xkfI;2<`&{0(DBQF{$z?C$Gp7ySRl_HCEw6lMk&WzLRPrh>>Hj3VdT7>@HR
z$Oy_Z%zD`_Y!&>Wg<q)3Pp~19@n2ZR`G=AvZ#PRc7d~e_v~EKAsmkm-(SC_CUQvA!
z`ZsvhG?F7Hd)8bG`1|u=fJIa9#O&x$*Kg6UzOg$yBy&V8KOeQ`XG3Ap7R9R$liGYF
zUoDms(7F24c&;|*-vhcr|2AJ+Dw*rlw&di$C2K^Vck*5pmQ-c(GShu|?}~I}UZrUi
zgHOQaGnbas7_4GWj7&ebQz|r1boN(vfjukdPFpg^V5Oh`scg{;nI2UeqLSu4knO!Y
zYwO<z#mj{}Um1&Y7{0#z!FhU<s@jv4YZOFhx-R$M9Cl1MqG{RQuvmd#bJX(pbu*{l
z$$K7^eU)|QRpWWC2iC9De%$fjV6x_xQ;+`g-U<GFGFj^VWSu16+sn+RG)Qii($?s`
z74R-mOjJAdO1884vB@v0YFe_SGj&Uk=I?BO7k+5@hgUDo9?#d%)NfC6+sbipGUH;0
z)4!Qtg}aMZuRJ~{(kfL)!pmpf`w0(auCLKdJ`*QbbW!M2xJ<*^=8Xw;2d0Q#o3|n8
zGpDhp|I3O~`!+wYO}eN0k|D>NVUPE|gL6Oi)}6lkKk`S;k4lCQ!M6mD<bK&UMgDVu
z<NI_`_smbz%@XzY&$eWkGUf7*2df|4>R4ww_5CRshC9`(@6Ra{_{Y0^{=#P(+qbI=
z-BFtD;=cQ8%(`P+45Q<XYcsmf)V#iM=DX>M1-_r0o8>p<9rof;Si?Q>%!~i>i*6}>
j|K4V0i4~ZN{$-w0@n1Q-#v+1&fq}u()z4*}Q$iB})LJg$

literal 0
HcmV?d00001

diff --git a/ring-android/app/src/main/res/layout/dialog_profile.xml b/ring-android/app/src/main/res/layout/dialog_profile.xml
new file mode 100644
index 000000000..5104ea29b
--- /dev/null
+++ b/ring-android/app/src/main/res/layout/dialog_profile.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout 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:layout_gravity="center"
+    android:orientation="vertical"
+    android:padding="20dp">
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="@string/profile_message_warning" />
+
+    <RelativeLayout
+        android:id="@+id/profile_container"
+        android:layout_width="150dp"
+        android:layout_height="180dp"
+        android:layout_gravity="center">
+
+        <ImageView
+            android:id="@+id/profile_photo"
+            android:layout_width="120dp"
+            android:layout_height="120dp"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            android:layout_margin="10dp"
+            android:scaleType="fitCenter"
+            tools:src="@drawable/ic_contact_picture" />
+
+        <android.support.design.widget.FloatingActionButton
+            android:id="@+id/camera"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignTop="@+id/anchor"
+            android:layout_toRightOf="@+id/anchor"
+            android:contentDescription="@string/take_a_photo"
+            android:src="@drawable/ic_photo_camera"
+            android:text="@string/take_a_photo"
+            app:backgroundTint="@color/light"
+            app:rippleColor="@android:color/white" />
+
+        <View
+            android:id="@+id/anchor"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_alignBottom="@+id/profile_photo"
+            android:layout_centerHorizontal="true" />
+
+        <android.support.design.widget.FloatingActionButton
+            android:id="@+id/gallery"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignTop="@+id/anchor"
+            android:layout_toLeftOf="@+id/anchor"
+            android:contentDescription="@string/open_the_gallery"
+            android:src="@drawable/ic_insert_photo"
+            android:text="@string/open_the_gallery"
+            app:backgroundTint="@color/light"
+            app:rippleColor="@android:color/white" />
+
+    </RelativeLayout>
+
+    <EditText
+        android:id="@+id/user_name"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:inputType="textCapWords"
+        android:maxLines="1"
+        android:textAlignment="center"
+        android:hint="@string/unknowm_if_empty"
+        tools:text="Username" />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/layout/frag_menu_header.xml b/ring-android/app/src/main/res/layout/frag_menu_header.xml
index 1c64429b5..e52effcc1 100644
--- a/ring-android/app/src/main/res/layout/frag_menu_header.xml
+++ b/ring-android/app/src/main/res/layout/frag_menu_header.xml
@@ -1,40 +1,66 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout 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="wrap_content"
     android:background="@color/color_primary_dark"
     android:paddingLeft="16dp"
     android:paddingRight="16dp"
-    android:paddingTop="40dp"
-    android:theme="@style/MenuHeader"
-    tools:showIn="@layout/menuheader">
+    android:paddingTop="30dp"
+    android:theme="@style/MenuHeader">
 
-    <ImageView
-        android:id="@+id/user_photo"
-        android:layout_width="70dp"
-        android:layout_height="70dp"
-        android:layout_alignParentTop="true"
-        android:layout_centerHorizontal="true" />
+    <RelativeLayout
+        android:id="@+id/profile_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content">
 
-    <TextView
-        android:id="@+id/user_name"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_below="@+id/user_photo"
-        android:layout_centerHorizontal="true"
-        android:layout_marginBottom="8dp"
-        android:layout_marginTop="8dp"
-        android:singleLine="true"
-        android:textColor="@color/white"
-        android:textSize="20sp"
-        android:textStyle="bold" />
+        <ImageView
+            android:id="@+id/user_photo"
+            android:layout_width="80dp"
+            android:layout_height="80dp"
+            android:layout_alignParentTop="true"
+            android:layout_centerHorizontal="true"
+            tools:src="@drawable/ic_contact_picture" />
+
+        <View
+            android:id="@+id/anchor"
+            android:layout_width="20dp"
+            android:layout_height="20dp"
+            android:layout_alignBottom="@+id/user_photo"
+            android:layout_centerHorizontal="true" />
+
+        <android.support.design.widget.FloatingActionButton
+            android:id="@+id/edit_profile"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignTop="@+id/anchor"
+            android:layout_toRightOf="@+id/anchor"
+            android:src="@drawable/ic_action_edit"
+            app:backgroundTint="@color/transparent_light"
+            app:fabSize="mini" />
+
+        <TextView
+            android:id="@+id/user_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/edit_profile"
+            android:layout_centerHorizontal="true"
+            android:layout_marginBottom="8dp"
+            android:layout_marginTop="8dp"
+            android:maxLines="1"
+            android:textColor="@color/white"
+            android:textSize="20sp"
+            android:textStyle="bold"
+            tools:text="Username" />
+
+    </RelativeLayout>
 
     <ImageButton
         android:id="@+id/qr_image"
         android:layout_width="85dp"
         android:layout_height="85dp"
-        android:layout_below="@+id/user_name"
+        android:layout_below="@+id/profile_container"
         android:layout_centerHorizontal="true"
         android:background="@null" />
 
@@ -73,7 +99,7 @@
         android:layout_alignParentLeft="true"
         android:layout_alignParentRight="true"
         android:layout_alignParentStart="true"
-        android:layout_below="@+id/user_name"
+        android:layout_below="@+id/profile_container"
         android:text="@string/action_create"
         android:visibility="gone" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/ring-android/app/src/main/res/values/strings.xml b/ring-android/app/src/main/res/values/strings.xml
index 9149e88ef..00aa5f796 100644
--- a/ring-android/app/src/main/res/values/strings.xml
+++ b/ring-android/app/src/main/res/values/strings.xml
@@ -168,4 +168,12 @@ along with this program; if not, write to the Free Software
     <string name="prompt_new_password_repeat">Repeat new password</string>
     <string name="account_device_updated_message">You have successfully updated your Ring account.</string>
 
+    <!-- MenuHeaderView -->
+    <string name="profile">My profile</string>
+    <string name="profile_message_warning">This is shared with your contacts</string>
+    <string name="open_the_gallery">Open the gallery</string>
+    <string name="take_a_photo">Take a photo</string>
+    <string name="unknowm_if_empty">Unknown if empty</string>
+    <string name="unknown">Unknown</string>
+
 </resources>
-- 
GitLab