1 /*
2  * Copyright (C) 2016 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 package com.android.contacts.editor;
17 
18 import android.content.Context;
19 import android.os.Bundle;
20 import androidx.annotation.NonNull;
21 import androidx.annotation.StringRes;
22 import android.view.View;
23 import android.widget.AdapterView;
24 import android.widget.ImageView;
25 import android.widget.ListPopupWindow;
26 import android.widget.TextView;
27 
28 import com.android.contacts.R;
29 import com.android.contacts.model.account.AccountInfo;
30 import com.android.contacts.model.account.AccountWithDataSet;
31 import com.android.contacts.util.AccountsListAdapter;
32 import com.android.contacts.util.UiClosables;
33 
34 import java.util.List;
35 
36 /**
37  * Controls the display of an account selector or header.
38  *
39  * TODO: This was mostly copied from {@link RawContactEditorView}. The code in that class
40  * should probably be modified to use this instead of leaving it duplicated.
41  */
42 public class AccountHeaderPresenter {
43 
44     private static final String KEY_SELECTED_ACCOUNT = "accountHeaderSelectedAccount";
45 
46     public interface Observer {
onChange(AccountHeaderPresenter sender)47         void onChange(AccountHeaderPresenter sender);
48 
49         public static final Observer NONE = new Observer() {
50             @Override
51             public void onChange(AccountHeaderPresenter sender) {
52             }
53         };
54     }
55 
56     private final Context mContext;
57 
58     private List<AccountInfo> mAccounts;
59     private AccountWithDataSet mCurrentAccount;
60 
61     // Account header
62     private final View mAccountHeaderContainer;
63     private TextView mAccountHeaderType;
64     private TextView mAccountHeaderName;
65     private ImageView mAccountHeaderIcon;
66     private ImageView mAccountHeaderExpanderIcon;
67 
68     // This would be different if the account was readonly
69     @StringRes
70     private int mSelectorTitle = R.string.editor_account_selector_title;
71 
72     private Observer mObserver = Observer.NONE;
73 
AccountHeaderPresenter(View container)74     public AccountHeaderPresenter(View container) {
75         mContext = container.getContext();
76         mAccountHeaderContainer = container;
77         // mAccountHeaderType is optional and may not be in the container view in which case
78         // the variable will be null
79         mAccountHeaderType = (TextView) container.findViewById(R.id.account_type);
80         mAccountHeaderName = (TextView) container.findViewById(R.id.account_name);
81         mAccountHeaderIcon = (ImageView) container.findViewById(R.id.account_type_icon);
82         mAccountHeaderExpanderIcon = (ImageView) container.findViewById(R.id.account_expander_icon);
83     }
84 
setObserver(Observer observer)85     public void setObserver(Observer observer) {
86         mObserver = observer;
87     }
88 
setCurrentAccount(@onNull AccountWithDataSet account)89     public void setCurrentAccount(@NonNull AccountWithDataSet account) {
90         if (mCurrentAccount != null && mCurrentAccount.equals(account)) {
91             return;
92         }
93         mCurrentAccount = account;
94         if (mObserver != null) {
95             mObserver.onChange(this);
96         }
97         updateDisplayedAccount();
98     }
99 
setAccounts(List<AccountInfo> accounts)100     public void setAccounts(List<AccountInfo> accounts) {
101         mAccounts = accounts;
102         // If the current account hasn't been set or it has been removed just use the first
103         // account.
104         if (mCurrentAccount == null || !AccountInfo.contains(mAccounts, mCurrentAccount)) {
105             mCurrentAccount = mAccounts.isEmpty() ? null : accounts.get(0).getAccount();
106             mObserver.onChange(this);
107         }
108         updateDisplayedAccount();
109     }
110 
getCurrentAccount()111     public AccountWithDataSet getCurrentAccount() {
112         return mCurrentAccount != null ? mCurrentAccount : null;
113     }
114 
onSaveInstanceState(Bundle outState)115     public void onSaveInstanceState(Bundle outState) {
116         outState.putParcelable(KEY_SELECTED_ACCOUNT, mCurrentAccount);
117     }
118 
onRestoreInstanceState(Bundle savedInstanceState)119     public void onRestoreInstanceState(Bundle savedInstanceState) {
120         if (savedInstanceState == null) return;
121         if (mCurrentAccount == null) {
122             mCurrentAccount = savedInstanceState.getParcelable(KEY_SELECTED_ACCOUNT);
123         }
124         updateDisplayedAccount();
125     }
126 
updateDisplayedAccount()127     private void updateDisplayedAccount() {
128         mAccountHeaderContainer.setVisibility(View.GONE);
129         if (mCurrentAccount == null) return;
130         if (mAccounts == null) return;
131 
132         final String accountLabel = getAccountLabel(mCurrentAccount);
133 
134         if (mAccounts.size() > 1) {
135             addAccountSelector(accountLabel);
136         } else {
137             addAccountHeader(accountLabel);
138         }
139     }
140 
addAccountHeader(String accountLabel)141     private void addAccountHeader(String accountLabel) {
142         mAccountHeaderContainer.setVisibility(View.VISIBLE);
143 
144         // Set the account name
145         mAccountHeaderName.setVisibility(View.VISIBLE);
146         mAccountHeaderName.setText(accountLabel);
147 
148         // Set the account type
149         final String selectorTitle = mContext.getResources().getString(mSelectorTitle);
150         if (mAccountHeaderType != null) {
151             mAccountHeaderType.setText(selectorTitle);
152         }
153 
154         final AccountInfo accountInfo = AccountInfo.getAccount(mAccounts, mCurrentAccount);
155 
156         // Set the icon
157         mAccountHeaderIcon.setImageDrawable(accountInfo.getIcon());
158 
159         // Set the content description
160         mAccountHeaderContainer.setContentDescription(
161                 EditorUiUtils.getAccountInfoContentDescription(accountLabel,
162                         selectorTitle));
163     }
164 
addAccountSelector(CharSequence nameLabel)165     private void addAccountSelector(CharSequence nameLabel) {
166         final View.OnClickListener onClickListener = new View.OnClickListener() {
167             @Override
168             public void onClick(View v) {
169                 showPopup();
170             }
171         };
172         setUpAccountSelector(nameLabel.toString(), onClickListener);
173     }
174 
showPopup()175     private void showPopup() {
176         final ListPopupWindow popup = new ListPopupWindow(mContext);
177         final AccountsListAdapter adapter =
178                 new AccountsListAdapter(mContext, mAccounts, mCurrentAccount);
179         popup.setWidth(mAccountHeaderContainer.getWidth());
180         popup.setAnchorView(mAccountHeaderContainer);
181         popup.setAdapter(adapter);
182         popup.setModal(true);
183         popup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
184         popup.setOnItemClickListener(new AdapterView.OnItemClickListener() {
185             @Override
186             public void onItemClick(AdapterView<?> parent, View view, int position,
187                     long id) {
188                 UiClosables.closeQuietly(popup);
189                 final AccountWithDataSet newAccount = adapter.getItem(position);
190                 setCurrentAccount(newAccount);
191                 // Make sure the new selection will be announced once it's changed
192                 mAccountHeaderContainer.setAccessibilityLiveRegion(
193                         View.ACCESSIBILITY_LIVE_REGION_POLITE);
194             }
195         });
196         mAccountHeaderContainer.post(new Runnable() {
197             @Override
198             public void run() {
199                 popup.show();
200             }
201         });
202     }
203 
setUpAccountSelector(String nameLabel, View.OnClickListener listener)204     private void setUpAccountSelector(String nameLabel, View.OnClickListener listener) {
205         addAccountHeader(nameLabel);
206         // Add handlers for choosing another account to save to.
207         mAccountHeaderExpanderIcon.setVisibility(View.VISIBLE);
208         // Add the listener to the icon so that it will be announced by talkback as a clickable
209         // element
210         mAccountHeaderExpanderIcon.setOnClickListener(listener);
211         mAccountHeaderContainer.setOnClickListener(listener);
212     }
213 
getAccountLabel(AccountWithDataSet account)214     private String getAccountLabel(AccountWithDataSet account) {
215         final AccountInfo accountInfo = AccountInfo.getAccount(mAccounts, account);
216         return accountInfo != null ? accountInfo.getNameLabel().toString() : null;
217     }
218 }
219