Commit a41e42ec authored by Robbie McElrath's avatar Robbie McElrath Committed by Commit Bot

[WebLayer] Create org.chromium.components.content_settings.ContentSettingsFeatureList

This new FeatureList class contains the
IMPROVED_COOKIE_CONTROLS[_FOR_THIRD_PARTY_COOKIE_BLOCKING] features,
which are used by Clank's Site Settings UI, which is in the process of
being componentized so it can be used by WebLayer.

Bug: 1058597
Change-Id: I46ceae9dc71e6d96b89278fe7fd28ebfdf8e337d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2123336Reviewed-by: default avatarChristian Dullweber <dullweber@chromium.org>
Reviewed-by: default avatarHenrique Nakashima <hnakashima@chromium.org>
Reviewed-by: default avatarTheresa  <twellington@chromium.org>
Reviewed-by: default avatarFinnur Thorarinsson <finnur@chromium.org>
Commit-Queue: Robbie McElrath <rmcelrath@chromium.org>
Cr-Commit-Position: refs/heads/master@{#755931}
parent 6d3d8fed
......@@ -322,6 +322,7 @@ android_library("chrome_java") {
"//components/browser_ui/util/android:java",
"//components/browser_ui/widget/android:java",
"//components/content_capture/android:java",
"//components/content_settings/android:java",
"//components/contextual_search/content/common/mojom:mojom_java",
"//components/crash/android:java",
"//components/dom_distiller/content/browser/android:dom_distiller_content_java",
......
......@@ -13,6 +13,7 @@ include_rules = [
"+components/browser_ui/util/android",
"+components/browser_ui/widget/android",
"+components/content_capture",
"+components/content_settings",
"+components/download",
"+components/embedder_support/android",
"+components/external_intents/android",
......
......@@ -11,12 +11,12 @@ import android.widget.CompoundButton.OnCheckedChangeListener;
import org.chromium.base.ObserverList;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.settings.SettingsLauncher;
import org.chromium.chrome.browser.site_settings.CookieControlsServiceBridge;
import org.chromium.chrome.browser.site_settings.CookieControlsServiceBridge.CookieControlsServiceObserver;
import org.chromium.chrome.browser.site_settings.SingleCategorySettings;
import org.chromium.chrome.browser.site_settings.SiteSettingsCategory;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.CookieControlsEnforcement;
/**
......@@ -60,8 +60,9 @@ public class IncognitoCookieControlsManager
public void initialize() {
if (mIsInitialized) return;
if (ChromeFeatureList.isInitialized()
&& ChromeFeatureList.isEnabled(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)) {
if (ContentSettingsFeatureList.isInitialized()
&& ContentSettingsFeatureList.isEnabled(
ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)) {
mServiceBridge = new CookieControlsServiceBridge(this);
mShowCard = true;
}
......
......@@ -28,7 +28,7 @@ import androidx.preference.PreferenceViewHolder;
import org.chromium.base.ApiCompatibilityUtils;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.ui.KeyboardVisibilityDelegate;
/**
......@@ -123,8 +123,8 @@ public class AddExceptionPreference
(CheckBox) view.findViewById(R.id.third_parties_exception_checkbox);
if (!mCategory.showSites(SiteSettingsCategory.Type.COOKIES)
|| !ChromeFeatureList.isEnabled(
ChromeFeatureList
|| !ContentSettingsFeatureList.isEnabled(
ContentSettingsFeatureList
.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING)) {
thirdPartyExceptionsBox.setVisibility(View.GONE);
thirdPartyExceptionsBox.setChecked(false);
......
......@@ -51,6 +51,7 @@ import org.chromium.components.browser_ui.settings.ManagedPreferenceDelegate;
import org.chromium.components.browser_ui.settings.ManagedPreferencesUtils;
import org.chromium.components.browser_ui.settings.SearchUtils;
import org.chromium.components.browser_ui.settings.SettingsUtils;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.content_settings.CookieControlsMode;
import org.chromium.components.embedder_support.util.UrlUtilities;
......@@ -930,7 +931,8 @@ public class SingleCategorySettings extends SiteSettingsPreferenceFragment
}
if (!(mCategory.showSites(SiteSettingsCategory.Type.COOKIES)
&& ChromeFeatureList.isEnabled(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS))) {
&& ContentSettingsFeatureList.isEnabled(
ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS))) {
screen.removePreference(screen.findPreference(COOKIE_INFO_TEXT_KEY));
}
......
......@@ -9,8 +9,8 @@ import androidx.annotation.VisibleForTesting;
import org.chromium.base.Callback;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.profiles.Profile;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.location.LocationUtils;
......@@ -322,7 +322,8 @@ public class WebsitePreferenceBridge {
*/
public static boolean requiresFourStateContentSetting(int contentSettingsType) {
return contentSettingsType == ContentSettingsType.COOKIES
&& ChromeFeatureList.isEnabled(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS);
&& ContentSettingsFeatureList.isEnabled(
ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS);
}
/**
......
......@@ -29,7 +29,6 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
......@@ -37,6 +36,7 @@ import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features.DisableFeatures;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.CookieControlsMode;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
......@@ -45,7 +45,7 @@ import org.chromium.content_public.browser.test.util.TestThreadUtils;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)
@EnableFeatures(ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)
public class IncognitoNewTabPageTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
......@@ -80,7 +80,7 @@ public class IncognitoNewTabPageTest {
*/
@Test
@SmallTest
@DisableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)
@DisableFeatures(ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)
public void testCookieControlsCardGONE() throws Exception {
mActivityTestRule.newIncognitoTabFromMenu();
onView(withId(R.id.cookie_controls_card))
......
......@@ -25,13 +25,13 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.page_info.R;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
import org.chromium.content_public.common.ContentSwitches;
......@@ -42,12 +42,11 @@ import org.chromium.ui.test.util.DisableAnimationsTestRule;
* Tests for CookieControlsView.
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags
.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
@Features.
EnableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING)
public class CookieControlsViewTest {
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1"})
@Features.EnableFeatures(
ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING)
public class CookieControlsViewTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
new ChromeActivityTestRule<>(ChromeActivity.class);
......
......@@ -19,7 +19,6 @@ import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
......@@ -27,6 +26,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.content_settings.CookieControlsEnforcement;
import org.chromium.components.content_settings.CookieControlsMode;
......@@ -40,7 +40,7 @@ import org.chromium.net.test.EmbeddedTestServer;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)
@EnableFeatures(ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)
public class CookieControlsBridgeTest {
private class TestCallbackHandler implements CookieControlsBridge.CookieControlsObserver {
private CallbackHelper mHelper;
......
......@@ -17,7 +17,6 @@ import org.junit.runner.RunWith;
import org.chromium.base.test.util.CallbackHelper;
import org.chromium.base.test.util.CommandLineFlags;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.preferences.Pref;
import org.chromium.chrome.browser.preferences.PrefServiceBridge;
......@@ -25,6 +24,7 @@ import org.chromium.chrome.browser.tab.Tab;
import org.chromium.chrome.test.ChromeActivityTestRule;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.CookieControlsEnforcement;
import org.chromium.components.content_settings.CookieControlsMode;
import org.chromium.content_public.browser.test.util.TestThreadUtils;
......@@ -35,7 +35,7 @@ import org.chromium.net.test.EmbeddedTestServer;
*/
@RunWith(ChromeJUnit4ClassRunner.class)
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE})
@EnableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)
@EnableFeatures(ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)
public class CookieControlsServiceBridgeTest {
private class TestCallbackHandler
implements CookieControlsServiceBridge.CookieControlsServiceObserver {
......
......@@ -28,7 +28,6 @@ import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.base.test.util.RetryOnFailure;
import org.chromium.chrome.browser.ChromeActivity;
import org.chromium.chrome.browser.flags.ChromeFeatureList;
import org.chromium.chrome.browser.flags.ChromeSwitches;
import org.chromium.chrome.browser.infobar.InfoBarContainer;
import org.chromium.chrome.browser.notifications.channels.ChannelDefinitions;
......@@ -46,6 +45,7 @@ import org.chromium.chrome.test.util.browser.Features.EnableFeatures;
import org.chromium.chrome.test.util.browser.LocationSettingsTestUtil;
import org.chromium.components.browser_ui.settings.ChromeBaseCheckBoxPreference;
import org.chromium.components.browser_ui.settings.ChromeSwitchPreference;
import org.chromium.components.content_settings.ContentSettingsFeatureList;
import org.chromium.components.content_settings.ContentSettingsType;
import org.chromium.components.embedder_support.util.Origin;
import org.chromium.components.permissions.nfc.NfcSystemLevelSetting;
......@@ -67,7 +67,7 @@ import java.util.concurrent.Callable;
@RetryOnFailure
@CommandLineFlags.Add({ChromeSwitches.DISABLE_FIRST_RUN_EXPERIENCE,
ContentSwitches.HOST_RESOLVER_RULES + "=MAP * 127.0.0.1", "ignore-certificate-errors"})
@EnableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)
@EnableFeatures(ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)
public class SiteSettingsTest {
@Rule
public ChromeActivityTestRule<ChromeActivity> mActivityTestRule =
......@@ -251,8 +251,9 @@ public class SiteSettingsTest {
}
private boolean improvedControlsEnabled() {
return ChromeFeatureList.isEnabled(
ChromeFeatureList.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING);
return ContentSettingsFeatureList.isEnabled(
ContentSettingsFeatureList
.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING);
}
});
}
......@@ -395,7 +396,7 @@ public class SiteSettingsTest {
@Test
@SmallTest
@Feature({"Preferences"})
@DisableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS)
@DisableFeatures(ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS)
public void testThirdPartyCookieToggleGetsDisabledOld() {
SettingsActivity settingsActivity =
SiteSettingsTestUtils.startSiteSettingsCategory(SiteSettingsCategory.Type.COOKIES);
......@@ -465,7 +466,8 @@ public class SiteSettingsTest {
@SmallTest
@Feature({"Preferences"})
// Todo(eokoyomon) figure out how to set and test third party cookie setting in this test
// @EnableFeatures(ChromeFeatureList.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING)
// @EnableFeatures(
// ContentSettingsFeatureList.IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING)
public void testSiteExceptionCookiesBlocked() throws Exception {
SettingsActivity settingsActivity =
SiteSettingsTestUtils.startSiteSettingsCategory(SiteSettingsCategory.Type.COOKIES);
......
......@@ -3015,6 +3015,7 @@ jumbo_static_library("browser") {
"//components/cbor",
"//components/cdm/browser",
"//components/content_capture/android",
"//components/content_settings/android",
"//components/crash/android:crash_android",
"//components/embedder_support/android:util",
"//components/embedder_support/android:web_contents_delegate",
......
......@@ -20,7 +20,6 @@
#include "components/autofill/core/common/autofill_payments_features.h"
#include "components/autofill_assistant/browser/features.h"
#include "components/browser_sync/browser_sync_switches.h"
#include "components/content_settings/core/common/features.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_features.h"
#include "components/download/public/common/download_features.h"
#include "components/feature_engagement/public/feature_list.h"
......@@ -71,8 +70,6 @@ const base::Feature* kFeaturesExposedToJava[] = {
&autofill_assistant::features::kAutofillAssistantChromeEntry,
&autofill_assistant::features::kAutofillAssistantDirectActions,
&autofill::features::kAutofillTouchToFill,
&content_settings::kImprovedCookieControls,
&content_settings::kImprovedCookieControlsForThirdPartyCookieBlocking,
&device::kWebAuthPhoneSupport,
&download::features::kDownloadAutoResumptionNative,
&download::features::kUseDownloadOfflineContentProvider,
......
......@@ -299,9 +299,6 @@ public abstract class ChromeFeatureList {
public static final String HOMEPAGE_SETTINGS_UI_CONVERSION = "HomepageSettingsUIConversion";
public static final String HORIZONTAL_TAB_SWITCHER_ANDROID = "HorizontalTabSwitcherAndroid";
public static final String IMMERSIVE_UI_MODE = "ImmersiveUiMode";
public static final String IMPROVED_COOKIE_CONTROLS = "ImprovedCookieControls";
public static final String IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING =
"ImprovedCookieControlsForThirdPartyCookieBlocking";
public static final String INLINE_UPDATE_FLOW = "InlineUpdateFlow";
public static final String INSTALLABLE_AMBIENT_BADGE_INFOBAR = "InstallableAmbientBadgeInfoBar";
public static final String INSTANT_START = "InstantStart";
......
# Copyright 2020 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import("//build/config/android/rules.gni")
generate_jni("content_settings_jni_headers") {
sources = [ "java/src/org/chromium/components/content_settings/ContentSettingsFeatureList.java" ]
}
android_library("java") {
sources = [ "java/src/org/chromium/components/content_settings/ContentSettingsFeatureList.java" ]
deps = [
"//base:base_java",
"//base:jni_java",
]
annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ]
}
source_set("android") {
sources = [ "content_settings_feature_list.cc" ]
deps = [
":content_settings_jni_headers",
"//base",
"//components/content_settings/core/common",
]
}
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/android/jni_string.h"
#include "base/feature_list.h"
#include "base/stl_util.h"
#include "components/content_settings/android/content_settings_jni_headers/ContentSettingsFeatureList_jni.h"
#include "components/content_settings/core/common/features.h"
using base::android::ConvertJavaStringToUTF8;
using base::android::JavaParamRef;
namespace content_settings {
namespace {
// Array of features exposed through the Java ContentFeatureList API. Entries in
// this array may either refer to features defined in the header of this file or
// in other locations in the code base (e.g. content_features.h).
const base::Feature* kFeaturesExposedToJava[] = {
&kImprovedCookieControls,
&kImprovedCookieControlsForThirdPartyCookieBlocking,
};
// TODO(crbug.com/1060097): Remove this once a generalized FeatureList exists.
const base::Feature* FindFeatureExposedToJava(const std::string& feature_name) {
for (size_t i = 0; i < base::size(kFeaturesExposedToJava); ++i) {
if (kFeaturesExposedToJava[i]->name == feature_name)
return kFeaturesExposedToJava[i];
}
NOTREACHED() << "Queried feature not found in ContentSettingsFeatureList: "
<< feature_name;
return nullptr;
}
} // namespace
static jboolean JNI_ContentSettingsFeatureList_IsInitialized(JNIEnv* env) {
return !!base::FeatureList::GetInstance();
}
static jboolean JNI_ContentSettingsFeatureList_IsEnabled(
JNIEnv* env,
const JavaParamRef<jstring>& jfeature_name) {
const base::Feature* feature =
FindFeatureExposedToJava(ConvertJavaStringToUTF8(env, jfeature_name));
return base::FeatureList::IsEnabled(*feature);
}
} // namespace content_settings
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.components.content_settings;
import org.chromium.base.FeatureList;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.base.annotations.MainDex;
import org.chromium.base.annotations.NativeMethods;
import org.chromium.base.library_loader.LibraryLoader;
/**
* Provides an API for querying the status of Content Settings features.
*/
// TODO(crbug.com/1060097): Remove/update this once a generalized FeatureList exists.
@JNINamespace("content_settings")
@MainDex
public class ContentSettingsFeatureList {
public static final String IMPROVED_COOKIE_CONTROLS = "ImprovedCookieControls";
public static final String IMPROVED_COOKIE_CONTROLS_FOR_THIRD_PARTY_COOKIE_BLOCKING =
"ImprovedCookieControlsForThirdPartyCookieBlocking";
private ContentSettingsFeatureList() {}
/**
* @return Whether the native FeatureList has been initialized. If this method returns false,
* none of the methods in this class that require native access should be called.
*/
public static boolean isInitialized() {
if (FeatureList.hasTestFeatures()) return true;
return isNativeInitialized();
}
/**
* Returns whether the specified feature is enabled or not.
*
* Note: Features queried through this API must be added to the array
* |kFeaturesExposedToJava| in
* //components/content_settings/android/content_settings_feature_list.cc
*
* @param featureName The name of the feature to query.
* @return Whether the feature is enabled or not.
*/
public static boolean isEnabled(String featureName) {
Boolean testValue = FeatureList.getTestValueForFeature(featureName);
if (testValue != null) return testValue;
assert isNativeInitialized();
return ContentSettingsFeatureListJni.get().isEnabled(featureName);
}
/**
* @return Whether the native FeatureList is initialized or not.
*/
private static boolean isNativeInitialized() {
if (FeatureList.hasTestFeatures()) return true;
if (!LibraryLoader.getInstance().isInitialized()) return false;
// Even if the native library is loaded, the C++ FeatureList might not be initialized yet.
// In that case, accessing it will not immediately fail, but instead cause a crash later
// when it is initialized. Return whether the native FeatureList has been initialized,
// so the return value can be tested, or asserted for a more actionable stack trace
// on failure.
//
// The FeatureList is however guaranteed to be initialized by the time
// AsyncInitializationActivity#finishNativeInitialization is called.
return ContentSettingsFeatureListJni.get().isInitialized();
}
@NativeMethods
interface Natives {
boolean isInitialized();
boolean isEnabled(String featureName);
}
}
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