Loading AndroidManifest.xml +2 −9 Original line number Diff line number Diff line Loading @@ -20,8 +20,8 @@ android:versionName="1.7.34"> <uses-sdk android:minSdkVersion="34" android:targetSdkVersion="34"/> android:minSdkVersion="36" android:targetSdkVersion="36"/> <original-package android:name="com.android.contacts"/> Loading Loading @@ -392,13 +392,6 @@ </activity-alias> <!-- Accounts changed prompt that can appear when creating a new contact. --> <activity android:name=".activities.ContactEditorAccountsChangedActivity" android:exported="false" android:theme="@style/ContactEditorAccountsChangedActivityTheme" android:windowSoftInputMode="adjustResize"/> <!-- Edit or create a contact with only the most important fields displayed initially. --> <activity android:name=".activities.ContactEditorActivity" Loading res/values/styles.xml +0 −8 Original line number Diff line number Diff line Loading @@ -289,14 +289,6 @@ <item name="android:windowCloseOnTouchOutside">true</item> </style> <style name="ContactEditorAccountsChangedActivityTheme" parent="@android:style/Theme.Material.Light.Dialog.NoActionBar.MinWidth"> <item name="android:windowCloseOnTouchOutside">true</item> <item name="android:textColorPrimary">@color/primary_text_color</item> <item name="android:textColorSecondary">@color/secondary_text_color</item> <item name="android:listViewStyle">@style/ListViewStyle</item> <item name="android:colorAccent">@color/primary_color</item> </style> <style name="SelectableItem" parent="@android:style/Theme.Material.Light"> <item name="android:background">?android:attr/selectableItemBackground</item> </style> Loading res/xml/preference_display_options.xml +2 −3 Original line number Diff line number Diff line Loading @@ -26,11 +26,10 @@ android:title="@string/settings_accounts"> </Preference> <com.android.contacts.preference.DefaultAccountPreference <Preference android:icon="@null" android:key="defaultAccount" android:title="@string/default_editor_account" android:dialogTitle="@string/default_editor_account" /> android:title="@string/default_editor_account" /> <Preference android:icon="@null" Loading src/com/android/contacts/MoreContactUtils.java +109 −32 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.contacts; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Rect; Loading @@ -24,28 +25,34 @@ import android.provider.ContactsContract; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.android.contacts.model.account.AccountType; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; /** * Shared static contact utility methods. */ /** Shared static contact utility methods. */ public class MoreContactUtils { private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT); /** * Returns true if two data with mimetypes which represent values in contact entries are * considered equal for collapsing in the GUI. For caller-id, use * {@link android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} * instead * considered equal for collapsing in the GUI. For caller-id, use {@link * android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} instead */ public static boolean shouldCollapse(CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) { public static boolean shouldCollapse( CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) { // different mimetypes? don't collapse if (!TextUtils.equals(mimetype1, mimetype2)) return false; Loading @@ -57,8 +64,8 @@ public class MoreContactUtils { // if this is not about phone numbers, we know this is not a match (of course, some // mimetypes could have more sophisticated matching is the future, e.g. addresses) if (!TextUtils.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, mimetype1)) { if (!TextUtils.equals( ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, mimetype1)) { return false; } Loading Loading @@ -165,18 +172,18 @@ public class MoreContactUtils { case SHORT_NSN_MATCH: return false; default: throw new IllegalStateException("Unknown result value from phone number " + "library"); throw new IllegalStateException( "Unknown result value from phone number " + "library"); } } return true; } /** * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates * that are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to * how the target {@link android.graphics.Rect} is calculated in * {@link android.provider.ContactsContract.QuickContact#showQuickContact}. * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates that * are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to how the * target {@link android.graphics.Rect} is calculated in {@link * android.provider.ContactsContract.QuickContact#showQuickContact}. */ public static Rect getTargetRectFromView(View view) { final int[] pos = new int[2]; Loading @@ -191,8 +198,8 @@ public class MoreContactUtils { } /** * Returns a header view based on the R.layout.list_separator, where the * containing {@link android.widget.TextView} is set using the given textResourceId. * Returns a header view based on the R.layout.list_separator, where the containing {@link * android.widget.TextView} is set using the given textResourceId. */ public static TextView createHeaderView(Context context, int textResourceId) { final TextView textView = (TextView) View.inflate(context, R.layout.list_separator, null); Loading @@ -201,29 +208,37 @@ public class MoreContactUtils { } /** * Set the top padding on the header view dynamically, based on whether the header is in * the first row or not. * Set the top padding on the header view dynamically, based on whether the header is in the * first row or not. */ public static void setHeaderViewBottomPadding(Context context, TextView textView, boolean isFirstRow) { public static void setHeaderViewBottomPadding( Context context, TextView textView, boolean isFirstRow) { final int topPadding; if (isFirstRow) { topPadding = (int) context.getResources().getDimension( R.dimen.frequently_contacted_title_top_margin_when_first_row); topPadding = (int) context.getResources() .getDimension( R.dimen .frequently_contacted_title_top_margin_when_first_row); } else { topPadding = (int) context.getResources().getDimension( R.dimen.frequently_contacted_title_top_margin); topPadding = (int) context.getResources() .getDimension(R.dimen.frequently_contacted_title_top_margin); } textView.setPaddingRelative(textView.getPaddingStart(), topPadding, textView.getPaddingEnd(), textView.getPaddingBottom()); textView.setPaddingRelative( textView.getPaddingStart(), topPadding, textView.getPaddingEnd(), textView.getPaddingBottom()); } /** * Returns the intent to launch for the given invitable account type and contact lookup URI. * This will return null if the account type is not invitable (i.e. there is no * {@link AccountType#getInviteContactActivityClassName()} or * {@link AccountType#syncAdapterPackageName}). * This will return null if the account type is not invitable (i.e. there is no {@link * AccountType#getInviteContactActivityClassName()} or {@link * AccountType#syncAdapterPackageName}). */ public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) { String syncAdapterPackageName = accountType.syncAdapterPackageName; Loading @@ -240,4 +255,66 @@ public class MoreContactUtils { intent.setData(lookupUri); return intent; } /** * Enable new edge to edge feature. * * @param activity the Activity need to setup the edge to edge feature. */ public static void setupEdgeToEdge(@NonNull Activity activity, EdgeToEdgeInsetHandler handler) { ViewCompat.setOnApplyWindowInsetsListener( activity.findViewById(android.R.id.content), (v, windowInsets) -> { final Insets insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime() | WindowInsetsCompat.Type.displayCutout()); // Apply the insets paddings to the view. v.setPadding( insets.left, handler == null ? insets.top : v.getPaddingTop(), insets.right, insets.bottom); if (handler != null) { handler.applyTopInset(insets.top); } // Return CONSUMED if you don't want the window insets to keep being // passed down to descendant views. return WindowInsetsCompat.CONSUMED; }); } /** Handles setting the insets on a {@link View}. */ public static class EdgeToEdgeInsetHandler { private final View mView; private int mOriginalHeight = -1; private int mOriginalPaddingTop = -1; public EdgeToEdgeInsetHandler(View view) { mView = view; } public void applyTopInset(int top) { ViewGroup.LayoutParams layoutParams = mView.getLayoutParams(); if (mOriginalHeight == -1) { mOriginalHeight = layoutParams.height; } if (mOriginalPaddingTop == -1) { mOriginalPaddingTop = mView.getPaddingTop(); } layoutParams.height = mOriginalHeight + top; mView.setLayoutParams(layoutParams); mView.setPadding( mView.getPaddingLeft(), mOriginalPaddingTop + top, mView.getPaddingRight(), mView.getPaddingBottom()); } } } src/com/android/contacts/SimImportFragment.java +96 −82 Original line number Diff line number Diff line Loading @@ -22,14 +22,6 @@ import android.content.Context; import android.content.IntentFilter; import android.content.Loader; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.snackbar.Snackbar; import androidx.collection.ArrayMap; import androidx.core.view.ViewCompat; import androidx.core.widget.ContentLoadingProgressBar; import androidx.appcompat.widget.Toolbar; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; Loading @@ -40,6 +32,15 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.collection.ArrayMap; import androidx.core.view.ViewCompat; import androidx.core.widget.ContentLoadingProgressBar; import com.android.contacts.MoreContactUtils; import com.android.contacts.MoreContactUtils.EdgeToEdgeInsetHandler; import com.android.contacts.compat.CompatUtils; import com.android.contacts.database.SimContactDao; import com.android.contacts.editor.AccountHeaderPresenter; Loading @@ -51,6 +52,8 @@ import com.android.contacts.model.account.AccountWithDataSet; import com.android.contacts.preference.ContactsPreferences; import com.android.contacts.util.concurrent.ContactsExecutors; import com.android.contacts.util.concurrent.ListenableFutureLoader; import com.google.android.material.snackbar.Snackbar; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; Loading @@ -70,7 +73,8 @@ import java.util.concurrent.Callable; */ public class SimImportFragment extends Fragment implements LoaderManager.LoaderCallbacks<SimImportFragment.LoaderResult>, AdapterView.OnItemClickListener, AbsListView.OnScrollListener { AdapterView.OnItemClickListener, AbsListView.OnScrollListener { private static final String KEY_SUFFIX_SELECTED_IDS = "_selectedIds"; private static final String ARG_SUBSCRIPTION_ID = "subscriptionId"; Loading Loading @@ -102,8 +106,10 @@ public class SimImportFragment extends Fragment mAdapter = new SimContactAdapter(getActivity()); final Bundle args = getArguments(); mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID); mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID); } @Override Loading @@ -114,15 +120,14 @@ public class SimImportFragment extends Fragment @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_sim_import, container, false); mAccountHeaderContainer = view.findViewById(R.id.account_header_container); mAccountScrolledElevationPixels = getResources() .getDimension(R.dimen.contact_list_header_elevation); mAccountHeaderPresenter = new AccountHeaderPresenter( mAccountHeaderContainer); mAccountScrolledElevationPixels = getResources().getDimension(R.dimen.contact_list_header_elevation); mAccountHeaderPresenter = new AccountHeaderPresenter(mAccountHeaderContainer); if (savedInstanceState != null) { mAccountHeaderPresenter.onRestoreInstanceState(savedInstanceState); } else { Loading @@ -130,7 +135,8 @@ public class SimImportFragment extends Fragment // after they are loaded. mAccountHeaderPresenter.setCurrentAccount(mPreferences.getDefaultAccount()); } mAccountHeaderPresenter.setObserver(new AccountHeaderPresenter.Observer() { mAccountHeaderPresenter.setObserver( new AccountHeaderPresenter.Observer() { @Override public void onChange(AccountHeaderPresenter sender) { rememberSelectionsForCurrentAccount(); Loading @@ -147,7 +153,8 @@ public class SimImportFragment extends Fragment mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); mListView.setOnItemClickListener(this); mImportButton = view.findViewById(R.id.import_button); mImportButton.setOnClickListener(new View.OnClickListener() { mImportButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { importCurrentSelections(); Loading @@ -158,7 +165,9 @@ public class SimImportFragment extends Fragment }); mToolbar = (Toolbar) view.findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { MoreContactUtils.setupEdgeToEdge(getActivity(), new EdgeToEdgeInsetHandler(mToolbar)); mToolbar.setNavigationOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { getActivity().setResult(Activity.RESULT_CANCELED); Loading Loading @@ -188,8 +197,8 @@ public class SimImportFragment extends Fragment return; } for (int i = 0, len = mListView.getCount(); i < len; i++) { mListView.setItemChecked(i, Arrays.binarySearch(ids, mListView.getItemIdAtPosition(i)) >= 0); mListView.setItemChecked( i, Arrays.binarySearch(ids, mListView.getItemIdAtPosition(i)) >= 0); } } Loading Loading @@ -243,8 +252,7 @@ public class SimImportFragment extends Fragment } @Override public void onLoadFinished(Loader<LoaderResult> loader, LoaderResult data) { public void onLoadFinished(Loader<LoaderResult> loader, LoaderResult data) { mLoadingIndicator.hide(); if (data == null) { return; Loading @@ -259,8 +267,7 @@ public class SimImportFragment extends Fragment } @Override public void onLoaderReset(Loader<LoaderResult> loader) { } public void onLoaderReset(Loader<LoaderResult> loader) {} private void restoreAdapterSelectedStates(List<AccountInfo> accounts) { if (mSavedInstanceState == null) { Loading @@ -268,7 +275,8 @@ public class SimImportFragment extends Fragment } for (AccountInfo account : accounts) { final long[] selections = mSavedInstanceState.getLongArray( final long[] selections = mSavedInstanceState.getLongArray( account.getAccount().stringify() + KEY_SUFFIX_SELECTED_IDS); mPerAccountCheckedIds.put(account.getAccount(), selections); } Loading @@ -282,8 +290,8 @@ public class SimImportFragment extends Fragment // Make sure the selections are up-to-date for (Map.Entry<AccountWithDataSet, long[]> entry : mPerAccountCheckedIds.entrySet()) { outState.putLongArray(entry.getKey().stringify() + KEY_SUFFIX_SELECTED_IDS, entry.getValue()); outState.putLongArray( entry.getKey().stringify() + KEY_SUFFIX_SELECTED_IDS, entry.getValue()); } } Loading @@ -297,14 +305,17 @@ public class SimImportFragment extends Fragment importableContacts.add(mAdapter.getItem(checked.keyAt(i))); } } SimImportService.startImport(getContext(), mSubscriptionId, importableContacts, SimImportService.startImport( getContext(), mSubscriptionId, importableContacts, mAccountHeaderPresenter.getCurrentAccount()); } public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (mAdapter.existsInCurrentAccount(position)) { Snackbar.make(getView(), R.string.sim_import_contact_exists_toast, Snackbar.LENGTH_LONG).show(); Snackbar.make(getView(), R.string.sim_import_contact_exists_toast, Snackbar.LENGTH_LONG) .show(); } else { updateToolbarWithCurrentSelections(); } Loading @@ -321,8 +332,8 @@ public class SimImportFragment extends Fragment public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { public void onScroll( AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int firstCompletelyVisibleItem = firstVisibleItem; if (view != null && view.getChildAt(0) != null && view.getChildAt(0).getTop() < 0) { firstCompletelyVisibleItem++; Loading @@ -335,9 +346,7 @@ public class SimImportFragment extends Fragment } } /** * Creates a fragment that will display contacts stored on the default SIM card */ /** Creates a fragment that will display contacts stored on the default SIM card */ public static SimImportFragment newInstance() { return new SimImportFragment(); } Loading Loading @@ -394,9 +403,10 @@ public class SimImportFragment extends Fragment public View getView(int position, View convertView, ViewGroup parent) { TextView text = (TextView) convertView; if (text == null) { final int layoutRes = existsInCurrentAccount(position) ? R.layout.sim_import_list_item_disabled : R.layout.sim_import_list_item; final int layoutRes = existsInCurrentAccount(position) ? R.layout.sim_import_list_item_disabled : R.layout.sim_import_list_item; text = (TextView) mInflater.inflate(layoutRes, parent, false); } text.setText(getItemLabel(getItem(position))); Loading Loading @@ -444,7 +454,6 @@ public class SimImportFragment extends Fragment } } private static class SimContactLoader extends ListenableFutureLoader<LoaderResult> { private SimContactDao mDao; private AccountTypeManager mAccountTypeManager; Loading @@ -459,17 +468,21 @@ public class SimImportFragment extends Fragment @Override protected ListenableFuture<LoaderResult> loadData() { final ListenableFuture<List<Object>> future = Futures.<Object>allAsList( mAccountTypeManager .filterAccountsAsync(AccountTypeManager.writableFilter()), ContactsExecutors.getSimReadExecutor().<Object>submit( final ListenableFuture<List<Object>> future = Futures.<Object>allAsList( mAccountTypeManager.filterAccountsAsync( AccountTypeManager.insertableFilter(getContext())), ContactsExecutors.getSimReadExecutor() .<Object>submit( new Callable<Object>() { @Override public LoaderResult call() throws Exception { return loadFromSim(); } })); return Futures.transform(future, new Function<List<Object>, LoaderResult>() { return Futures.transform( future, new Function<List<Object>, LoaderResult>() { @Override public LoaderResult apply(List<Object> input) { final List<AccountInfo> accounts = (List<AccountInfo>) input.get(0); Loading @@ -477,7 +490,8 @@ public class SimImportFragment extends Fragment simLoadResult.accounts = accounts; return simLoadResult; } }, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor()); } private LoaderResult loadFromSim() { Loading Loading
AndroidManifest.xml +2 −9 Original line number Diff line number Diff line Loading @@ -20,8 +20,8 @@ android:versionName="1.7.34"> <uses-sdk android:minSdkVersion="34" android:targetSdkVersion="34"/> android:minSdkVersion="36" android:targetSdkVersion="36"/> <original-package android:name="com.android.contacts"/> Loading Loading @@ -392,13 +392,6 @@ </activity-alias> <!-- Accounts changed prompt that can appear when creating a new contact. --> <activity android:name=".activities.ContactEditorAccountsChangedActivity" android:exported="false" android:theme="@style/ContactEditorAccountsChangedActivityTheme" android:windowSoftInputMode="adjustResize"/> <!-- Edit or create a contact with only the most important fields displayed initially. --> <activity android:name=".activities.ContactEditorActivity" Loading
res/values/styles.xml +0 −8 Original line number Diff line number Diff line Loading @@ -289,14 +289,6 @@ <item name="android:windowCloseOnTouchOutside">true</item> </style> <style name="ContactEditorAccountsChangedActivityTheme" parent="@android:style/Theme.Material.Light.Dialog.NoActionBar.MinWidth"> <item name="android:windowCloseOnTouchOutside">true</item> <item name="android:textColorPrimary">@color/primary_text_color</item> <item name="android:textColorSecondary">@color/secondary_text_color</item> <item name="android:listViewStyle">@style/ListViewStyle</item> <item name="android:colorAccent">@color/primary_color</item> </style> <style name="SelectableItem" parent="@android:style/Theme.Material.Light"> <item name="android:background">?android:attr/selectableItemBackground</item> </style> Loading
res/xml/preference_display_options.xml +2 −3 Original line number Diff line number Diff line Loading @@ -26,11 +26,10 @@ android:title="@string/settings_accounts"> </Preference> <com.android.contacts.preference.DefaultAccountPreference <Preference android:icon="@null" android:key="defaultAccount" android:title="@string/default_editor_account" android:dialogTitle="@string/default_editor_account" /> android:title="@string/default_editor_account" /> <Preference android:icon="@null" Loading
src/com/android/contacts/MoreContactUtils.java +109 −32 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.contacts; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.graphics.Rect; Loading @@ -24,28 +25,34 @@ import android.provider.ContactsContract; import android.telephony.PhoneNumberUtils; import android.text.TextUtils; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import com.android.contacts.model.account.AccountType; import com.google.i18n.phonenumbers.NumberParseException; import com.google.i18n.phonenumbers.PhoneNumberUtil; /** * Shared static contact utility methods. */ /** Shared static contact utility methods. */ public class MoreContactUtils { private static final String WAIT_SYMBOL_AS_STRING = String.valueOf(PhoneNumberUtils.WAIT); /** * Returns true if two data with mimetypes which represent values in contact entries are * considered equal for collapsing in the GUI. For caller-id, use * {@link android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} * instead * considered equal for collapsing in the GUI. For caller-id, use {@link * android.telephony.PhoneNumberUtils#compare(android.content.Context, String, String)} instead */ public static boolean shouldCollapse(CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) { public static boolean shouldCollapse( CharSequence mimetype1, CharSequence data1, CharSequence mimetype2, CharSequence data2) { // different mimetypes? don't collapse if (!TextUtils.equals(mimetype1, mimetype2)) return false; Loading @@ -57,8 +64,8 @@ public class MoreContactUtils { // if this is not about phone numbers, we know this is not a match (of course, some // mimetypes could have more sophisticated matching is the future, e.g. addresses) if (!TextUtils.equals(ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, mimetype1)) { if (!TextUtils.equals( ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE, mimetype1)) { return false; } Loading Loading @@ -165,18 +172,18 @@ public class MoreContactUtils { case SHORT_NSN_MATCH: return false; default: throw new IllegalStateException("Unknown result value from phone number " + "library"); throw new IllegalStateException( "Unknown result value from phone number " + "library"); } } return true; } /** * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates * that are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to * how the target {@link android.graphics.Rect} is calculated in * {@link android.provider.ContactsContract.QuickContact#showQuickContact}. * Returns the {@link android.graphics.Rect} with left, top, right, and bottom coordinates that * are equivalent to the given {@link android.view.View}'s bounds. This is equivalent to how the * target {@link android.graphics.Rect} is calculated in {@link * android.provider.ContactsContract.QuickContact#showQuickContact}. */ public static Rect getTargetRectFromView(View view) { final int[] pos = new int[2]; Loading @@ -191,8 +198,8 @@ public class MoreContactUtils { } /** * Returns a header view based on the R.layout.list_separator, where the * containing {@link android.widget.TextView} is set using the given textResourceId. * Returns a header view based on the R.layout.list_separator, where the containing {@link * android.widget.TextView} is set using the given textResourceId. */ public static TextView createHeaderView(Context context, int textResourceId) { final TextView textView = (TextView) View.inflate(context, R.layout.list_separator, null); Loading @@ -201,29 +208,37 @@ public class MoreContactUtils { } /** * Set the top padding on the header view dynamically, based on whether the header is in * the first row or not. * Set the top padding on the header view dynamically, based on whether the header is in the * first row or not. */ public static void setHeaderViewBottomPadding(Context context, TextView textView, boolean isFirstRow) { public static void setHeaderViewBottomPadding( Context context, TextView textView, boolean isFirstRow) { final int topPadding; if (isFirstRow) { topPadding = (int) context.getResources().getDimension( R.dimen.frequently_contacted_title_top_margin_when_first_row); topPadding = (int) context.getResources() .getDimension( R.dimen .frequently_contacted_title_top_margin_when_first_row); } else { topPadding = (int) context.getResources().getDimension( R.dimen.frequently_contacted_title_top_margin); topPadding = (int) context.getResources() .getDimension(R.dimen.frequently_contacted_title_top_margin); } textView.setPaddingRelative(textView.getPaddingStart(), topPadding, textView.getPaddingEnd(), textView.getPaddingBottom()); textView.setPaddingRelative( textView.getPaddingStart(), topPadding, textView.getPaddingEnd(), textView.getPaddingBottom()); } /** * Returns the intent to launch for the given invitable account type and contact lookup URI. * This will return null if the account type is not invitable (i.e. there is no * {@link AccountType#getInviteContactActivityClassName()} or * {@link AccountType#syncAdapterPackageName}). * This will return null if the account type is not invitable (i.e. there is no {@link * AccountType#getInviteContactActivityClassName()} or {@link * AccountType#syncAdapterPackageName}). */ public static Intent getInvitableIntent(AccountType accountType, Uri lookupUri) { String syncAdapterPackageName = accountType.syncAdapterPackageName; Loading @@ -240,4 +255,66 @@ public class MoreContactUtils { intent.setData(lookupUri); return intent; } /** * Enable new edge to edge feature. * * @param activity the Activity need to setup the edge to edge feature. */ public static void setupEdgeToEdge(@NonNull Activity activity, EdgeToEdgeInsetHandler handler) { ViewCompat.setOnApplyWindowInsetsListener( activity.findViewById(android.R.id.content), (v, windowInsets) -> { final Insets insets = windowInsets.getInsets( WindowInsetsCompat.Type.systemBars() | WindowInsetsCompat.Type.ime() | WindowInsetsCompat.Type.displayCutout()); // Apply the insets paddings to the view. v.setPadding( insets.left, handler == null ? insets.top : v.getPaddingTop(), insets.right, insets.bottom); if (handler != null) { handler.applyTopInset(insets.top); } // Return CONSUMED if you don't want the window insets to keep being // passed down to descendant views. return WindowInsetsCompat.CONSUMED; }); } /** Handles setting the insets on a {@link View}. */ public static class EdgeToEdgeInsetHandler { private final View mView; private int mOriginalHeight = -1; private int mOriginalPaddingTop = -1; public EdgeToEdgeInsetHandler(View view) { mView = view; } public void applyTopInset(int top) { ViewGroup.LayoutParams layoutParams = mView.getLayoutParams(); if (mOriginalHeight == -1) { mOriginalHeight = layoutParams.height; } if (mOriginalPaddingTop == -1) { mOriginalPaddingTop = mView.getPaddingTop(); } layoutParams.height = mOriginalHeight + top; mView.setLayoutParams(layoutParams); mView.setPadding( mView.getPaddingLeft(), mOriginalPaddingTop + top, mView.getPaddingRight(), mView.getPaddingBottom()); } } }
src/com/android/contacts/SimImportFragment.java +96 −82 Original line number Diff line number Diff line Loading @@ -22,14 +22,6 @@ import android.content.Context; import android.content.IntentFilter; import android.content.Loader; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.google.android.material.snackbar.Snackbar; import androidx.collection.ArrayMap; import androidx.core.view.ViewCompat; import androidx.core.widget.ContentLoadingProgressBar; import androidx.appcompat.widget.Toolbar; import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; Loading @@ -40,6 +32,15 @@ import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.Toolbar; import androidx.collection.ArrayMap; import androidx.core.view.ViewCompat; import androidx.core.widget.ContentLoadingProgressBar; import com.android.contacts.MoreContactUtils; import com.android.contacts.MoreContactUtils.EdgeToEdgeInsetHandler; import com.android.contacts.compat.CompatUtils; import com.android.contacts.database.SimContactDao; import com.android.contacts.editor.AccountHeaderPresenter; Loading @@ -51,6 +52,8 @@ import com.android.contacts.model.account.AccountWithDataSet; import com.android.contacts.preference.ContactsPreferences; import com.android.contacts.util.concurrent.ContactsExecutors; import com.android.contacts.util.concurrent.ListenableFutureLoader; import com.google.android.material.snackbar.Snackbar; import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; Loading @@ -70,7 +73,8 @@ import java.util.concurrent.Callable; */ public class SimImportFragment extends Fragment implements LoaderManager.LoaderCallbacks<SimImportFragment.LoaderResult>, AdapterView.OnItemClickListener, AbsListView.OnScrollListener { AdapterView.OnItemClickListener, AbsListView.OnScrollListener { private static final String KEY_SUFFIX_SELECTED_IDS = "_selectedIds"; private static final String ARG_SUBSCRIPTION_ID = "subscriptionId"; Loading Loading @@ -102,8 +106,10 @@ public class SimImportFragment extends Fragment mAdapter = new SimContactAdapter(getActivity()); final Bundle args = getArguments(); mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID); mSubscriptionId = args == null ? SimCard.NO_SUBSCRIPTION_ID : args.getInt(ARG_SUBSCRIPTION_ID, SimCard.NO_SUBSCRIPTION_ID); } @Override Loading @@ -114,15 +120,14 @@ public class SimImportFragment extends Fragment @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View view = inflater.inflate(R.layout.fragment_sim_import, container, false); mAccountHeaderContainer = view.findViewById(R.id.account_header_container); mAccountScrolledElevationPixels = getResources() .getDimension(R.dimen.contact_list_header_elevation); mAccountHeaderPresenter = new AccountHeaderPresenter( mAccountHeaderContainer); mAccountScrolledElevationPixels = getResources().getDimension(R.dimen.contact_list_header_elevation); mAccountHeaderPresenter = new AccountHeaderPresenter(mAccountHeaderContainer); if (savedInstanceState != null) { mAccountHeaderPresenter.onRestoreInstanceState(savedInstanceState); } else { Loading @@ -130,7 +135,8 @@ public class SimImportFragment extends Fragment // after they are loaded. mAccountHeaderPresenter.setCurrentAccount(mPreferences.getDefaultAccount()); } mAccountHeaderPresenter.setObserver(new AccountHeaderPresenter.Observer() { mAccountHeaderPresenter.setObserver( new AccountHeaderPresenter.Observer() { @Override public void onChange(AccountHeaderPresenter sender) { rememberSelectionsForCurrentAccount(); Loading @@ -147,7 +153,8 @@ public class SimImportFragment extends Fragment mListView.setChoiceMode(AbsListView.CHOICE_MODE_MULTIPLE); mListView.setOnItemClickListener(this); mImportButton = view.findViewById(R.id.import_button); mImportButton.setOnClickListener(new View.OnClickListener() { mImportButton.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { importCurrentSelections(); Loading @@ -158,7 +165,9 @@ public class SimImportFragment extends Fragment }); mToolbar = (Toolbar) view.findViewById(R.id.toolbar); mToolbar.setNavigationOnClickListener(new View.OnClickListener() { MoreContactUtils.setupEdgeToEdge(getActivity(), new EdgeToEdgeInsetHandler(mToolbar)); mToolbar.setNavigationOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { getActivity().setResult(Activity.RESULT_CANCELED); Loading Loading @@ -188,8 +197,8 @@ public class SimImportFragment extends Fragment return; } for (int i = 0, len = mListView.getCount(); i < len; i++) { mListView.setItemChecked(i, Arrays.binarySearch(ids, mListView.getItemIdAtPosition(i)) >= 0); mListView.setItemChecked( i, Arrays.binarySearch(ids, mListView.getItemIdAtPosition(i)) >= 0); } } Loading Loading @@ -243,8 +252,7 @@ public class SimImportFragment extends Fragment } @Override public void onLoadFinished(Loader<LoaderResult> loader, LoaderResult data) { public void onLoadFinished(Loader<LoaderResult> loader, LoaderResult data) { mLoadingIndicator.hide(); if (data == null) { return; Loading @@ -259,8 +267,7 @@ public class SimImportFragment extends Fragment } @Override public void onLoaderReset(Loader<LoaderResult> loader) { } public void onLoaderReset(Loader<LoaderResult> loader) {} private void restoreAdapterSelectedStates(List<AccountInfo> accounts) { if (mSavedInstanceState == null) { Loading @@ -268,7 +275,8 @@ public class SimImportFragment extends Fragment } for (AccountInfo account : accounts) { final long[] selections = mSavedInstanceState.getLongArray( final long[] selections = mSavedInstanceState.getLongArray( account.getAccount().stringify() + KEY_SUFFIX_SELECTED_IDS); mPerAccountCheckedIds.put(account.getAccount(), selections); } Loading @@ -282,8 +290,8 @@ public class SimImportFragment extends Fragment // Make sure the selections are up-to-date for (Map.Entry<AccountWithDataSet, long[]> entry : mPerAccountCheckedIds.entrySet()) { outState.putLongArray(entry.getKey().stringify() + KEY_SUFFIX_SELECTED_IDS, entry.getValue()); outState.putLongArray( entry.getKey().stringify() + KEY_SUFFIX_SELECTED_IDS, entry.getValue()); } } Loading @@ -297,14 +305,17 @@ public class SimImportFragment extends Fragment importableContacts.add(mAdapter.getItem(checked.keyAt(i))); } } SimImportService.startImport(getContext(), mSubscriptionId, importableContacts, SimImportService.startImport( getContext(), mSubscriptionId, importableContacts, mAccountHeaderPresenter.getCurrentAccount()); } public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (mAdapter.existsInCurrentAccount(position)) { Snackbar.make(getView(), R.string.sim_import_contact_exists_toast, Snackbar.LENGTH_LONG).show(); Snackbar.make(getView(), R.string.sim_import_contact_exists_toast, Snackbar.LENGTH_LONG) .show(); } else { updateToolbarWithCurrentSelections(); } Loading @@ -321,8 +332,8 @@ public class SimImportFragment extends Fragment public void onScrollStateChanged(AbsListView view, int scrollState) {} @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { public void onScroll( AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { int firstCompletelyVisibleItem = firstVisibleItem; if (view != null && view.getChildAt(0) != null && view.getChildAt(0).getTop() < 0) { firstCompletelyVisibleItem++; Loading @@ -335,9 +346,7 @@ public class SimImportFragment extends Fragment } } /** * Creates a fragment that will display contacts stored on the default SIM card */ /** Creates a fragment that will display contacts stored on the default SIM card */ public static SimImportFragment newInstance() { return new SimImportFragment(); } Loading Loading @@ -394,9 +403,10 @@ public class SimImportFragment extends Fragment public View getView(int position, View convertView, ViewGroup parent) { TextView text = (TextView) convertView; if (text == null) { final int layoutRes = existsInCurrentAccount(position) ? R.layout.sim_import_list_item_disabled : R.layout.sim_import_list_item; final int layoutRes = existsInCurrentAccount(position) ? R.layout.sim_import_list_item_disabled : R.layout.sim_import_list_item; text = (TextView) mInflater.inflate(layoutRes, parent, false); } text.setText(getItemLabel(getItem(position))); Loading Loading @@ -444,7 +454,6 @@ public class SimImportFragment extends Fragment } } private static class SimContactLoader extends ListenableFutureLoader<LoaderResult> { private SimContactDao mDao; private AccountTypeManager mAccountTypeManager; Loading @@ -459,17 +468,21 @@ public class SimImportFragment extends Fragment @Override protected ListenableFuture<LoaderResult> loadData() { final ListenableFuture<List<Object>> future = Futures.<Object>allAsList( mAccountTypeManager .filterAccountsAsync(AccountTypeManager.writableFilter()), ContactsExecutors.getSimReadExecutor().<Object>submit( final ListenableFuture<List<Object>> future = Futures.<Object>allAsList( mAccountTypeManager.filterAccountsAsync( AccountTypeManager.insertableFilter(getContext())), ContactsExecutors.getSimReadExecutor() .<Object>submit( new Callable<Object>() { @Override public LoaderResult call() throws Exception { return loadFromSim(); } })); return Futures.transform(future, new Function<List<Object>, LoaderResult>() { return Futures.transform( future, new Function<List<Object>, LoaderResult>() { @Override public LoaderResult apply(List<Object> input) { final List<AccountInfo> accounts = (List<AccountInfo>) input.get(0); Loading @@ -477,7 +490,8 @@ public class SimImportFragment extends Fragment simLoadResult.accounts = accounts; return simLoadResult; } }, MoreExecutors.directExecutor()); }, MoreExecutors.directExecutor()); } private LoaderResult loadFromSim() { Loading