All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.android.tools.lint.checks.ButtonDetector Maven / Gradle / Ivy

There is a newer version: 25.3.0
Show newest version
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.tools.lint.checks;

import static com.android.SdkConstants.ANDROID_STRING_PREFIX;
import static com.android.SdkConstants.ANDROID_URI;
import static com.android.SdkConstants.ATTR_BACKGROUND;
import static com.android.SdkConstants.ATTR_ID;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_LEFT;
import static com.android.SdkConstants.ATTR_LAYOUT_ALIGN_PARENT_RIGHT;
import static com.android.SdkConstants.ATTR_LAYOUT_TO_LEFT_OF;
import static com.android.SdkConstants.ATTR_LAYOUT_TO_RIGHT_OF;
import static com.android.SdkConstants.ATTR_NAME;
import static com.android.SdkConstants.ATTR_ORIENTATION;
import static com.android.SdkConstants.ATTR_STYLE;
import static com.android.SdkConstants.ATTR_TEXT;
import static com.android.SdkConstants.BUTTON;
import static com.android.SdkConstants.LINEAR_LAYOUT;
import static com.android.SdkConstants.RELATIVE_LAYOUT;
import static com.android.SdkConstants.STRING_PREFIX;
import static com.android.SdkConstants.TABLE_ROW;
import static com.android.SdkConstants.TAG_STRING;
import static com.android.SdkConstants.VALUE_SELECTABLE_ITEM_BACKGROUND;
import static com.android.SdkConstants.VALUE_TRUE;
import static com.android.SdkConstants.VALUE_VERTICAL;

import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.resources.ResourceFolderType;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.LintUtils;
import com.android.tools.lint.detector.api.Location;
import com.android.tools.lint.detector.api.ResourceXmlDetector;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.android.tools.lint.detector.api.Speed;
import com.android.tools.lint.detector.api.XmlContext;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Check which looks at the order of buttons in dialogs and makes sure that
 * "the dismissive action of a dialog is always on the left whereas the affirmative actions
 * are on the right."
 * 

* This only looks for the affirmative and dismissive actions named "OK" and "Cancel"; * "Cancel" usually works, but the affirmative action often has many other names -- "Done", * "Send", "Go", etc. *

* TODO: Perhaps we should look for Yes/No dialogs and suggested they be rephrased as * Cancel/OK dialogs? Similarly, consider "Abort" a synonym for "Cancel" ? */ public class ButtonDetector extends ResourceXmlDetector { /** Name of cancel value ("Cancel") */ private static final String CANCEL_LABEL = "Cancel"; /** Name of OK value ("Cancel") */ private static final String OK_LABEL = "OK"; /** Name of Back value ("Back") */ private static final String BACK_LABEL = "Back"; /** Layout text attribute reference to {@code @android:string/ok} */ private static final String ANDROID_OK_RESOURCE = ANDROID_STRING_PREFIX + "ok"; //$NON-NLS-1$ /** Layout text attribute reference to {@code @android:string/cancel} */ private static final String ANDROID_CANCEL_RESOURCE = ANDROID_STRING_PREFIX + "cancel"; //$NON-NLS-1$ private static final Implementation IMPLEMENTATION = new Implementation( ButtonDetector.class, Scope.RESOURCE_FILE_SCOPE); /** The main issue discovered by this detector */ public static final Issue ORDER = Issue.create( "ButtonOrder", //$NON-NLS-1$ "Button order", "According to the Android Design Guide,\n" + "\n" + "\"Action buttons are typically Cancel and/or OK, with OK indicating the preferred " + "or most likely action. However, if the options consist of specific actions such " + "as Close or Wait rather than a confirmation or cancellation of the action " + "described in the content, then all the buttons should be active verbs. As a rule, " + "the dismissive action of a dialog is always on the left whereas the affirmative " + "actions are on the right.\"\n" + "\n" + "This check looks for button bars and buttons which look like cancel buttons, " + "and makes sure that these are on the left.", Category.USABILITY, 8, Severity.WARNING, IMPLEMENTATION) .addMoreInfo( "http://developer.android.com/design/building-blocks/dialogs.html"); //$NON-NLS-1$ /** The main issue discovered by this detector */ public static final Issue STYLE = Issue.create( "ButtonStyle", //$NON-NLS-1$ "Button should be borderless", "Button bars typically use a borderless style for the buttons. Set the " + "`style=\"?android:attr/buttonBarButtonStyle\"` attribute " + "on each of the buttons, and set `style=\"?android:attr/buttonBarStyle\"` on " + "the parent layout", Category.USABILITY, 5, Severity.WARNING, IMPLEMENTATION) .addMoreInfo( "http://developer.android.com/design/building-blocks/buttons.html"); //$NON-NLS-1$ /** The main issue discovered by this detector */ public static final Issue BACK_BUTTON = Issue.create( "BackButton", //$NON-NLS-1$ "Back button", // TODO: Look for ">" as label suffixes as well "According to the Android Design Guide,\n" + "\n" + "\"Other platforms use an explicit back button with label to allow the user " + "to navigate up the application's hierarchy. Instead, Android uses the main " + "action bar's app icon for hierarchical navigation and the navigation bar's " + "back button for temporal navigation.\"" + "\n" + "This check is not very sophisticated (it just looks for buttons with the " + "label \"Back\"), so it is disabled by default to not trigger on common " + "scenarios like pairs of Back/Next buttons to paginate through screens.", Category.USABILITY, 6, Severity.WARNING, IMPLEMENTATION) .setEnabledByDefault(false) .addMoreInfo( "http://developer.android.com/design/patterns/pure-android.html"); //$NON-NLS-1$ /** The main issue discovered by this detector */ public static final Issue CASE = Issue.create( "ButtonCase", //$NON-NLS-1$ "Cancel/OK dialog button capitalization", "The standard capitalization for OK/Cancel dialogs is \"OK\" and \"Cancel\". " + "To ensure that your dialogs use the standard strings, you can use " + "the resource strings @android:string/ok and @android:string/cancel.", Category.USABILITY, 2, Severity.WARNING, IMPLEMENTATION); /** Set of resource names whose value was either OK or Cancel */ private Set mApplicableResources; /** * Map of resource names we'd like resolved into strings in phase 2. The * values should be filled in with the actual string contents. */ private Map mKeyToLabel; /** * Set of elements we've already warned about. If we've already complained * about a cancel button, don't also report the OK button (since it's listed * for the warnings on OK buttons). */ private Set mIgnore; /** Constructs a new {@link ButtonDetector} */ public ButtonDetector() { } @NonNull @Override public Speed getSpeed() { return Speed.FAST; } @Override public Collection getApplicableElements() { return Arrays.asList(BUTTON, TAG_STRING); } @Override public boolean appliesTo(@NonNull ResourceFolderType folderType) { return folderType == ResourceFolderType.LAYOUT || folderType == ResourceFolderType.VALUES; } @Override public void afterCheckProject(@NonNull Context context) { int phase = context.getPhase(); if (phase == 1 && mApplicableResources != null) { // We found resources for the string "Cancel"; perform a second pass // where we check layout text attributes against these strings. context.getDriver().requestRepeat(this, Scope.RESOURCE_FILE_SCOPE); } } private static String stripLabel(String text) { text = text.trim(); if (text.length() > 2 && (text.charAt(0) == '"' || text.charAt(0) == '\'') && (text.charAt(0) == text.charAt(text.length() - 1))) { text = text.substring(1, text.length() - 1); } return text; } @Override public void visitElement(@NonNull XmlContext context, @NonNull Element element) { // This detector works in two passes. // In pass 1, it looks in layout files for hardcoded strings of "Cancel", or // references to @string/cancel or @android:string/cancel. // It also looks in values/ files for strings whose value is "Cancel", // and if found, stores the corresponding keys in a map. (This is necessary // since value files are processed after layout files). // Then, if at the end of phase 1 any "Cancel" string resources were // found in the value files, then it requests a *second* phase, // where it looks only for





© 2015 - 2024 Weber Informatics LLC | Privacy Policy