| /* |
| * Copyright (C) 2011 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.tools.lint.checks.infrastructure.ProjectDescription.Type.LIBRARY; |
| |
| import com.android.tools.lint.checks.infrastructure.ProjectDescription; |
| import com.android.tools.lint.checks.infrastructure.TestFile; |
| import com.android.tools.lint.detector.api.Detector; |
| |
| public class SecurityDetectorTest extends AbstractCheckTest { |
| @Override |
| protected Detector getDetector() { |
| return new SecurityDetector(); |
| } |
| |
| @Override |
| protected boolean allowCompilationErrors() { |
| // Some of these unit tests are still relying on source code that references |
| // unresolved symbols etc. |
| return true; |
| } |
| |
| public void testBroken() { |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <service\n" |
| + " android:exported=\"true\"\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expect( |
| "" |
| + "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" |
| + " <service\n" |
| + " ~~~~~~~\n" |
| + "0 errors, 1 warnings\n") |
| .expectFixDiffs( |
| "" |
| + "Fix for AndroidManifest.xml line 12: Set permission:\n" |
| + "@@ -16 +16\n" |
| + "+ android:permission=\"[TODO]|\"\n" |
| + "Fix for AndroidManifest.xml line 12: Set exported=\"false\":\n" |
| + "@@ -14 +14\n" |
| + "- android:exported=\"true\"\n" |
| + "+ android:exported=\"false\""); |
| } |
| |
| public void testBroken2() { |
| String expected = |
| "" |
| + "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" |
| + " <service\n" |
| + " ~~~~~~~\n" |
| + "0 errors, 1 warnings\n"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <service\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expect(expected); |
| } |
| |
| public void testBroken3() { |
| // Not defining exported, but have intent-filters |
| String expected = |
| "" |
| + "AndroidManifest.xml:12: Warning: Exported service does not require permission [ExportedService]\n" |
| + " <service\n" |
| + " ~~~~~~~\n" |
| + "0 errors, 1 warnings\n"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <service\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expect(expected); |
| } |
| |
| public void testOk1() { |
| // Defines a permission on the <service> element |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <service\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:permission=\"android.permission.RECEIVE_BOOT_COMPLETED\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testOk2() { |
| // Defines a permission on the parent <application> element |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:permission=\"android.permission.RECEIVE_BOOT_COMPLETED\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <service\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testUri() { |
| String expected = |
| "" |
| + "AndroidManifest.xml:25: Warning: Content provider shares everything; this is potentially dangerous [GrantAllUris]\n" |
| + " <grant-uri-permission android:path=\"/\"/>\n" |
| + " ~~~~~~~~~~~~~~~~\n" |
| + "AndroidManifest.xml:26: Warning: Content provider shares everything; this is potentially dangerous [GrantAllUris]\n" |
| + " <grant-uri-permission android:pathPrefix=\"/\"/>\n" |
| + " ~~~~~~~~~~~~~~~~~~~~~~\n" |
| + "0 errors, 2 warnings\n"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <activity\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\".Foo2Activity\"\n" |
| + " android:permission=\"Foo\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"android.intent.action.MAIN\" />\n" |
| + "\n" |
| + " <category android:name=\"android.intent.category.LAUNCHER\" />\n" |
| + " </intent-filter>\n" |
| + " </activity>\n" |
| + " <!-- good: -->\n" |
| + " <grant-uri-permission android:pathPrefix=\"/all_downloads/\"/>\n" |
| + " <!-- bad: -->\n" |
| + " <grant-uri-permission android:path=\"/\"/>\n" |
| + " <grant-uri-permission android:pathPrefix=\"/\"/>\n" |
| + " <grant-uri-permission android:pathPattern=\".*\"/>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expect(expected); |
| } |
| |
| // exportprovider1.xml has two exported content providers with no permissions |
| public void testContentProvider1() { |
| String expected = |
| "" |
| + "AndroidManifest.xml:14: Warning: Exported content providers can provide access to potentially sensitive data [ExportedContentProvider]\n" |
| + " <provider\n" |
| + " ~~~~~~~~\n" |
| + "AndroidManifest.xml:20: Warning: Exported content providers can provide access to potentially sensitive data [ExportedContentProvider]\n" |
| + " <provider\n" |
| + " ~~~~~~~~\n" |
| + "0 errors, 2 warnings"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + "\n" |
| + " <!-- exported implicitly, fail -->\n" |
| + " <provider\n" |
| + " android:name=\"com.sample.provider.providerClass1\"\n" |
| + " android:authorities=\"com.sample.provider.providerData\">\n" |
| + " </provider>\n" |
| + "\n" |
| + " <!-- exported explicitly, fail -->\n" |
| + " <provider\n" |
| + " android:exported=\"true\"\n" |
| + " android:name=\"com.sample.provider.providerClass2\"\n" |
| + " android:authorities=\"com.sample.provider.providerData\">\n" |
| + " </provider>\n" |
| + "\n" |
| + " <!-- not exported, win -->\n" |
| + " <provider\n" |
| + " android:exported=\"false\"\n" |
| + " android:name=\"com.sample.provider.providerClass3\"\n" |
| + " android:authorities=\"com.sample.provider.providerData\">\n" |
| + " </provider>\n" |
| + " </application>\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expect(expected) |
| .verifyFixes() |
| .window(1) |
| .expectFixDiffs( |
| "" |
| + "Fix for AndroidManifest.xml line 13: Set exported=\"false\":\n" |
| + "@@ -16 +16\n" |
| + " android:name=\"com.sample.provider.providerClass1\"\n" |
| + "- android:authorities=\"com.sample.provider.providerData\" >\n" |
| + "+ android:authorities=\"com.sample.provider.providerData\"\n" |
| + "+ android:exported=\"false\" >\n" |
| + " </provider>\n" |
| + "Fix for AndroidManifest.xml line 19: Set exported=\"false\":\n" |
| + "@@ -23 +23\n" |
| + " android:authorities=\"com.sample.provider.providerData\"\n" |
| + "- android:exported=\"true\" >\n" |
| + "+ android:exported=\"false\" >\n" |
| + " </provider>\n"); |
| } |
| |
| // exportprovider2.xml has no un-permissioned exported content providers |
| public void testContentProvider2() { |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + "\n" |
| + " <!-- read+write permission attribute, win -->\n" |
| + " <provider\n" |
| + " android:name=\"com.sample.provider.providerClass\"\n" |
| + " android:authorities=\"com.sample.provider.providerData\"\n" |
| + " android:readPermission=\"com.sample.provider.READ_PERMISSON\"\n" |
| + " android:writePermission=\"com.sample.provider.WRITE_PERMISSON\">\n" |
| + " </provider>\n" |
| + "\n" |
| + " <!-- permission attribute, win -->\n" |
| + " <provider\n" |
| + " android:name=\"com.sample.provider.providerClass\"\n" |
| + " android:authorities=\"com.sample.provider.providerData\"\n" |
| + " android:permission=\"com.sample.provider.PERMISSION\">\n" |
| + " </provider>\n" |
| + "\n" |
| + " <!-- path-permission, win -->\n" |
| + " <provider\n" |
| + " android:name=\"com.sample.provider.providerClass\"\n" |
| + " android:authorities=\"com.sample.provider.providerData\">\n" |
| + " <path-permission\n" |
| + " android:pathPrefix=\"/hello\"\n" |
| + " android:permission=\"com.sample.provider.PERMISSION\">\n" |
| + " </path-permission>\n" |
| + " </provider>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testWorldWriteable() { |
| String expected = |
| "" |
| + "src/test/pkg/WorldWriteableFile.java:41: Warning: Setting file permissions to world-readable can be risky, review carefully [SetWorldReadable]\n" |
| + " mFile.setReadable(true, false);\n" |
| + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:48: Warning: Setting file permissions to world-writable can be risky, review carefully [SetWorldWritable]\n" |
| + " mFile.setWritable(true, false);\n" |
| + " ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:27: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" |
| + " out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);\n" |
| + " ~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:32: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" |
| + " prefs = getSharedPreferences(mFile.getName(), MODE_WORLD_READABLE);\n" |
| + " ~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:36: Warning: Using MODE_WORLD_READABLE when creating files can be risky, review carefully [WorldReadableFiles]\n" |
| + " dir = getDir(mFile.getName(), MODE_WORLD_READABLE);\n" |
| + " ~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:26: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" |
| + " out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);\n" |
| + " ~~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:31: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" |
| + " prefs = getSharedPreferences(mFile.getName(), MODE_WORLD_WRITEABLE);\n" |
| + " ~~~~~~~~~~~~~~~~~~~~\n" |
| + "src/test/pkg/WorldWriteableFile.java:35: Warning: Using MODE_WORLD_WRITEABLE when creating files can be risky, review carefully [WorldWriteableFiles]\n" |
| + " dir = getDir(mFile.getName(), MODE_WORLD_WRITEABLE);\n" |
| + " ~~~~~~~~~~~~~~~~~~~~\n" |
| + "0 errors, 8 warnings\n"; |
| lint().files( |
| java( |
| "" |
| + "package test.pkg;\n" |
| + "\n" |
| + "import java.io.File;\n" |
| + "import java.io.IOException;\n" |
| + "import java.io.OutputStream;\n" |
| + "import java.io.InputStream;\n" |
| + "import java.io.FileNotFoundException;\n" |
| + "import android.content.Context;\n" |
| + "import android.content.SharedPreferences;\n" |
| + "import android.app.Activity;\n" |
| + "import android.os.Bundle;\n" |
| + "\n" |
| + "public class WorldWriteableFile extends Activity {\n" |
| + " File mFile;\n" |
| + " Context mContext;\n" |
| + "\n" |
| + " public void foo() {\n" |
| + " OutputStream out = null;\n" |
| + " SharedPreferences prefs = null;\n" |
| + " File dir = null;\n" |
| + "\n" |
| + " boolean success = false;\n" |
| + " try {\n" |
| + " //out = openFileOutput(mFile.getName()); // ok\n" |
| + " out = openFileOutput(mFile.getName(), MODE_PRIVATE); // ok\n" |
| + " out = openFileOutput(mFile.getName(), MODE_WORLD_WRITEABLE);\n" |
| + " out = openFileOutput(mFile.getName(), MODE_WORLD_READABLE);\n" |
| + "\n" |
| + " prefs = getSharedPreferences(mFile.getName(), 0); // ok\n" |
| + " prefs = getSharedPreferences(mFile.getName(), MODE_PRIVATE); // ok\n" |
| + " prefs = getSharedPreferences(mFile.getName(), MODE_WORLD_WRITEABLE);\n" |
| + " prefs = getSharedPreferences(mFile.getName(), MODE_WORLD_READABLE);\n" |
| + "\n" |
| + " dir = getDir(mFile.getName(), MODE_PRIVATE); // ok\n" |
| + " dir = getDir(mFile.getName(), MODE_WORLD_WRITEABLE);\n" |
| + " dir = getDir(mFile.getName(), MODE_WORLD_READABLE);\n" |
| + "\n" |
| + " mFile.setReadable(true, true); // ok\n" |
| + " mFile.setReadable(false, true); // ok\n" |
| + " mFile.setReadable(false, false); // ok\n" |
| + " mFile.setReadable(true, false);\n" |
| + " mFile.setReadable(true); // ok\n" |
| + " mFile.setReadable(false); // ok\n" |
| + "\n" |
| + " mFile.setWritable(true, true); // ok\n" |
| + " mFile.setWritable(false, true); // ok\n" |
| + " mFile.setWritable(false, false); // ok\n" |
| + " mFile.setWritable(true, false);\n" |
| + " mFile.setWritable(true); // ok\n" |
| + " mFile.setWritable(false); // ok\n" |
| + "\n" |
| + " // Flickr.get().downloadPhoto(params[0], Flickr.PhotoSize.LARGE,\n" |
| + " // out);\n" |
| + " success = true;\n" |
| + " } catch (FileNotFoundException e) {\n" |
| + " }\n" |
| + " }\n" |
| + "}\n")) |
| .run() |
| .expect(expected); |
| } |
| |
| public void testReceiver0() { |
| // Activities that do not have intent-filters do not need warnings |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <receiver\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testReceiver1() { |
| String expected = |
| "" |
| + "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" |
| + " <receiver\n" |
| + " ~~~~~~~~\n" |
| + "0 errors, 1 warnings"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <receiver\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expect(expected) |
| .verifyFixes() |
| .window(1) |
| .expectFixDiffs( |
| "" |
| + "Fix for AndroidManifest.xml line 12: Set permission:\n" |
| + "@@ -14 +14\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + "- android:label=\"@string/app_name\" >\n" |
| + "+ android:label=\"@string/app_name\"\n" |
| + "+ android:permission=\"[TODO]|\" >\n" |
| + " <intent-filter>"); |
| } |
| |
| public void testReceiver2() { |
| // Defines a permission on the <activity> element |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <receiver\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:permission=\"android.permission.RECEIVE_BOOT_COMPLETED\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testReceiver3() { |
| // Defines a permission on the parent <application> element |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:permission=\"android.permission.RECEIVE_BOOT_COMPLETED\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <receiver\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testReceiver4() { |
| // Not defining exported, but have intent-filters |
| String expected = |
| "" |
| + "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" |
| + " <receiver\n" |
| + " ~~~~~~~~\n" |
| + "0 errors, 1 warnings"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <receiver\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expect(expected) |
| .verifyFixes() |
| // Make sure we don't get any fixes if in robot mode (e.g. lintFix Gradle task) |
| .robot(true) |
| .expectFixDiffs("") |
| // But in the IDE, offer quickfixes: |
| .robot(false) |
| .window(1) |
| .expectFixDiffs( |
| "" |
| + "Fix for AndroidManifest.xml line 12: Set permission:\n" |
| + "@@ -15 +15\n" |
| + " android:label=\"@string/app_name\"\n" |
| + "+ android:permission=\"[TODO]|\"\n" |
| + " android:process=\":remote\" >"); |
| } |
| |
| public void testReceiverWithTodo() { |
| // User applied quickfix which put in temporary todo marker; should not |
| // be treated as having solved the problem |
| String expected = |
| "" |
| + "AndroidManifest.xml:12: Warning: Exported receiver does not require permission [ExportedReceiver]\n" |
| + " <receiver\n" |
| + " ~~~~~~~~\n" |
| + "0 errors, 1 warnings"; |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <receiver\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\"" |
| + " android:permission=\"TODO\">\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expect(expected) |
| .verifyFixes() |
| .robot(false) |
| .window(1) |
| .expectFixDiffs( |
| "" |
| + "Fix for AndroidManifest.xml line 12: Set permission:\n" |
| + "@@ -15 +15\n" |
| + " android:label=\"@string/app_name\"\n" |
| + "- android:permission=\"TODO\"\n" |
| + "+ android:permission=\"[TODO]|\"\n" |
| + " android:process=\":remote\" >"); |
| } |
| |
| public void testReceiver5() { |
| // Intent filter for standard Android action |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\">\n" |
| + " <receiver>\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"android.intent.action.BOOT_COMPLETED\" />\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n" |
| + "\n"), |
| mStrings) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testStandard() { |
| // Various regression tests for http://code.google.com/p/android/issues/detail?id=33976 |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + "\n" |
| + " <receiver android:name=\".DockReceiver\" >\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"android.intent.action.DOCK_EVENT\" />\n" |
| + " <action android:name=\"android.app.action.ENTER_CAR_MODE\" />\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + "\n" |
| + " <receiver\n" |
| + " android:name=\"com.foo.BarReceiver\"\n" |
| + " android:enabled=\"false\" >\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"android.intent.action.ACTION_POWER_CONNECTED\" />\n" |
| + " <action android:name=\"android.net.conn.CONNECTIVITY_CHANGE\" />\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + "\n" |
| + " <receiver\n" |
| + " android:name=\".AppWidget\"\n" |
| + " android:exported=\"true\"\n" |
| + " android:label=\"@string/label\" >\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"android.appwidget.action.APPWIDGET_UPDATE\" />\n" |
| + " </intent-filter>\n" |
| + "\n" |
| + " <meta-data\n" |
| + " android:name=\"android.appwidget.provider\"\n" |
| + " android:resource=\"@xml/config\" />\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n")) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testUsingInstallReferrerReceiver() { |
| // Regression test for https://code.google.com/p/android/issues/detail?id=73934 |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"test.pkg\" >\n" |
| + "\n" |
| + " <uses-permission android:name=\"android.permission.internet\"/>\n" |
| + " <application\n" |
| + " android:allowBackup=\"true\"\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:theme=\"@style/AppTheme\" >\n" |
| + " <activity\n" |
| + " android:name=\".MyActivity\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"android.intent.action.MAIN\" />\n" |
| + "\n" |
| + " <category android:name=\"android.intent.category.LAUNCHER\" />\n" |
| + " </intent-filter>\n" |
| + " </activity>\n" |
| + "\n" |
| + " <!-- Used for install referrer tracking-->\n" |
| + " <service android:name=\"com.google.android.gms.tagmanager.InstallReferrerService\"/>\n" |
| + " <receiver\n" |
| + " android:name=\"com.google.android.gms.tagmanager.InstallReferrerReceiver\"\n" |
| + " android:exported=\"true\">\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"com.android.vending.INSTALL_REFERRER\" />\n" |
| + " </intent-filter>\n" |
| + " </receiver>\n" |
| + " </application>\n" |
| + "</manifest>\n")) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testGmsWearable() { |
| // As documented in |
| // https://developer.android.com/training/wearables/data-layer/events.html |
| // you shouldn't need a permission here. |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"test.pkg\" >\n" |
| + "\n" |
| + " <uses-permission android:name=\"android.permission.internet\"/>\n" |
| + " <application\n" |
| + " android:allowBackup=\"true\"\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:theme=\"@style/AppTheme\" >\n" |
| + " <service android:name=\".DataLayerListenerService\">\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"com.google.android.gms.wearable.DATA_CHANGED\" />\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "</manifest>\n")) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testSlices() { |
| // Intent filter for standard Android action |
| lint().files( |
| manifest( |
| "" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"${packageName}\">\n" |
| + " <application>\n" |
| + " <provider android:name=\"MySliceProvider\"\n" |
| + " android:authorities=\"com.example.app\"\n" |
| + " android:exported=\"true\" >\n" |
| + " <intent-filter>\n" |
| + " <action android:name=\"android.intent.action.VIEW\" />\n" |
| + " <category android:name=\"android.app.slice.category.SLICE\" />\n" |
| + " </intent-filter>\n" |
| + " </provider>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n")) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testManifestMerger() { |
| // Regression test for |
| // 163364847: ExportedContentProvider doesn't handle tools:node="remove" |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " xmlns:tools=\"http://schemas.android.com/tools\"\n" |
| + " package=\"test.pkg\">\n" |
| + " <application\n" |
| + " android:allowBackup=\"true\"\n" |
| + " android:icon=\"@mipmap/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:roundIcon=\"@mipmap/ic_launcher_round\"\n" |
| + " android:supportsRtl=\"true\"\n" |
| + " android:theme=\"@style/AppTheme\">\n" |
| + " <provider\n" |
| + " android:name=\"test.pkg.LibContentProvider\"\n" |
| + " tools:node=\"remove\" />\n" |
| + " </application>\n" |
| + "</manifest>\n")) |
| .run() |
| .expectClean(); |
| } |
| |
| public void testLibraryActivityConsumedFromTargetPreS() { |
| // Check that implicitly exported via intent filter works for target < 31 |
| ProjectDescription lib = project(noExportButIntentFilter).type(LIBRARY); |
| ProjectDescription app = project(manifest().minSdk(16).targetSdk(19)).dependsOn(lib); |
| |
| lint().projects(lib, app) |
| .run() |
| .expect( |
| "" |
| + "../lib/AndroidManifest.xml:11: Warning: Exported service does not require permission [ExportedService]\n" |
| + " <service\n" |
| + " ~~~~~~~\n" |
| + "0 errors, 1 warnings"); |
| } |
| |
| public void testLibraryActivityConsumedFromTargetS() { |
| // Check that not implicitly exported via intent filter for target >= 31 |
| ProjectDescription lib = project(noExportButIntentFilter).type(LIBRARY); |
| ProjectDescription app = project(manifest().minSdk(16).targetSdk(31)).dependsOn(lib); |
| lint().projects(lib, app).run().expectClean(); |
| } |
| |
| public void testQueries() { |
| // Regression test for 233656713: ignore providers in queries |
| // Sample snippet from |
| // https://developer.android.com/training/package-visibility/declaring#provider-authority |
| lint().files( |
| xml( |
| "AndroidManifest.xml", |
| "" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"com.example.suite.enterprise\">\n" |
| + " <queries>\n" |
| + " <provider android:authorities=\"com.example.settings.files\" />\n" |
| + " </queries>\n" |
| + "</manifest>")) |
| .run() |
| .expectClean(); |
| } |
| |
| private final TestFile noExportButIntentFilter = |
| manifest( |
| "" |
| + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n" |
| + " package=\"foo.bar2\"\n" |
| + " android:versionCode=\"1\"\n" |
| + " android:versionName=\"1.0\" >\n" |
| + "\n" |
| + " <uses-sdk android:minSdkVersion=\"14\" />\n" |
| + "\n" |
| + " <application\n" |
| + " android:icon=\"@drawable/ic_launcher\"\n" |
| + " android:label=\"@string/app_name\" >\n" |
| + " <service\n" |
| + " android:label=\"@string/app_name\"\n" |
| + " android:name=\"com.sample.service.serviceClass\"\n" |
| + " android:process=\":remote\" >\n" |
| + " <intent-filter >\n" |
| + " <action android:name=\"com.sample.service.serviceClass\" >\n" |
| + " </action>\n" |
| + " </intent-filter>\n" |
| + " </service>\n" |
| + " </application>\n" |
| + "\n" |
| + "</manifest>\n"); |
| |
| @SuppressWarnings("all") // Sample code |
| private TestFile mStrings = |
| xml( |
| "res/values/strings.xml", |
| "" |
| + "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" |
| + "<!-- Copyright (C) 2007 The Android Open Source Project\n" |
| + "\n" |
| + " Licensed under the Apache License, Version 2.0 (the \"License\");\n" |
| + " you may not use this file except in compliance with the License.\n" |
| + " You may obtain a copy of the License at\n" |
| + "\n" |
| + " http://www.apache.org/licenses/LICENSE-2.0\n" |
| + "\n" |
| + " Unless required by applicable law or agreed to in writing, software\n" |
| + " distributed under the License is distributed on an \"AS IS\" BASIS,\n" |
| + " WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" |
| + " See the License for the specific language governing permissions and\n" |
| + " limitations under the License.\n" |
| + "-->\n" |
| + "\n" |
| + "<resources>\n" |
| + " <!-- Home -->\n" |
| + " <string name=\"home_title\">Home Sample</string>\n" |
| + " <string name=\"show_all_apps\">All</string>\n" |
| + "\n" |
| + " <!-- Home Menus -->\n" |
| + " <string name=\"menu_wallpaper\">Wallpaper</string>\n" |
| + " <string name=\"menu_search\">Search</string>\n" |
| + " <string name=\"menu_settings\">Settings</string>\n" |
| + " <string name=\"sample\" translatable=\"false\">Ignore Me</string>\n" |
| + "\n" |
| + " <!-- Wallpaper -->\n" |
| + " <string name=\"wallpaper_instructions\">Tap picture to set portrait wallpaper</string>\n" |
| + "</resources>\n" |
| + "\n"); |
| } |