Commit c806c71f authored by estade's avatar estade Committed by Commit bot

Autofill item deletion on android

You can now delete things by long pressing suggestions in the dropdown (assuming it's the right kind of suggestion).

BUG=486153

Review URL: https://codereview.chromium.org/1134793004

Cr-Commit-Position: refs/heads/master@{#330413}
parent b7931b4d
......@@ -56,6 +56,8 @@ public class AwAutofillClient {
public void suggestionSelected(int listIndex) {
nativeSuggestionSelected(mNativeAwAutofillClient, listIndex);
}
@Override
public void deleteSuggestion(int listIndex) { }
});
}
mAutofillPopup.setAnchorRect(x, y, width, height);
......@@ -84,7 +86,7 @@ public class AwAutofillClient {
@CalledByNative
private static void addToAutofillSuggestionArray(AutofillSuggestion[] array, int index,
String name, String label, int uniqueId) {
array[index] = new AutofillSuggestion(name, label, DropdownItem.NO_ICON, uniqueId);
array[index] = new AutofillSuggestion(name, label, DropdownItem.NO_ICON, uniqueId, false);
}
private native void nativeSuggestionSelected(long nativeAwAutofillClient,
......
......@@ -147,6 +147,9 @@ IDS_AUTOFILL_CC_MASTERCARD
IDS_AUTOFILL_CC_UNION_PAY
IDS_AUTOFILL_CC_VISA
IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM
IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY
IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY
IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY
IDS_AUTOFILL_DIALOG_PRIVACY_POLICY_LINK
IDS_AUTOFILL_FIELD_LABEL_AREA
IDS_AUTOFILL_FIELD_LABEL_COUNTY
......
......@@ -106,7 +106,7 @@ public class AutofillKeyboardAccessoryBridge implements AutofillKeyboardAccessor
private static void addToAutofillSuggestionArray(AutofillSuggestion[] array, int index,
String label, int iconId, int suggestionId) {
int drawableId = iconId == 0 ? DropdownItem.NO_ICON : ResourceId.mapToDrawableId(iconId);
array[index] = new AutofillSuggestion(label, null, drawableId, suggestionId);
array[index] = new AutofillSuggestion(label, null, drawableId, suggestionId, false);
}
private native void nativeViewDismissed(long nativeAutofillKeyboardAccessoryView);
......
......@@ -5,10 +5,14 @@
package org.chromium.chrome.browser.autofill;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Handler;
import android.support.v7.app.AlertDialog;
import org.chromium.base.CalledByNative;
import org.chromium.base.JNINamespace;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ResourceId;
import org.chromium.ui.DropdownItem;
import org.chromium.ui.autofill.AutofillPopup;
......@@ -21,9 +25,11 @@ import org.chromium.ui.base.WindowAndroid;
* JNI call glue for AutofillExternalDelagate C++ and Java objects.
*/
@JNINamespace("autofill")
public class AutofillPopupBridge implements AutofillPopupDelegate{
public class AutofillPopupBridge implements AutofillPopupDelegate, DialogInterface.OnClickListener {
private final long mNativeAutofillPopup;
private final AutofillPopup mAutofillPopup;
private AlertDialog mDeletionDialog;
private final Context mContext;
public AutofillPopupBridge(long nativeAutofillPopupViewAndroid, WindowAndroid windowAndroid,
ViewAndroidDelegate containerViewDelegate) {
......@@ -31,6 +37,7 @@ public class AutofillPopupBridge implements AutofillPopupDelegate{
Activity activity = windowAndroid.getActivity().get();
if (activity == null) {
mAutofillPopup = null;
mContext = null;
// Clean up the native counterpart. This is posted to allow the native counterpart
// to fully finish the construction of this glue object before we attempt to delete it.
new Handler().post(new Runnable() {
......@@ -41,6 +48,7 @@ public class AutofillPopupBridge implements AutofillPopupDelegate{
});
} else {
mAutofillPopup = new AutofillPopup(activity, containerViewDelegate, this);
mContext = activity;
}
}
......@@ -61,12 +69,24 @@ public class AutofillPopupBridge implements AutofillPopupDelegate{
nativeSuggestionSelected(mNativeAutofillPopup, listIndex);
}
@Override
public void deleteSuggestion(int listIndex) {
nativeDeletionRequested(mNativeAutofillPopup, listIndex);
}
@Override
public void onClick(DialogInterface dialog, int which) {
assert which == DialogInterface.BUTTON_POSITIVE;
nativeDeletionConfirmed(mNativeAutofillPopup);
}
/**
* Hides the Autofill Popup and removes its anchor from the ContainerView.
*/
@CalledByNative
private void dismiss() {
if (mAutofillPopup != null) mAutofillPopup.dismiss();
if (mDeletionDialog != null) mDeletionDialog.dismiss();
}
/**
......@@ -90,6 +110,17 @@ public class AutofillPopupBridge implements AutofillPopupDelegate{
if (mAutofillPopup != null) mAutofillPopup.setAnchorRect(x, y, width, height);
}
@CalledByNative
private void confirmDeletion(String title, String body) {
mDeletionDialog = new AlertDialog.Builder(mContext, R.style.AlertDialogTheme)
.setTitle(title)
.setMessage(body)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, this)
.create();
mDeletionDialog.show();
}
// Helper methods for AutofillSuggestion
@CalledByNative
......@@ -104,15 +135,19 @@ public class AutofillPopupBridge implements AutofillPopupDelegate{
* @param sublabel Second line of the suggestion.
* @param iconId The resource ID for the icon associated with the suggestion, or 0 for no icon.
* @param suggestionId Identifier for the suggestion type.
* @param deletable Whether this item is deletable.
*/
@CalledByNative
private static void addToAutofillSuggestionArray(AutofillSuggestion[] array, int index,
String label, String sublabel, int iconId, int suggestionId) {
String label, String sublabel, int iconId, int suggestionId, boolean deletable) {
int drawableId = iconId == 0 ? DropdownItem.NO_ICON : ResourceId.mapToDrawableId(iconId);
array[index] = new AutofillSuggestion(label, sublabel, drawableId, suggestionId);
array[index] = new AutofillSuggestion(label, sublabel, drawableId, suggestionId, deletable);
}
private native void nativePopupDismissed(long nativeAutofillPopupViewAndroid);
private native void nativeSuggestionSelected(long nativeAutofillPopupViewAndroid,
int listIndex);
private native void nativeDeletionRequested(long nativeAutofillPopupViewAndroid,
int listIndex);
private native void nativeDeletionConfirmed(long nativeAutofillPopupViewAndroid);
private native void nativePopupDismissed(long nativeAutofillPopupViewAndroid);
}
......@@ -73,6 +73,10 @@ public class AutofillTest extends ChromeShellTestBase {
mGotPopupSelection.set(true);
}
@Override
public void deleteSuggestion(int listIndex) {
}
public boolean waitForCallback() throws InterruptedException {
return CriteriaHelper.pollForCriteria(new Criteria() {
@Override
......@@ -90,19 +94,19 @@ public class AutofillTest extends ChromeShellTestBase {
private AutofillSuggestion[] createTwoAutofillSuggestionArray() {
return new AutofillSuggestion[] {
new AutofillSuggestion("Sherlock Holmes", "221B Baker Street", DropdownItem.NO_ICON,
42),
new AutofillSuggestion("Arthur Dent", "West Country", DropdownItem.NO_ICON, 43),
42, false),
new AutofillSuggestion("Arthur Dent", "West Country", DropdownItem.NO_ICON, 43, false),
};
}
private AutofillSuggestion[] createFiveAutofillSuggestionArray() {
return new AutofillSuggestion[] {
new AutofillSuggestion("Sherlock Holmes", "221B Baker Street", DropdownItem.NO_ICON,
42),
new AutofillSuggestion("Arthur Dent", "West Country", DropdownItem.NO_ICON, 43),
new AutofillSuggestion("Arthos", "France", DropdownItem.NO_ICON, 44),
new AutofillSuggestion("Porthos", "France", DropdownItem.NO_ICON, 45),
new AutofillSuggestion("Aramis", "France", DropdownItem.NO_ICON, 46),
42, false),
new AutofillSuggestion("Arthur Dent", "West Country", DropdownItem.NO_ICON, 43, false),
new AutofillSuggestion("Arthos", "France", DropdownItem.NO_ICON, 44, false),
new AutofillSuggestion("Porthos", "France", DropdownItem.NO_ICON, 45, false),
new AutofillSuggestion("Aramis", "France", DropdownItem.NO_ICON, 46, false),
};
}
......
......@@ -25,7 +25,8 @@ namespace autofill {
AutofillPopupViewAndroid::AutofillPopupViewAndroid(
AutofillPopupController* controller)
: controller_(controller) {}
: controller_(controller),
deleting_index_(-1) {}
AutofillPopupViewAndroid::~AutofillPopupViewAndroid() {}
......@@ -77,6 +78,8 @@ void AutofillPopupViewAndroid::UpdateBoundsAndRedrawPopup() {
controller_->GetIconResourceID(suggestion.icon));
}
bool deletable =
controller_->GetRemovalConfirmationText(i, nullptr, nullptr);
Java_AutofillPopupBridge_addToAutofillSuggestionArray(
env,
data_array.obj(),
......@@ -84,7 +87,8 @@ void AutofillPopupViewAndroid::UpdateBoundsAndRedrawPopup() {
value.obj(),
label.obj(),
android_icon_id,
suggestion.frontend_id);
suggestion.frontend_id,
deletable);
}
Java_AutofillPopupBridge_show(
......@@ -99,6 +103,37 @@ void AutofillPopupViewAndroid::SuggestionSelected(JNIEnv* env,
controller_->AcceptSuggestion(list_index);
}
void AutofillPopupViewAndroid::DeletionRequested(JNIEnv* env,
jobject obj,
jint list_index) {
if (!controller_)
return;
base::string16 confirmation_title, confirmation_body;
if (!controller_->GetRemovalConfirmationText(list_index, &confirmation_title,
&confirmation_body)) {
return;
}
deleting_index_ = list_index;
Java_AutofillPopupBridge_confirmDeletion(
env,
java_object_.obj(),
base::android::ConvertUTF16ToJavaString(
env, confirmation_title).obj(),
base::android::ConvertUTF16ToJavaString(
env, confirmation_body).obj());
}
void AutofillPopupViewAndroid::DeletionConfirmed(JNIEnv* env,
jobject obj) {
if (!controller_)
return;
CHECK_GE(deleting_index_, 0);
controller_->RemoveSuggestion(deleting_index_);
}
void AutofillPopupViewAndroid::PopupDismissed(JNIEnv* env, jobject obj) {
if (controller_)
controller_->ViewDestroyed();
......
......@@ -29,6 +29,10 @@ class AutofillPopupViewAndroid : public AutofillPopupView {
// Called when an autofill item was selected.
void SuggestionSelected(JNIEnv* env, jobject obj, jint list_index);
void DeletionRequested(JNIEnv* env, jobject obj, jint list_index);
void DeletionConfirmed(JNIEnv* env, jobject obj);
void PopupDismissed(JNIEnv* env, jobject obj);
static bool RegisterAutofillPopupViewAndroid(JNIEnv* env);
......@@ -45,6 +49,10 @@ class AutofillPopupViewAndroid : public AutofillPopupView {
AutofillPopupController* controller_; // weak.
// The index of the last item the user long-pressed (they will be shown a
// confirmation dialog).
int deleting_index_;
// The corresponding java object.
base::android::ScopedJavaGlobalRef<jobject> java_object_;
......
......@@ -2448,6 +2448,14 @@ void AutofillDialogControllerImpl::DidAcceptSuggestion(
HidePopup();
}
bool AutofillDialogControllerImpl::GetDeletionConfirmationText(
const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) {
return false;
}
bool AutofillDialogControllerImpl::RemoveSuggestion(
const base::string16& value,
int identifier) {
......
......@@ -169,6 +169,10 @@ class AutofillDialogControllerImpl
int identifier) override;
void DidAcceptSuggestion(const base::string16& value,
int identifier) override;
bool GetDeletionConfirmationText(const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) override;
bool RemoveSuggestion(const base::string16& value, int identifier) override;
void ClearPreviewedForm() override;
......
......@@ -53,6 +53,15 @@ class AutofillPopupController : public AutofillPopupViewDelegate {
virtual const base::string16& GetElidedValueAt(size_t row) const = 0;
virtual const base::string16& GetElidedLabelAt(size_t row) const = 0;
// Returns whether the item at |list_index| can be removed. If so, fills
// out |title| and |body| (when non-null) with relevant user-facing text.
virtual bool GetRemovalConfirmationText(int index,
base::string16* title,
base::string16* body) = 0;
// Removes the suggestion at the given index.
virtual bool RemoveSuggestion(int index) = 0;
#if !defined(OS_ANDROID)
// The same font can vary based on the type of data it is showing,
// so we need to know the row.
......
......@@ -417,6 +417,38 @@ const base::string16& AutofillPopupControllerImpl::GetElidedLabelAt(
return elided_labels_[row];
}
bool AutofillPopupControllerImpl::GetRemovalConfirmationText(
int list_index,
base::string16* title,
base::string16* body) {
return delegate_->GetDeletionConfirmationText(
suggestions_[list_index].value, suggestions_[list_index].frontend_id,
title, body);
}
bool AutofillPopupControllerImpl::RemoveSuggestion(int list_index) {
if (!delegate_->RemoveSuggestion(suggestions_[list_index].value,
suggestions_[list_index].frontend_id)) {
return false;
}
// Remove the deleted element.
suggestions_.erase(suggestions_.begin() + list_index);
elided_values_.erase(elided_values_.begin() + list_index);
elided_labels_.erase(elided_labels_.begin() + list_index);
SetSelectedLine(kNoSelection);
if (HasSuggestions()) {
delegate_->ClearPreviewedForm();
UpdateBoundsAndRedrawPopup();
} else {
Hide();
}
return true;
}
#if !defined(OS_ANDROID)
const gfx::FontList& AutofillPopupControllerImpl::GetValueFontListForRow(
size_t index) const {
......@@ -499,27 +531,7 @@ bool AutofillPopupControllerImpl::RemoveSelectedLine() {
DCHECK_GE(selected_line_, 0);
DCHECK_LT(selected_line_, static_cast<int>(GetLineCount()));
if (!delegate_->RemoveSuggestion(suggestions_[selected_line_].value,
suggestions_[selected_line_].frontend_id)) {
return false;
}
// Remove the deleted element.
suggestions_.erase(suggestions_.begin() + selected_line_);
elided_values_.erase(elided_values_.begin() + selected_line_);
elided_labels_.erase(elided_labels_.begin() + selected_line_);
SetSelectedLine(kNoSelection);
if (HasSuggestions()) {
delegate_->ClearPreviewedForm();
UpdateBoundsAndRedrawPopup();
} else {
Hide();
}
return true;
return RemoveSuggestion(selected_line_);
}
int AutofillPopupControllerImpl::LineFromY(int y) {
......
......@@ -88,6 +88,10 @@ class AutofillPopupControllerImpl : public AutofillPopupController {
const autofill::Suggestion& GetSuggestionAt(size_t row) const override;
const base::string16& GetElidedValueAt(size_t row) const override;
const base::string16& GetElidedLabelAt(size_t row) const override;
bool GetRemovalConfirmationText(int list_index,
base::string16* title,
base::string16* body) override;
bool RemoveSuggestion(int list_index) override;
#if !defined(OS_ANDROID)
const gfx::FontList& GetValueFontListForRow(size_t index) const override;
const gfx::FontList& GetLabelFontList() const override;
......
......@@ -267,6 +267,14 @@ void AutofillExternalDelegate::DidAcceptSuggestion(const base::string16& value,
manager_->client()->HideAutofillPopup();
}
bool AutofillExternalDelegate::GetDeletionConfirmationText(
const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) {
return manager_->GetDeletionConfirmationText(value, identifier, title, body);
}
bool AutofillExternalDelegate::RemoveSuggestion(const base::string16& value,
int identifier) {
if (identifier > 0)
......
......@@ -42,6 +42,10 @@ class AutofillExternalDelegate : public AutofillPopupDelegate {
int identifier) override;
void DidAcceptSuggestion(const base::string16& value,
int identifier) override;
bool GetDeletionConfirmationText(const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) override;
bool RemoveSuggestion(const base::string16& value, int identifier) override;
void ClearPreviewedForm() override;
......
......@@ -700,6 +700,62 @@ void AutofillManager::OnHidePopup() {
client_->HideAutofillPopup();
}
bool AutofillManager::GetDeletionConfirmationText(const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) {
if (identifier == POPUP_ITEM_ID_AUTOCOMPLETE_ENTRY) {
if (title)
title->assign(value);
if (body) {
body->assign(l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY));
}
return true;
}
if (identifier < 0)
return false;
size_t variant = 0;
const CreditCard* credit_card = nullptr;
const AutofillProfile* profile = nullptr;
if (GetCreditCard(identifier, &credit_card)) {
if (credit_card->record_type() != CreditCard::LOCAL_CARD)
return false;
if (title)
title->assign(credit_card->TypeAndLastFourDigits());
if (body) {
body->assign(l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY));
}
return true;
} else if (GetProfile(identifier, &profile, &variant)) {
if (profile->record_type() != AutofillProfile::LOCAL_PROFILE)
return false;
if (title) {
base::string16 street_address = profile->GetRawInfo(ADDRESS_HOME_CITY);
if (!street_address.empty())
title->swap(street_address);
else
title->assign(value);
}
if (body) {
body->assign(l10n_util::GetStringUTF16(
IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY));
}
return true;
}
NOTREACHED();
return false;
}
bool AutofillManager::RemoveAutofillProfileOrCreditCard(int unique_id) {
std::string guid;
size_t variant = 0;
......
......@@ -123,6 +123,13 @@ class AutofillManager : public AutofillDownloadManager::Observer,
void OnDidFillAutofillFormData(const base::TimeTicks& timestamp);
void OnDidPreviewAutofillFormData();
// Returns true if the value/identifier is deletable. Fills out
// |title| and |body| with relevant user-facing text.
bool GetDeletionConfirmationText(const base::string16& value,
int identifier,
base::string16* title,
base::string16* body);
// Remove the credit card or Autofill profile that matches |unique_id|
// from the database. Returns true if deletion is allowed.
bool RemoveAutofillProfileOrCreditCard(int unique_id);
......
......@@ -28,6 +28,13 @@ class AutofillPopupDelegate {
virtual void DidAcceptSuggestion(const base::string16& value,
int identifier) = 0;
// Returns whether the given value can be deleted, and if true,
// fills out |title| and |body|.
virtual bool GetDeletionConfirmationText(const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) = 0;
// Delete the described suggestion. Returns true if something was deleted,
// or false if deletion is not allowed.
virtual bool RemoveSuggestion(const base::string16& value,
......
......@@ -10,6 +10,15 @@
<message name="IDS_AUTOFILL_WARNING_INSECURE_CONNECTION" desc="Warning text to show when credit card autofill is disabled because the website is not using a secure connection.">
Automatic credit card filling is disabled because this form does not use a secure connection.
</message>
<message name="IDS_AUTOFILL_DELETE_AUTOCOMPLETE_SUGGESTION_CONFIRMATION_BODY" desc="Text in a dialog to confirm that the user wants to delete an autocomplete form history suggestion.">
Remove form suggestion from Chrome?
</message>
<message name="IDS_AUTOFILL_DELETE_CREDIT_CARD_SUGGESTION_CONFIRMATION_BODY" desc="Text in a dialog to confirm that the user wants to delete a credit card from Chrome Autofill.">
Remove credit card from Chrome?
</message>
<message name="IDS_AUTOFILL_DELETE_PROFILE_SUGGESTION_CONFIRMATION_BODY" desc="Text in a dialog to confirm that the user wants to delete an address from Chrome Autofill.">
Remove address from Chrome?
</message>
<message name="IDS_AUTOFILL_CC_AMEX" desc="Full American Express credit card name.">
American Express
</message>
......
......@@ -218,6 +218,14 @@ void PasswordAutofillManager::DidAcceptSuggestion(const base::string16& value,
autofill_client_->HideAutofillPopup();
}
bool PasswordAutofillManager::GetDeletionConfirmationText(
const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) {
return false;
}
bool PasswordAutofillManager::RemoveSuggestion(const base::string16& value,
int identifier) {
// http://crbug.com/329038
......
......@@ -35,6 +35,10 @@ class PasswordAutofillManager : public autofill::AutofillPopupDelegate {
int identifier) override;
void DidAcceptSuggestion(const base::string16& value,
int identifier) override;
bool GetDeletionConfirmationText(const base::string16& value,
int identifier,
base::string16* title,
base::string16* body) override;
bool RemoveSuggestion(const base::string16& value, int identifier) override;
void ClearPreviewedForm() override;
......
......@@ -25,7 +25,7 @@ import java.util.List;
* The Autofill suggestion popup that lists relevant suggestions.
*/
public class AutofillPopup extends DropdownPopupWindow implements AdapterView.OnItemClickListener,
PopupWindow.OnDismissListener {
AdapterView.OnItemLongClickListener, PopupWindow.OnDismissListener {
/**
* The constant used to specify a separator in a list of Autofill suggestions.
......@@ -52,6 +52,12 @@ public class AutofillPopup extends DropdownPopupWindow implements AdapterView.On
* @param listIndex The index of the selected Autofill suggestion.
*/
public void suggestionSelected(int listIndex);
/**
* Initiates the deletion process for an item. (A confirm dialog should be shown.)
* @param listIndex The index of the suggestion to delete.
*/
public void deleteSuggestion(int listIndex);
}
/**
......@@ -95,6 +101,7 @@ public class AutofillPopup extends DropdownPopupWindow implements AdapterView.On
setAdapter(new DropdownAdapter(mContext, cleanedData, separators));
setRtl(isRtl);
show();
getListView().setOnItemLongClickListener(this);
}
@Override
......@@ -105,6 +112,18 @@ public class AutofillPopup extends DropdownPopupWindow implements AdapterView.On
mAutofillCallback.suggestionSelected(listIndex);
}
@Override
public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
DropdownAdapter adapter = (DropdownAdapter) parent.getAdapter();
AutofillSuggestion suggestion = (AutofillSuggestion) adapter.getItem(position);
if (!suggestion.isDeletable()) return false;
int listIndex = mSuggestions.indexOf(suggestion);
assert listIndex > -1;
mAutofillCallback.deleteSuggestion(listIndex);
return true;
}
@Override
public void onDismiss() {
mAutofillCallback.dismissed();
......
......@@ -14,18 +14,22 @@ public class AutofillSuggestion implements DropdownItem {
private final String mSublabel;
private final int mIconId;
private final int mSuggestionId;
private final boolean mDeletable;
/**
* Constructs a Autofill suggestion container.
* @param name The name of the Autofill suggestion.
* @param label The describing label of the Autofill suggestion.
* @param suggestionId The type of suggestion.
* @param deletable Whether the item can be deleted by the user.
*/
public AutofillSuggestion(String name, String label, int iconId, int suggestionId) {
public AutofillSuggestion(
String name, String label, int iconId, int suggestionId, boolean deletable) {
mLabel = name;
mSublabel = label;
mIconId = iconId;
mSuggestionId = suggestionId;
mDeletable = deletable;
}
@Override
......@@ -56,4 +60,8 @@ public class AutofillSuggestion implements DropdownItem {
public int getSuggestionId() {
return mSuggestionId;
}
public boolean isDeletable() {
return mDeletable;
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment