1 /*
2  * Copyright (C) 2009 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.contacts.editor;
18 
19 import android.content.Context;
20 import android.database.Cursor;
21 import android.graphics.Bitmap;
22 import android.net.Uri;
23 import android.provider.ContactsContract.CommonDataKinds.Photo;
24 import android.provider.ContactsContract.Data;
25 import android.text.TextUtils;
26 import android.util.AttributeSet;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.widget.ImageView;
30 import android.widget.LinearLayout;
31 import android.widget.TextView;
32 
33 import com.android.contacts.R;
34 import com.android.contacts.common.model.RawContactDelta;
35 import com.android.contacts.common.model.ValuesDelta;
36 import com.android.contacts.common.model.RawContactModifier;
37 import com.android.contacts.common.model.account.AccountType;
38 import com.android.contacts.common.model.account.AccountType.EditType;
39 import com.android.contacts.common.model.account.AccountWithDataSet;
40 
41 /**
42  * Base view that provides common code for the editor interaction for a specific
43  * RawContact represented through an {@link RawContactDelta}.
44  * <p>
45  * Internal updates are performed against {@link ValuesDelta} so that the
46  * source {@link RawContact} can be swapped out. Any state-based changes, such as
47  * adding {@link Data} rows or changing {@link EditType}, are performed through
48  * {@link RawContactModifier} to ensure that {@link AccountType} are enforced.
49  */
50 public abstract class BaseRawContactEditorView extends LinearLayout {
51 
52     private PhotoEditorView mPhoto;
53 
54     private View mAccountHeaderContainer;
55     private ImageView mExpandAccountButton;
56     private LinearLayout mCollapsibleSection;
57     private TextView mAccountName;
58     private TextView mAccountType;
59 
60     protected Listener mListener;
61 
62     public interface Listener {
onExternalEditorRequest(AccountWithDataSet account, Uri uri)63         void onExternalEditorRequest(AccountWithDataSet account, Uri uri);
onEditorExpansionChanged()64         void onEditorExpansionChanged();
65     }
66 
BaseRawContactEditorView(Context context)67     public BaseRawContactEditorView(Context context) {
68         super(context);
69     }
70 
BaseRawContactEditorView(Context context, AttributeSet attrs)71     public BaseRawContactEditorView(Context context, AttributeSet attrs) {
72         super(context, attrs);
73     }
74 
75     @Override
onFinishInflate()76     protected void onFinishInflate() {
77         super.onFinishInflate();
78 
79         mPhoto = (PhotoEditorView)findViewById(R.id.edit_photo);
80         mPhoto.setEnabled(isEnabled());
81 
82         mAccountHeaderContainer = findViewById(R.id.account_header_container);
83         mExpandAccountButton = (ImageView) findViewById(R.id.expand_account_button);
84         mCollapsibleSection = (LinearLayout) findViewById(R.id.collapsable_section);
85         mAccountName = (TextView) findViewById(R.id.account_name);
86         mAccountType = (TextView) findViewById(R.id.account_type);
87 
88         setCollapsed(false);
89         setCollapsible(true);
90     }
91 
setGroupMetaData(Cursor groupMetaData)92     public void setGroupMetaData(Cursor groupMetaData) {
93     }
94 
95 
setListener(Listener listener)96     public void setListener(Listener listener) {
97         mListener = listener;
98     }
99 
100     /**
101      * Assign the given {@link Bitmap} to the internal {@link PhotoEditorView}
102      * in order to update the {@link RawContactDelta} currently being edited.
103      */
setPhotoEntry(Bitmap bitmap)104     public void setPhotoEntry(Bitmap bitmap) {
105         mPhoto.setPhotoEntry(bitmap);
106     }
107 
108     /**
109      * Assign the given photo {@link Uri} to UI of the {@link PhotoEditorView}, so that it can
110      * display a full sized photo.
111      */
setFullSizedPhoto(Uri uri)112     public void setFullSizedPhoto(Uri uri) {
113         mPhoto.setFullSizedPhoto(uri);
114     }
115 
setHasPhotoEditor(boolean hasPhotoEditor)116     protected void setHasPhotoEditor(boolean hasPhotoEditor) {
117         mPhoto.setVisibility(hasPhotoEditor ? View.VISIBLE : View.GONE);
118     }
119 
120     /**
121      * Return true if internal {@link PhotoEditorView} has a {@link Photo} set.
122      */
hasSetPhoto()123     public boolean hasSetPhoto() {
124         return mPhoto.hasSetPhoto();
125     }
126 
getPhotoEditor()127     public PhotoEditorView getPhotoEditor() {
128         return mPhoto;
129     }
130 
131     /**
132      * @return the RawContact ID that this editor is editing.
133      */
getRawContactId()134     public abstract long getRawContactId();
135 
136     /**
137      * If {@param isCollapsible} is TRUE, then this editor can be collapsed by clicking on its
138      * account header.
139      */
setCollapsible(boolean isCollapsible)140     public void setCollapsible(boolean isCollapsible) {
141         if (isCollapsible) {
142             mAccountHeaderContainer.setOnClickListener(new OnClickListener() {
143                 @Override
144                 public void onClick(View v) {
145                     final int startingHeight = mCollapsibleSection.getMeasuredHeight();
146                     final boolean isCollapsed = isCollapsed();
147                     setCollapsed(!isCollapsed);
148                     // The slideAndFadeIn animation only looks good when collapsing. For expanding,
149                     // it looks like the editor is loading sluggishly. I tried animating the
150                     // clipping bounds instead of the alpha value. But because the editors are very
151                     // tall, this animation looked very similar to doing no animation at all. It
152                     // wasn't worth the significant additional complexity.
153                     if (!isCollapsed) {
154                         EditorAnimator.getInstance().slideAndFadeIn(mCollapsibleSection,
155                                 startingHeight);
156                         // We want to place the focus near the top of the screen now that a
157                         // potentially focused editor is being collapsed.
158                         EditorAnimator.placeFocusAtTopOfScreenAfterReLayout(mCollapsibleSection);
159                     } else {
160                         // When expanding we should scroll the expanded view onto the screen.
161                         // Otherwise, user's may not notice that any expansion happened.
162                         EditorAnimator.getInstance().scrollViewToTop(mAccountHeaderContainer);
163                         mCollapsibleSection.requestFocus();
164                     }
165                     if (mListener != null) {
166                         mListener.onEditorExpansionChanged();
167                     }
168                     updateAccountHeaderContentDescription();
169                 }
170             });
171             mExpandAccountButton.setVisibility(View.VISIBLE);
172             mAccountHeaderContainer.setClickable(true);
173         } else {
174             mAccountHeaderContainer.setOnClickListener(null);
175             mExpandAccountButton.setVisibility(View.GONE);
176             mAccountHeaderContainer.setClickable(false);
177         }
178     }
179 
isCollapsed()180     public boolean isCollapsed() {
181         return mCollapsibleSection.getLayoutParams().height == 0;
182     }
183 
setCollapsed(boolean isCollapsed)184     public void setCollapsed(boolean isCollapsed) {
185         final LinearLayout.LayoutParams params
186                 = (LayoutParams) mCollapsibleSection.getLayoutParams();
187         if (isCollapsed) {
188             params.height = 0;
189             mCollapsibleSection.setLayoutParams(params);
190             mExpandAccountButton.setImageResource(R.drawable.ic_menu_expander_minimized_holo_light);
191         } else {
192             params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
193             mCollapsibleSection.setLayoutParams(params);
194             mExpandAccountButton.setImageResource(R.drawable.ic_menu_expander_maximized_holo_light);
195         }
196     }
197 
updateAccountHeaderContentDescription()198     protected void updateAccountHeaderContentDescription() {
199         final StringBuilder builder = new StringBuilder();
200         builder.append(EditorUiUtils.getAccountInfoContentDescription(
201                 mAccountName.getText(), mAccountType.getText()));
202         if (mExpandAccountButton.getVisibility() == View.VISIBLE) {
203             builder.append(getResources().getString(isCollapsed()
204                     ? R.string.content_description_expand_editor
205                     : R.string.content_description_collapse_editor));
206         }
207         mAccountHeaderContainer.setContentDescription(builder);
208     }
209 
210     /**
211      * Set the internal state for this view, given a current
212      * {@link RawContactDelta} state and the {@link AccountType} that
213      * apply to that state.
214      */
setState(RawContactDelta state, AccountType source, ViewIdGenerator vig, boolean isProfile)215     public abstract void setState(RawContactDelta state, AccountType source, ViewIdGenerator vig,
216             boolean isProfile);
217 }
218