blob: f11eaf428a142ee2e292c201d0f5f08119f5697f [file] [log] [blame]
/*
* Copyright (C) 2013 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 com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Context;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.google.common.collect.Maps;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import lombok.ast.AstVisitor;
import lombok.ast.Cast;
import lombok.ast.Expression;
import lombok.ast.MethodInvocation;
import lombok.ast.StrictListAccessor;
/**
* Detector looking for casts on th result of context.getSystemService which are suspect
*/
public class ServiceCastDetector extends Detector implements Detector.JavaScanner {
/** The main issue discovered by this detector */
public static final Issue ISSUE = Issue.create(
"ServiceCast", //$NON-NLS-1$
"Wrong system service casts",
"When you call `Context#getSystemService()`, the result is typically cast to " +
"a specific interface. This lint check ensures that the cast is compatible with " +
"the expected type of the return value.",
Category.CORRECTNESS,
6,
Severity.FATAL,
new Implementation(
ServiceCastDetector.class,
Scope.JAVA_FILE_SCOPE));
/** Constructs a new {@link ServiceCastDetector} check */
public ServiceCastDetector() {
}
@Override
public boolean appliesTo(@NonNull Context context, @NonNull File file) {
return true;
}
// ---- Implements JavaScanner ----
@Override
public List<String> getApplicableMethodNames() {
return Collections.singletonList("getSystemService"); //$NON-NLS-1$
}
@Override
public void visitMethod(@NonNull JavaContext context, @Nullable AstVisitor visitor,
@NonNull MethodInvocation node) {
if (node.getParent() instanceof Cast) {
Cast cast = (Cast) node.getParent();
StrictListAccessor<Expression, MethodInvocation> args = node.astArguments();
if (args.size() == 1) {
String name = stripPackage(args.first().toString());
String expectedClass = getExpectedType(name);
if (expectedClass != null) {
String castType = cast.astTypeReference().getTypeName();
if (castType.indexOf('.') == -1) {
expectedClass = stripPackage(expectedClass);
}
if (!castType.equals(expectedClass)) {
// It's okay to mix and match
// android.content.ClipboardManager and android.text.ClipboardManager
if (isClipboard(castType) && isClipboard(expectedClass)) {
return;
}
String message = String.format(
"Suspicious cast to `%1$s` for a `%2$s`: expected `%3$s`",
stripPackage(castType), name, stripPackage(expectedClass));
context.report(ISSUE, node, context.getLocation(cast), message);
}
}
}
}
}
private static boolean isClipboard(String cls) {
return cls.equals("android.content.ClipboardManager") //$NON-NLS-1$
|| cls.equals("android.text.ClipboardManager"); //$NON-NLS-1$
}
private static String stripPackage(String fqcn) {
int index = fqcn.lastIndexOf('.');
if (index != -1) {
fqcn = fqcn.substring(index + 1);
}
return fqcn;
}
@Nullable
private static String getExpectedType(@NonNull String value) {
return getServiceMap().get(value);
}
@NonNull
private static Map<String, String> getServiceMap() {
if (sServiceMap == null) {
final int EXPECTED_SIZE = 49;
sServiceMap = Maps.newHashMapWithExpectedSize(EXPECTED_SIZE);
sServiceMap.put("ACCESSIBILITY_SERVICE", "android.view.accessibility.AccessibilityManager");
sServiceMap.put("ACCOUNT_SERVICE", "android.accounts.AccountManager");
sServiceMap.put("ACTIVITY_SERVICE", "android.app.ActivityManager");
sServiceMap.put("ALARM_SERVICE", "android.app.AlarmManager");
sServiceMap.put("APPWIDGET_SERVICE", "android.appwidget.AppWidgetManager");
sServiceMap.put("APP_OPS_SERVICE", "android.app.AppOpsManager");
sServiceMap.put("AUDIO_SERVICE", "android.media.AudioManager");
sServiceMap.put("BATTERY_SERVICE", "android.os.BatteryManager");
sServiceMap.put("BLUETOOTH_SERVICE", "android.bluetooth.BluetoothManager");
sServiceMap.put("CAMERA_SERVICE", "android.hardware.camera2.CameraManager");
sServiceMap.put("CAPTIONING_SERVICE", "android.view.accessibility.CaptioningManager");
sServiceMap.put("CLIPBOARD_SERVICE", "android.text.ClipboardManager");
sServiceMap.put("CONNECTIVITY_SERVICE", "android.net.ConnectivityManager");
sServiceMap.put("CONSUMER_IR_SERVICE", "android.hardware.ConsumerIrManager");
sServiceMap.put("DEVICE_POLICY_SERVICE", "android.app.admin.DevicePolicyManager");
sServiceMap.put("DISPLAY_SERVICE", "android.hardware.display.DisplayManager");
sServiceMap.put("DOWNLOAD_SERVICE", "android.app.DownloadManager");
sServiceMap.put("DROPBOX_SERVICE", "android.os.DropBoxManager");
sServiceMap.put("INPUT_METHOD_SERVICE", "android.view.inputmethod.InputMethodManager");
sServiceMap.put("INPUT_SERVICE", "android.hardware.input.InputManager");
sServiceMap.put("JOB_SCHEDULER_SERVICE", "android.app.job.JobScheduler");
sServiceMap.put("KEYGUARD_SERVICE", "android.app.KeyguardManager");
sServiceMap.put("LAUNCHER_APPS_SERVICE", "android.content.pm.LauncherApps");
sServiceMap.put("LAYOUT_INFLATER_SERVICE", "android.view.LayoutInflater");
sServiceMap.put("LOCATION_SERVICE", "android.location.LocationManager");
sServiceMap.put("MEDIA_PROJECTION_SERVICE", "android.media.projection.MediaProjectionManager");
sServiceMap.put("MEDIA_ROUTER_SERVICE", "android.media.MediaRouter");
sServiceMap.put("MEDIA_SESSION_SERVICE", "android.media.session.MediaSessionManager");
sServiceMap.put("NFC_SERVICE", "android.nfc.NfcManager");
sServiceMap.put("NOTIFICATION_SERVICE", "android.app.NotificationManager");
sServiceMap.put("NSD_SERVICE", "android.net.nsd.NsdManager");
sServiceMap.put("POWER_SERVICE", "android.os.PowerManager");
sServiceMap.put("PRINT_SERVICE", "android.print.PrintManager");
sServiceMap.put("RESTRICTIONS_SERVICE", "android.content.RestrictionsManager");
sServiceMap.put("SEARCH_SERVICE", "android.app.SearchManager");
sServiceMap.put("SENSOR_SERVICE", "android.hardware.SensorManager");
sServiceMap.put("STORAGE_SERVICE", "android.os.storage.StorageManager");
sServiceMap.put("TELECOM_SERVICE", "android.telecom.TelecomManager");
sServiceMap.put("TELEPHONY_SERVICE", "android.telephony.TelephonyManager");
sServiceMap.put("TEXT_SERVICES_MANAGER_SERVICE", "android.view.textservice.TextServicesManager");
sServiceMap.put("TV_INPUT_SERVICE", "android.media.tv.TvInputManager");
sServiceMap.put("UI_MODE_SERVICE", "android.app.UiModeManager");
sServiceMap.put("USB_SERVICE", "android.hardware.usb.UsbManager");
sServiceMap.put("USER_SERVICE", "android.os.UserManager");
sServiceMap.put("VIBRATOR_SERVICE", "android.os.Vibrator");
sServiceMap.put("WALLPAPER_SERVICE", "com.android.server.WallpaperService");
sServiceMap.put("WIFI_P2P_SERVICE", "android.net.wifi.p2p.WifiP2pManager");
sServiceMap.put("WIFI_SERVICE", "android.net.wifi.WifiManager");
sServiceMap.put("WINDOW_SERVICE", "android.view.WindowManager");
assert sServiceMap.size() == EXPECTED_SIZE : sServiceMap.size();
}
return sServiceMap;
}
private static Map<String, String> sServiceMap;
}